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

Reply via email to