Alex Monk has uploaded a new change for review. https://gerrit.wikimedia.org/r/271701
Change subject: Move redirects out of the meta list into actual nodes ...................................................................... Move redirects out of the meta list into actual nodes TODO: Make these un-draggable Bug: T63861 Change-Id: Ia07d5e385c542ef4fa96243a6df851a8b744a7ca --- M extension.json A modules/ve-mw/ce/nodes/ve.ce.MWRedirectNode.js D modules/ve-mw/dm/metaitems/ve.dm.MWRedirectMetaItem.js A modules/ve-mw/dm/nodes/ve.dm.MWRedirectNode.js M modules/ve-mw/i18n/en.json M modules/ve-mw/i18n/qqq.json M modules/ve-mw/init/targets/ve.init.mw.DesktopArticleTarget.js M modules/ve-mw/ui/dialogs/ve.ui.MWMetaDialog.js M modules/ve-mw/ui/pages/ve.ui.MWSettingsPage.js 9 files changed, 240 insertions(+), 93 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/VisualEditor refs/changes/01/271701/1 diff --git a/extension.json b/extension.json index ab99cf3..128b66b 100644 --- a/extension.json +++ b/extension.json @@ -1407,10 +1407,11 @@ "modules/ve-mw/dm/metaitems/ve.dm.MWNoEditSectionMetaItem.js", "modules/ve-mw/dm/metaitems/ve.dm.MWNoGalleryMetaItem.js", "modules/ve-mw/dm/metaitems/ve.dm.MWNoTitleConvertMetaItem.js", - "modules/ve-mw/dm/metaitems/ve.dm.MWRedirectMetaItem.js", "modules/ve-mw/dm/metaitems/ve.dm.MWStaticRedirectMetaItem.js", "modules/ve-mw/dm/metaitems/ve.dm.MWTOCDisableMetaItem.js", "modules/ve-mw/dm/metaitems/ve.dm.MWTOCForceMetaItem.js", + "modules/ve-mw/dm/nodes/ve.dm.MWRedirectNode.js", + "modules/ve-mw/ce/nodes/ve.ce.MWRedirectNode.js", "modules/ve-mw/ui/widgets/ve.ui.MWCategoryInputWidget.js", "modules/ve-mw/ui/widgets/ve.ui.MWCategoryPopupWidget.js", "modules/ve-mw/ui/widgets/ve.ui.MWCategoryItemWidget.js", @@ -1433,11 +1434,15 @@ "dependencies": [ "ext.visualEditor.mwcore", "ext.visualEditor.mwlink", + "mediawiki.action.view.redirectPage", "jquery.uls.data" ], "messages": [ + "redirectto", + "visualeditor-advancedsettings-tool", "visualeditor-categories-tool", + "visualeditor-ce-redirectnode-description", "visualeditor-dialog-meta-advancedsettings-label", "visualeditor-dialog-meta-advancedsettings-section", "visualeditor-dialog-meta-categories-category", diff --git a/modules/ve-mw/ce/nodes/ve.ce.MWRedirectNode.js b/modules/ve-mw/ce/nodes/ve.ce.MWRedirectNode.js new file mode 100644 index 0000000..14e3830 --- /dev/null +++ b/modules/ve-mw/ce/nodes/ve.ce.MWRedirectNode.js @@ -0,0 +1,94 @@ +/*! + * VisualEditor ContentEditable MediaWiki RedirectNode class. + * + * @copyright 2011-2016 VisualEditor Team and others; see http://ve.mit-license.org + */ + +/** + * ContentEditable MW redirect node. + * + * @class + * @extends ve.ce.LeafNode + * @mixins ve.ce.FocusableNode + * + * @constructor + * @param {ve.dm.MWRedirectNode} model Model to observe + * @param {Object} [config] Configuration options + */ +ve.ce.MWRedirectNode = function VeCeMwRedirectNode( model, config ) { + // Parent constructor + ve.ce.MWRedirectNode.super.call( this, model, config ); + + // Mixin constructors + ve.ce.FocusableNode.call( this, this.$element, config ); + + model.connect( this, { attributeChange: 'onAttributeChange' } ); + + // DOM changes + this.$element.addClass( 've-ce-mwRedirectNode' ) + .append( $( '<div>' ) + .addClass( 'redirectMsg' ) + .append( + $( '<p>' ).text( mw.msg( 'redirectto' ) ), + $( '<ul>' ) + .addClass( 'redirectText' ) + .append( $( '<li>' ).append( + $( '<a>' ) + .attr( 'title', model.getAttribute( 'title' ) ) + .attr( + 'href', + ve.dm.MWInternalLinkAnnotation.static.getHref( model.element ) + ) + .text( model.getAttribute( 'title' ) ) + ) ) + ) + ); +}; + +/* Inheritance */ + +OO.inheritClass( ve.ce.MWRedirectNode, ve.ce.LeafNode ); +OO.mixinClass( ve.ce.MWRedirectNode, ve.ce.FocusableNode ); + +/* Static Properties */ + +ve.ce.MWRedirectNode.static.name = 'mwRedirect'; + +ve.ce.MWRedirectNode.static.primaryCommandName = 'meta/settings'; + +/* Static Methods */ + +/** + * @inheritdoc + */ +ve.ce.MWRedirectNode.static.getDescription = function ( model ) { + return mw.msg( + 'visualeditor-ce-redirectnode-description', + model.getAttribute( 'title' ) + ); +}; + +/* Methods */ + +/** + * Handle attribute change events. + * + * @method + * @param {string} key Attribute key + * @param {string} from Old value + * @param {string} to New value + */ +ve.ce.MWRedirectNode.prototype.onAttributeChange = function ( key, from, to ) { + if ( key === 'title' ) { + this.$element.find( 'ul.redirectText a' ) + .attr( { + title: to, + href: ve.dm.MWInternalLinkAnnotation.static.getHref( this.model.element ) + } ) + .text( to ); + } +}; + +/* Registration */ + +ve.ce.nodeFactory.register( ve.ce.MWRedirectNode ); diff --git a/modules/ve-mw/dm/metaitems/ve.dm.MWRedirectMetaItem.js b/modules/ve-mw/dm/metaitems/ve.dm.MWRedirectMetaItem.js deleted file mode 100644 index df78741..0000000 --- a/modules/ve-mw/dm/metaitems/ve.dm.MWRedirectMetaItem.js +++ /dev/null @@ -1,52 +0,0 @@ -/*! - * VisualEditor DataModel MWRedirectMetaItem class. - * - * @copyright 2011-2016 VisualEditor Team and others; see AUTHORS.txt - * @license The MIT License (MIT); see LICENSE.txt - */ - -/** - * DataModel redirect meta item. - * - * @class - * @extends ve.dm.MetaItem - * @constructor - * @param {Object} element Reference to element in meta-linmod - */ -ve.dm.MWRedirectMetaItem = function VeDmMWRedirectMetaItem( element ) { - // Parent constructor - ve.dm.MetaItem.call( this, element ); -}; - -/* Inheritance */ - -OO.inheritClass( ve.dm.MWRedirectMetaItem, ve.dm.MetaItem ); - -/* Static Properties */ - -ve.dm.MWRedirectMetaItem.static.name = 'mwRedirect'; - -ve.dm.MWRedirectMetaItem.static.group = 'mwRedirect'; - -ve.dm.MWRedirectMetaItem.static.matchTagNames = [ 'link' ]; - -ve.dm.MWRedirectMetaItem.static.matchRdfaTypes = [ 'mw:PageProp/redirect' ]; - -ve.dm.MWRedirectMetaItem.static.toDataElement = function ( domElements, converter ) { - // HACK piggy-back on MWInternalLinkAnnotation's ./ stripping logic - var linkData = ve.dm.MWInternalLinkAnnotation.static.toDataElement( domElements, converter ); - linkData.type = this.name; - return linkData; -}; - -ve.dm.MWRedirectMetaItem.static.toDomElements = function ( dataElement, doc ) { - var meta = doc.createElement( 'link' ); - meta.setAttribute( 'rel', 'mw:PageProp/redirect' ); - // HACK piggy-back on MWInternalLinkAnnotation's logic - meta.setAttribute( 'href', ve.dm.MWInternalLinkAnnotation.static.getHref( dataElement ) ); - return [ meta ]; -}; - -/* Registration */ - -ve.dm.modelRegistry.register( ve.dm.MWRedirectMetaItem ); diff --git a/modules/ve-mw/dm/nodes/ve.dm.MWRedirectNode.js b/modules/ve-mw/dm/nodes/ve.dm.MWRedirectNode.js new file mode 100644 index 0000000..8608166 --- /dev/null +++ b/modules/ve-mw/dm/nodes/ve.dm.MWRedirectNode.js @@ -0,0 +1,59 @@ +/*! + * VisualEditor DataModel MediaWiki RedirectNode class. + * + * @copyright 2011-2016 VisualEditor Team and others; see http://ve.mit-license.org + */ + +/** + * @class + * @abstract + * @extends ve.dm.LeafNode + * @mixins ve.dm.FocusableNode + * + * @constructor + * @param {Object} element Reference to element in meta-linmod + */ +ve.dm.MWRedirectNode = function VeDmMwRedirectNode( element ) { + // Parent constructor + ve.dm.MWRedirectNode.super.call( this, element ); + + // Mixin constructors + ve.dm.FocusableNode.call( this ); +}; + +/* Inheritance */ + +OO.inheritClass( ve.dm.MWRedirectNode, ve.dm.LeafNode ); + +OO.mixinClass( ve.dm.MWRedirectNode, ve.dm.FocusableNode ); + +/* Static Properties */ + +ve.dm.MWRedirectNode.static.isContent = true; + +ve.dm.MWRedirectNode.static.preserveHtmlAttributes = false; + +ve.dm.MWRedirectNode.static.name = 'mwRedirect'; + +ve.dm.MWRedirectNode.static.matchTagNames = [ 'link' ]; + +ve.dm.MWRedirectNode.static.matchRdfaTypes = [ 'mw:PageProp/redirect' ]; + +ve.dm.MWRedirectNode.static.toDataElement = function ( domElements, converter ) { + // HACK piggy-back on MWInternalLinkAnnotation's ./ stripping logic + var linkData = ve.dm.MWInternalLinkAnnotation.static.toDataElement( domElements, converter ); + linkData.type = this.name; + return linkData; +}; + +ve.dm.MWRedirectNode.static.toDomElements = function ( dataElement, doc ) { + var meta = doc.createElement( 'link' ); + meta.setAttribute( 'rel', 'mw:PageProp/redirect' ); + // HACK piggy-back on MWInternalLinkAnnotation's logic + meta.setAttribute( 'href', ve.dm.MWInternalLinkAnnotation.static.getHref( dataElement ) ); + return [ meta ]; +}; + +/* Registration */ + +ve.dm.modelRegistry.register( ve.dm.MWRedirectNode ); \ No newline at end of file diff --git a/modules/ve-mw/i18n/en.json b/modules/ve-mw/i18n/en.json index 06ee990..3b7debf 100644 --- a/modules/ve-mw/i18n/en.json +++ b/modules/ve-mw/i18n/en.json @@ -63,6 +63,7 @@ "visualeditor-ca-ve-edit": "VisualEditor", "visualeditor-ca-ve-edit-section": "VisualEditor", "visualeditor-categories-tool": "Categories", + "visualeditor-ce-redirectnode-description": "Redirect to $1", "visualeditor-desc": "Visual editor for MediaWiki", "visualeditor-descriptionpagelink": "Project:VisualEditor", "visualeditor-dialog-media-alttext-section": "Alternative text", diff --git a/modules/ve-mw/i18n/qqq.json b/modules/ve-mw/i18n/qqq.json index 060ab73..e0bfa09 100644 --- a/modules/ve-mw/i18n/qqq.json +++ b/modules/ve-mw/i18n/qqq.json @@ -74,6 +74,7 @@ "visualeditor-ca-ve-edit": "Link text of the dedicated VisualEditor {{msg-mw|Edit}} tab.\n{{Identical|VisualEditor}}", "visualeditor-ca-ve-edit-section": "{{Identical|VisualEditor}}", "visualeditor-categories-tool": "Tool for opening the categories section of the meta dialog.\n{{Identical|Category}}", + "visualeditor-ce-redirectnode-description": "Title shown as the description of redirect nodes.\n\nParameters:\n* $1 - Target title of redirect", "visualeditor-desc": "{{desc|name=VisualEditor|url=https://www.mediawiki.org/wiki/Extension:VisualEditor}}", "visualeditor-descriptionpagelink": "{{doc-important|Do not translate \"Project\"; it is automatically converted to the wiki's project namespace.}}\nName of a page describing the use of VisualEditor in this project.\n\nUsed in:\n* {{msg-mw|Tag-visualeditor}}\n* {{msg-mw|Tag-visualeditor-description}}\n* {{msg-mw|Tag-visualeditor-needcheck}}\n* {{msg-mw|Tag-visualeditor-needcheck-description}}", "visualeditor-dialog-media-alttext-section": "Label for the image alternative text sub-section.", 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 c57a01d..a9b410b 100644 --- a/modules/ve-mw/init/targets/ve.init.mw.DesktopArticleTarget.js +++ b/modules/ve-mw/init/targets/ve.init.mw.DesktopArticleTarget.js @@ -1304,7 +1304,10 @@ * Show the meta dialog as needed on load. */ ve.init.mw.DesktopArticleTarget.prototype.maybeShowMetaDialog = function () { - var target = this; + var target = this, + doc = this.getSurface().getModel().getDocument(), + offset = doc.data.getNearestContentOffset( 0, 1 ), + node; this.welcomeDialogPromise .always( function () { @@ -1312,11 +1315,14 @@ target.actionsToolbar.tools.notices.getPopup().toggle( true ); } ); - if ( this.getSurface().getModel().metaList.getItemsInGroup( 'mwRedirect' ).length ) { - this.getSurface().getDialogs().openWindow( 'meta', { - page: 'settings', - fragment: this.getSurface().getModel().getFragment() - } ); + if ( offset >= 0 ) { + node = doc.getDocumentNode().getNodeFromOffset( offset + 1 ) + if ( node && node.getType() === "mwRedirect" ) { + this.getSurface().getDialogs().openWindow( 'meta', { + page: 'settings', + fragment: this.getSurface().getModel().getFragment() + } ); + } } }; diff --git a/modules/ve-mw/ui/dialogs/ve.ui.MWMetaDialog.js b/modules/ve-mw/ui/dialogs/ve.ui.MWMetaDialog.js index b61d0bd..3087c38 100644 --- a/modules/ve-mw/ui/dialogs/ve.ui.MWMetaDialog.js +++ b/modules/ve-mw/ui/dialogs/ve.ui.MWMetaDialog.js @@ -120,7 +120,7 @@ surfaceModel.pushStaging(); // Let each page set itself up ('languages' page doesn't need this yet) - this.settingsPage.setup( surfaceModel.metaList, data ); + this.settingsPage.setup( surfaceModel, data ); this.advancedSettingsPage.setup( surfaceModel.metaList, data ); this.categoriesPage.setup( surfaceModel.metaList, data ); }, this ); diff --git a/modules/ve-mw/ui/pages/ve.ui.MWSettingsPage.js b/modules/ve-mw/ui/pages/ve.ui.MWSettingsPage.js index 3ffaa92..ed459bc 100644 --- a/modules/ve-mw/ui/pages/ve.ui.MWSettingsPage.js +++ b/modules/ve-mw/ui/pages/ve.ui.MWSettingsPage.js @@ -24,6 +24,7 @@ // Properties this.metaList = null; + this.doc = null; this.tocOptionTouched = false; this.redirectOptionsTouched = false; this.tableOfContentsTouched = false; @@ -231,17 +232,39 @@ }; /** + * Finds the redirect node in the document, if it exists. + * @return {Object} Object with 'offset' and 'node' keys + */ +ve.ui.MWSettingsPage.prototype.findRedirect = function () { + var offset = this.doc.data.getNearestContentOffset( 0, 1 ), node; + if ( offset >= 0 ) { + node = this.doc.getDocumentNode().getNodeFromOffset( offset + 1 ); + if ( node && node.getType() === "mwRedirect" ) { + return { offset: offset, node: node }; + } else { + return null; + } + } else { + return null; + } +} + +/** * Setup settings page. * - * @param {ve.dm.MetaList} metaList Meta list + * @param {ve.dm.SurfaceModel} surfaceModel Surface model * @param {Object} [data] Dialog setup data */ -ve.ui.MWSettingsPage.prototype.setup = function ( metaList ) { +ve.ui.MWSettingsPage.prototype.setup = function ( surfaceModel ) { var tableOfContentsMetaItem, tableOfContentsField, tableOfContentsMode, - redirectTargetItem, redirectTarget, redirectStatic, - settingsPage = this; + redirectStatic, + redirectNode, + settingsPage = this, + redirectTarget = ''; - this.metaList = metaList; + this.surfaceModel = surfaceModel; + this.metaList = surfaceModel.metaList; + this.doc = surfaceModel.getDocument(); // Table of Contents items tableOfContentsMetaItem = this.getMetaItem( 'mwTOC' ); @@ -252,14 +275,18 @@ this.tableOfContentsTouched = false; // Redirect items (disabled states set by change event) - redirectTargetItem = this.getMetaItem( 'mwRedirect' ); - redirectTarget = redirectTargetItem && redirectTargetItem.getAttribute( 'title' ) || ''; + + redirect = this.findRedirect(); + if ( redirect ) { + redirectTarget = redirect.node.getAttribute( 'title' ); + } + redirectStatic = this.getMetaItem( 'mwStaticRedirect' ); - this.enableRedirectInput.setSelected( !!redirectTargetItem ); + this.enableRedirectInput.setSelected( !!redirectTarget ); this.redirectTargetInput.setValue( redirectTarget ); - this.redirectTargetInput.setDisabled( !redirectTargetItem ); + this.redirectTargetInput.setDisabled( !redirectTarget ); this.enableStaticRedirectInput.setSelected( !!redirectStatic ); - this.enableStaticRedirectInput.setDisabled( !redirectTargetItem ); + this.enableStaticRedirectInput.setDisabled( !redirectTarget ); this.redirectOptionsTouched = false; // Simple checkbox items @@ -276,7 +303,7 @@ */ ve.ui.MWSettingsPage.prototype.teardown = function ( data ) { var tableOfContentsMetaItem, tableOfContentsSelectedItem, tableOfContentsValue, - currentRedirectTargetItem, newRedirectData, newRedirectItemData, + newRedirectTargetTitle, tx, currentStaticRedirectItem, newStaticRedirectState, settingsPage = this; @@ -292,9 +319,7 @@ tableOfContentsValue = tableOfContentsSelectedItem && tableOfContentsSelectedItem.getData(); // Redirect items - currentRedirectTargetItem = this.getMetaItem( 'mwRedirect' ); - newRedirectData = this.redirectTargetInput.getValue(); - newRedirectItemData = { type: 'mwRedirect', attributes: { title: newRedirectData } }; + newRedirectTargetTitle = this.redirectTargetInput.getValue(); currentStaticRedirectItem = this.getMetaItem( 'mwStaticRedirect' ); newStaticRedirectState = this.enableStaticRedirectInput.isSelected(); @@ -321,32 +346,40 @@ // Alter the redirect options iff they've been touched & are different if ( this.redirectOptionsTouched ) { - if ( currentRedirectTargetItem ) { - if ( newRedirectData ) { - if ( currentRedirectTargetItem.getAttribute( 'title' ) !== newRedirectData ) { - // There was a redirect and is a new one, but they differ, so replace - currentRedirectTargetItem.replaceWith( - ve.extendObject( true, {}, - currentRedirectTargetItem.getElement(), - newRedirectItemData - ) ); + redirect = this.findRedirect(); + if ( redirect ) { + if ( newRedirectTargetTitle ) { + if ( redirect.node.getAttribute( 'title' ) !== newRedirectTargetTitle ) { + // There was a redirect and is a new one, but they differ + tx = ve.dm.Transaction.newFromAttributeChanges( + this.doc, + redirect.offset, + { title: newRedirectTargetTitle } + ); + this.surfaceModel.change( tx ); } } else { - // There was a redirect and is no new one, so remove - currentRedirectTargetItem.remove(); + // There was a redirect node but the user wants to remove it + tx = ve.dm.Transaction.newFromRemoval( this.doc, redirect.node.getOuterRange() ); + this.surfaceModel.change( tx ); } - } else { - if ( newRedirectData ) { - // There's no existing redirect but there is a new one, so create - // HACK: Putting this at index 0, offset 0 so that it works – bug 61862 - this.metaList.insertMeta( newRedirectItemData, 0, 0 ); - } + } else if ( newRedirectTargetTitle ) { + // There's no existing redirect but there is a new one, so create + tx = ve.dm.Transaction.newFromInsertion( + this.doc, + 0, + [ + { type: 'mwRedirect', attributes: { title: newRedirectTargetTitle } }, + { type: '/mwRedirect' } + ] + ); + this.surfaceModel.change( tx ); } - if ( currentStaticRedirectItem && ( !newStaticRedirectState || !newRedirectData ) ) { + if ( currentStaticRedirectItem && ( !newStaticRedirectState || !newRedirectTargetTitle ) ) { currentStaticRedirectItem.remove(); } - if ( !currentStaticRedirectItem && newStaticRedirectState && newRedirectData ) { + if ( !currentStaticRedirectItem && newStaticRedirectState && newRedirectTargetTitle ) { this.metaList.insertMeta( { type: 'mwStaticRedirect' } ); } } -- To view, visit https://gerrit.wikimedia.org/r/271701 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Ia07d5e385c542ef4fa96243a6df851a8b744a7ca Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/VisualEditor Gerrit-Branch: master Gerrit-Owner: Alex Monk <kren...@gmail.com> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits