Esanders has uploaded a new change for review. https://gerrit.wikimedia.org/r/297693
Change subject: Rewrite TOCWidget based on Linker::generateTOC ...................................................................... Rewrite TOCWidget based on Linker::generateTOC Change-Id: I5eb75c5db5ca466fd6f16a57c693c2a4458cff7c --- M extension.json M modules/ve-mw/init/targets/ve.init.mw.DesktopArticleTarget.js D modules/ve-mw/ui/styles/widgets/ve.ui.MWTocWidget.css D modules/ve-mw/ui/widgets/ve.ui.MWTocItemWidget.js M modules/ve-mw/ui/widgets/ve.ui.MWTocWidget.js 5 files changed, 69 insertions(+), 264 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/VisualEditor refs/changes/93/297693/1 diff --git a/extension.json b/extension.json index 85eaa1b..cf6102e 100644 --- a/extension.json +++ b/extension.json @@ -1130,7 +1130,6 @@ "modules/ve-mw/ui/datatransferhandlers/ve.ui.MWWikitextStringTransferHandler.js", "modules/ve-mw/ui/widgets/ve.ui.MWAceEditorWidget.js", "modules/ve-mw/ui/widgets/ve.ui.MWTargetWidget.js", - "modules/ve-mw/ui/widgets/ve.ui.MWTocItemWidget.js", "modules/ve-mw/ui/widgets/ve.ui.MWTocWidget.js", "modules/ve-mw/ui/dialogs/ve.ui.MWExtensionDialog.js", "modules/ve-mw/ui/dialogs/ve.ui.MWExtensionPreviewDialog.js", @@ -1155,7 +1154,6 @@ "modules/ve-mw/ui/styles/elements/ve.ui.MWExpandableErrorElement.css", "modules/ve-mw/ui/styles/tools/ve.ui.MWPopupTool.css", "modules/ve-mw/ui/styles/widgets/ve.ui.MWAceEditorWidget.css", - "modules/ve-mw/ui/styles/widgets/ve.ui.MWTocWidget.css", "modules/ve-mw/ui/styles/tools/ve.ui.MWEducationPopupTool.css" ], "skinStyles": { diff --git a/modules/ve-mw/init/targets/ve.init.mw.DesktopArticleTarget.js b/modules/ve-mw/init/targets/ve.init.mw.DesktopArticleTarget.js index c5b5ad8..c70004e 100644 --- a/modules/ve-mw/init/targets/ve.init.mw.DesktopArticleTarget.js +++ b/modules/ve-mw/init/targets/ve.init.mw.DesktopArticleTarget.js @@ -721,7 +721,7 @@ // TODO: mwTocWidget should probably live in a ve.ui.MWSurface subclass if ( mw.config.get( 'wgVisualEditorConfig' ).enableTocWidget ) { surface.mwTocWidget = new ve.ui.MWTocWidget( this.getSurface() ); - surface.$element.prepend( surface.mwTocWidget.$element ); + surface.$element.before( surface.mwTocWidget.$element ); } // Track how long it takes for the first transaction to happen @@ -1023,9 +1023,6 @@ // Update UI promises.push( this.teardownToolbar() ); this.restoreDocumentTitle(); - if ( this.getSurface().mwTocWidget ) { - this.getSurface().mwTocWidget.teardown(); - } if ( this.saveDialog ) { if ( this.saveDialog.isOpened() ) { diff --git a/modules/ve-mw/ui/styles/widgets/ve.ui.MWTocWidget.css b/modules/ve-mw/ui/styles/widgets/ve.ui.MWTocWidget.css deleted file mode 100644 index 816438a..0000000 --- a/modules/ve-mw/ui/styles/widgets/ve.ui.MWTocWidget.css +++ /dev/null @@ -1,28 +0,0 @@ -/*! - * VisualEditor MediaWiki UserInterface MWTocWidget styles. - * - * @copyright 2011-2016 VisualEditor Team and others; see AUTHORS.txt - * @license The MIT License (MIT); see LICENSE.txt - */ - -.ve-ui-mwTocWidget { - /* Margin to mock the standard appearance of TOC */ - margin: 1em 0 0 0; -} -.ve-ui-mwTocWidget .toctoggle { - margin: 0.25em; -} -.ve-ui-mwTocWidget .toctoggle:before { - content: ' ['; -} -.ve-ui-mwTocWidget .toctoggle:after { - content: '] '; -} - -.ve-ui-mwTocWidget .tocnumber:after { - content: ' '; -} - -.ve-ui-mwTocWidget a { - cursor: pointer; -} diff --git a/modules/ve-mw/ui/widgets/ve.ui.MWTocItemWidget.js b/modules/ve-mw/ui/widgets/ve.ui.MWTocItemWidget.js deleted file mode 100644 index 435d9d2..0000000 --- a/modules/ve-mw/ui/widgets/ve.ui.MWTocItemWidget.js +++ /dev/null @@ -1,89 +0,0 @@ -/*! - * VisualEditor UserInterface MWTocItemWidget class. - * - * @copyright 2011-2016 VisualEditor Team and others; see AUTHORS.txt - * @license The MIT License (MIT); see LICENSE.txt - */ - -/** - * Creates an item an item for the MWTocWidget - * - * @class - * @extends OO.ui.Widget - * @mixins OO.ui.mixin.GroupElement - * - * @constructor - * @param {Object} config TOC Item configuration - * @cfg {ve.ce.Node} node ContentEditable node - * @cfg {ve.ui.MWTocItemWidget} parent Parent toc item - * @cfg {string} sectionPrefix TOC item section number - * @cfg {number} tocLevel Depth level of the TOC item - * @cfg {number} tocIndex Running count of TOC items - * - */ -ve.ui.MWTocItemWidget = function VeUiMWTocItemWidget( config ) { - // Parent constructor - OO.ui.Widget.call( this, config ); - - // Mixin Constructor - OO.ui.mixin.GroupElement.call( this, $.extend( {}, config, { $group: $( '<ul>' ) } ) ); - - config = config || {}; - - // Properties - this.node = config.node || null; - this.parent = config.parent; - this.sectionPrefix = config.sectionPrefix; - this.tocLevel = config.tocLevel; - this.tocIndex = config.tocIndex; - - // Allows toc items to be optionally associated to a node. - // For the case of the zero level parent item. - if ( this.node ) { - this.$tocNumber = $( '<span>' ).addClass( 'tocnumber' ) - .text( this.sectionPrefix ); - this.$tocText = $( '<span>' ).addClass( 'toctext' ) - .text( this.node.$element.text() ); - this.$element - .addClass( 'toclevel-' + this.tocLevel ) - .addClass( 'tocsection-' + this.tocIndex ) - .append( $( '<a>' ).append( this.$tocNumber, this.$tocText ) ); - - // Monitor node events - this.node.model.connect( this, { update: 'onUpdate' } ); - } - this.$element.append( this.$group ); -}; - -/* Inheritance */ - -OO.inheritClass( ve.ui.MWTocItemWidget, OO.ui.Widget ); - -OO.mixinClass( ve.ui.MWTocItemWidget, OO.ui.mixin.GroupElement ); - -/* Static Properties */ - -ve.ui.MWTocItemWidget.static.tagName = 'li'; - -/* Methods */ - -/** - * Updates the text of the toc item - * - */ -ve.ui.MWTocItemWidget.prototype.onUpdate = function () { - var widget = this; - // Timeout needed to let the dom element actually update - setTimeout( function () { - widget.$tocText.text( widget.node.$element.text() ); - } ); -}; - -/** - * Removes this toc item from its parent - * - */ -ve.ui.MWTocItemWidget.prototype.remove = function () { - this.node.model.disconnect( this ); - this.parent.removeItems( [ this ] ); -}; diff --git a/modules/ve-mw/ui/widgets/ve.ui.MWTocWidget.js b/modules/ve-mw/ui/widgets/ve.ui.MWTocWidget.js index 9b4d38e..67d20c8 100644 --- a/modules/ve-mw/ui/widgets/ve.ui.MWTocWidget.js +++ b/modules/ve-mw/ui/widgets/ve.ui.MWTocWidget.js @@ -16,7 +16,6 @@ * @param {Object} [config] Configuration options */ ve.ui.MWTocWidget = function VeUiMWTocWidget( surface, config ) { - var widget = this; // Parent constructor OO.ui.Widget.call( this, config ); @@ -26,42 +25,22 @@ this.doc = surface.getModel().getDocument(); this.metaList = surface.getModel().metaList; // Topic level 0 lives inside of a toc item - this.topics = new ve.ui.MWTocItemWidget(); - // Place for a cloned previous toc to live while rebuilding. - this.$tempTopics = $( '<ul>' ); - // Section keyed item map - this.items = {}; + this.rootLength = 0; this.initialized = false; // Page settings cache this.mwTOCForce = false; this.mwTOCDisable = false; - // TODO: fix i18n - this.tocToggle = { - hideMsg: ve.msg( 'hidetoc' ), - showMsg: ve.msg( 'showtoc' ), - $link: $( '<a class="internal" id="togglelink"></a>' ).text( ve.msg( 'hidetoc' ) ), - open: true - }; + this.$tocList = $( '<ul>' ); this.$element.addClass( 'toc ve-ui-mwTocWidget' ).append( - $( '<div>' ).attr( 'id', 'toctitle' ).append( - $( '<h2>' ).text( ve.msg( 'toc' ) ), - $( '<span>' ).addClass( 'toctoggle' ).append( this.tocToggle.$link ) + $( '<div>' ).addClass( 'toctitle' ).append( + $( '<h2>' ).text( ve.msg( 'toc' ) ) ), - this.topics.$group, this.$tempTopics + this.$tocList ); - this.tocToggle.$link.on( 'click', function () { - if ( widget.tocToggle.open ) { - widget.tocToggle.$link.text( widget.tocToggle.showMsg ); - widget.tocToggle.open = false; - } else { - widget.tocToggle.$link.text( widget.tocToggle.hideMsg ); - widget.tocToggle.open = true; - } - // FIXME: We should really use CSS here - widget.topics.$group.add( widget.$tempTopics ).slideToggle(); - } ); + // Setup toggle link + mw.hook( 'wikipage.content' ).fire( this.$element ); this.metaList.connect( this, { insert: 'onMetaListInsert', @@ -90,7 +69,7 @@ // hide this.mwTOCDisable = true; } - this.hideOrShow(); + this.updateVisibility(); }; /** @@ -104,7 +83,7 @@ } else if ( metaItem instanceof ve.dm.MWTOCDisableMetaItem ) { this.mwTOCDisable = false; } - this.hideOrShow(); + this.updateVisibility(); }; /** @@ -124,17 +103,17 @@ this.mwTOCDisable = true; } } - this.hideOrShow(); + this.updateVisibility(); } }; /** * Hides or shows the TOC based on page and default settings */ -ve.ui.MWTocWidget.prototype.hideOrShow = function () { +ve.ui.MWTocWidget.prototype.updateVisibility = function () { // In MediaWiki if __FORCETOC__ is anywhere TOC is always displayed // ... Even if there is a __NOTOC__ in the article - this.toggle( !this.mwTOCDisable && ( this.mwTOCForce || this.topics.items.length >= 3 ) ); + this.toggle( !this.mwTOCDisable && ( this.mwTOCForce || this.rootLength >= 3 ) ); }; /** @@ -144,130 +123,78 @@ ve.ui.MWTocWidget.prototype.rebuild = ve.debounce( function () { var widget = this; // Only rebuild when initialized - if ( this.surface.mwTocWidget.initialized ) { - this.$tempTopics.append( this.topics.$group.children().clone() ); - this.teardownItems(); + if ( this.initialized ) { // Build after transactions setTimeout( function () { widget.build(); - widget.$tempTopics.empty(); - }, 0 ); + } ); } -}, 0 ); - -/** - * Teardown all of the TOC items - */ -ve.ui.MWTocWidget.prototype.teardownItems = function () { - var item; - for ( item in this.items ) { - this.items[ item ].remove(); - delete this.items[ item ]; - } - this.items = {}; -}; - -/** - * Teardown the widget and remove it from the dom - */ -ve.ui.MWTocWidget.prototype.teardown = function () { - this.teardownItems(); - this.$element.remove(); -}; +} ); /** * Build TOC from mwHeading dm nodes + * + * Based on generateTOC in Linker.php */ ve.ui.MWTocWidget.prototype.build = function () { - var i, l, node, + var i, l, level, levelDiff, tocNumber, modelNode, viewNode, + $list, $text, $item, $link, nodes = this.doc.getNodesByType( 'mwHeading', true ), - headingLevel = 0, - previousHeadingLevel = 0, - parentHeadingLevel = 0, - levelSkipped = false, - tocNumber = 0, - tocLevel = 0, - tocSection = 0, - sectionPrefix = [], - parentSectionArray, - key, - parent, - config, - ceNode; + lastLevel = 0, + stack = []; - this.topics.clearItems(); + function getItemIndex( $list, n ) { + return $list.children( 'li' ).length + ( n === stack.length - 1 ? 1 : 0 ); + } + + function linkClickHandler( heading ) { + ve.init.target.goToHeading( heading ); + return false; + } + + function setHeadingText( $text, headingNode ) { + $text.text( headingNode.$element.text() ); + } + + this.$tocList.empty(); for ( i = 0, l = nodes.length; i < l; i++ ) { - node = nodes[ i ]; - headingLevel = node.getAttribute( 'level' ); - // MW TOC Generation - // The first heading will always be be a zero level topic, even heading levels > 2 - // If heading level is 1 then it is definitely a zero level topic - // If heading level is 2 then it is a zero level topic, unless a child of a 1 level - // If heading went up and skipped a number, the following headings of the skipped number are in the same level - if ( this.topics.items.length === 0 || headingLevel === 1 || ( headingLevel === 2 && parentHeadingLevel !== 1 ) ) { - tocSection++; - sectionPrefix = [ tocSection ]; - tocLevel = 0; - // reset - levelSkipped = false; - parent = this.topics; - parentHeadingLevel = headingLevel; - } else { - // If previously skipped a level, place this heading in the same level as the previous higher one - if ( headingLevel === previousHeadingLevel || headingLevel < previousHeadingLevel && levelSkipped ) { - tocNumber++; - sectionPrefix.pop(); - sectionPrefix.push( tocNumber ); - // Only remove the flag if the heading level has dropped but we skipped to a higher number previously - if ( headingLevel < previousHeadingLevel ) { - levelSkipped = false; - } + modelNode = nodes[ i ]; + level = modelNode.getAttribute( 'level' ); + + if ( level > lastLevel ) { + if ( stack.length ) { + $list = $( '<ul>' ); + stack[ stack.length - 1 ].children().last().append( $list ); } else { - tocNumber = 1; - // Heading not the same as before - if ( headingLevel > previousHeadingLevel ) { - // Did we skip a level? Flag in case we drop down a number - if ( headingLevel - previousHeadingLevel > 1 ) { - levelSkipped = true; - } - tocLevel++; - sectionPrefix.push( tocNumber ); - // Step to lower level unless we are at 1 - } else if ( headingLevel < previousHeadingLevel && tocLevel !== 1 ) { - tocLevel--; - sectionPrefix.pop(); - tocNumber = sectionPrefix[ sectionPrefix.length - 1 ] + 1; - sectionPrefix.pop(); - sectionPrefix.push( tocNumber ); - } + $list = this.$tocList; + } + stack.push( $list ); + } else if ( level < lastLevel ) { + levelDiff = lastLevel - level; + while ( levelDiff > 0 ) { + stack.pop(); + levelDiff--; } } - // Determine parent - parentSectionArray = sectionPrefix.slice( 0 ); - parentSectionArray.pop(); - if ( parentSectionArray.length > 0 ) { - key = parentSectionArray.join( '.' ); - parent = this.items[ key ]; - } else { - // Topic level is zero - parent = this.topics; - } - ceNode = this.surface.getView().getDocument().getBranchNodeFromOffset( node.getRange().start ); - config = { - node: ceNode, - tocIndex: i, - parent: parent, - tocLevel: tocLevel, - tocSection: tocSection, - sectionPrefix: sectionPrefix.join( '.' ), - insertIndex: sectionPrefix[ sectionPrefix.length - 1 ] - }; - // Add item - this.items[ sectionPrefix.join( '.' ) ] = new ve.ui.MWTocItemWidget( config ); - config.parent.addItems( [ this.items[ sectionPrefix.join( '.' ) ] ], config.insertIndex ); - previousHeadingLevel = headingLevel; + + tocNumber = stack.map( getItemIndex ).join( '.' ); + viewNode = this.surface.getView().getDocument().getBranchNodeFromOffset( modelNode.getRange().start ); + $item = $( '<li>' ).addClass( 'toclevel-' + stack.length ).addClass( 'tocsection-' + ( i + 1 ) ); + $link = $( '<a href="#">' ).append( '<span class="tocnumber">' + tocNumber + '</span> ' ); + $text = $( '<span>' ).addClass( 'toctext' ); + + setHeadingText( $text, viewNode ); + modelNode.on( 'update', setHeadingText.bind( this, $text, viewNode ) ); + + stack[ stack.length - 1 ].append( $item.append( $link.append( $text ) ) ); + $link.on( 'click', linkClickHandler.bind( this, viewNode ) ); + + lastLevel = level; } + + this.rootLength = stack[ 0 ].children().length; + this.initialized = true; - this.hideOrShow(); + this.updateVisibility(); }; -- To view, visit https://gerrit.wikimedia.org/r/297693 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I5eb75c5db5ca466fd6f16a57c693c2a4458cff7c Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/VisualEditor Gerrit-Branch: master Gerrit-Owner: Esanders <esand...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits