diff --git a/extension.json b/extension.json index 95f265b38f..624ea0a932 100644 --- a/extension.json +++ b/extension.json @@ -1442,9 +1442,12 @@ ], "dependencies": [ "ext.visualEditor.mwcore", - "ext.visualEditor.mwlink" + "ext.visualEditor.mwlink", + "mediawiki.action.view.redirectPage" ], "messages": [ + "redirectto", + "visualeditor-advancedsettings-tool", "visualeditor-categories-tool", "visualeditor-dialog-meta-advancedsettings-label", @@ -1508,6 +1511,7 @@ "visualeditor-dialogbutton-meta-tooltip", "visualeditor-languages-tool", "visualeditor-meta-tool", + "visualeditor-redirect-description", "visualeditor-settings-tool" ], "targets": [ diff --git a/modules/ve-mw/i18n/en.json b/modules/ve-mw/i18n/en.json index 558ded3ef6..5fb080607d 100644 --- a/modules/ve-mw/i18n/en.json +++ b/modules/ve-mw/i18n/en.json @@ -285,6 +285,7 @@ "visualeditor-preference-tabs-prefer-wt": "Always give me the wikitext editor", "visualeditor-preference-tabs-remember-last": "Remember my last editor", "visualeditor-recreate": "The page has been deleted since you started editing. Press \"$1\" to recreate it.", + "visualeditor-redirect-description": "Redirect to $1", "visualeditor-savedialog-error-badtoken": "We could not save your edit because the session was no longer valid.", "visualeditor-savedialog-identify-anon": "Do you want to save this page as an anonymous user instead? Your IP address will be recorded in this page's edit history.", "visualeditor-savedialog-identify-trylogin": "You are no longer logged in. Please log back in from a different tab and try again.", diff --git a/modules/ve-mw/i18n/qqq.json b/modules/ve-mw/i18n/qqq.json index 5b897dfec8..c24b47df82 100644 --- a/modules/ve-mw/i18n/qqq.json +++ b/modules/ve-mw/i18n/qqq.json @@ -296,6 +296,7 @@ "visualeditor-preference-tabs-prefer-wt": "Used in [[Special:Preferences]].\n\nUsed as label for a radio button to have the wikitext editor be the preferred editor. Shown together with the following buttons, so consider formulating them consistently:\n* {{msg-mw|Visualeditor-preference-tabs-multi-tab}}\n* {{msg-mw|Visualeditor-preference-tabs-prefer-ve}}\n* {{msg-mw|Visualeditor-preference-tabs-remember-last}}", "visualeditor-preference-tabs-remember-last": "Used in [[Special:Preferences]].\n\nUsed as label for a radio button to have VisualEditor remember whether it was the last editor or not. Shown together with the following buttons, so consider formulating them consistently:\n* {{msg-mw|Visualeditor-preference-tabs-multi-tab}}\n* {{msg-mw|Visualeditor-preference-tabs-prefer-ve}}\n* {{msg-mw|Visualeditor-preference-tabs-prefer-wt}}", "visualeditor-recreate": "Text shown when the editor fails to save the page due to it having been deleted since they opened VE. $1 is the message {{msg-mw|ooui-dialog-process-continue}}.", + "visualeditor-redirect-description": "Title shown as the description of redirect nodes.\n\nParameters:\n* $1 - Target title of redirect", "visualeditor-savedialog-error-badtoken": "Error displayed in the save dialog if saving the edit failed due to an invalid edit token (likely due to the user having logged out in a separate window, or logged in again)", "visualeditor-savedialog-identify-anon": "Displayed in the save dialog if saving failed because the session expired and the session is now an anonymous user. Warning about IP address being recorded is based on {{msg-mw|anoneditwarning}}.\n\n{{format|jquerymsg}}", "visualeditor-savedialog-identify-trylogin": "Displayed in the save dialog if saving failed because the session expired and we also encountered an error while trying to figure out for which user the current session. This is usually due to the user getting logged out on a private wiki.", diff --git a/modules/ve-mw/init/styles/ve.init.mw.DesktopArticleTarget.css b/modules/ve-mw/init/styles/ve.init.mw.DesktopArticleTarget.css index 1154e4d998..fca5a24ecf 100644 --- a/modules/ve-mw/init/styles/ve.init.mw.DesktopArticleTarget.css +++ b/modules/ve-mw/init/styles/ve.init.mw.DesktopArticleTarget.css @@ -21,3 +21,20 @@ .ve-active .ve-ui-toolbar-floating .oo-ui-toolbar-bar { transform: translateY( 0 ); } + +/*! + * ve-redirect-header is added just outside of the surface, which has its own + * hiding style during activation. + */ +.ve-activating .ve-redirect-header { + height: 0; + overflow: hidden; +} + +.ve-init-mw-desktopArticleTarget .ve-redirect-header { + cursor: pointer; +} + +.ve-init-mw-desktopArticleTarget .redirectMsg a:hover { + text-decoration: none; +} \ No newline at end of file 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 fa05593db0..cbc10f6cd0 100644 --- a/modules/ve-mw/init/targets/ve.init.mw.DesktopArticleTarget.js +++ b/modules/ve-mw/init/targets/ve.init.mw.DesktopArticleTarget.js @@ -643,6 +643,11 @@ ve.init.mw.DesktopArticleTarget.prototype.surfaceReady = function () { } ); } ); + this.getSurface().getModel().getMetaList().connect( this, { + insert: 'onMetaItemInserted', + remove: 'onMetaItemRemoved' + } ); + // Update UI this.changeDocumentTitle(); this.restoreScrollPosition(); @@ -662,6 +667,65 @@ ve.init.mw.DesktopArticleTarget.prototype.surfaceReady = function () { mw.hook( 've.activationComplete' ).fire(); }; +/** + * Add the redirect header when a redirect is inserted into the page. + * + * @param {ve.dm.MetaItem} metaItem Item that was inserted + */ +ve.init.mw.DesktopArticleTarget.prototype.onMetaItemInserted = function ( metaItem ) { + var title, target, $link; + if ( metaItem.getType() === 'mwRedirect' ) { + target = this; + title = metaItem.getAttribute( 'title' ); + $link = $( '' ) + .attr( 'title', mw.msg( 'visualeditor-redirect-description', title ) ) + .text( title ); + ve.init.platform.linkCache.styleElement( title, $link ); + + // Add redirect target header + if ( !$( '#redirectsub' ).length ) { + $( '#contentSub' ).append( + $( '' ) + .text( mw.msg( 'redirectpagesub' ) ) + .attr( 'id', 'redirectsub' ), + $( '
' ) + ); + } + this.$element.children( '.redirectMsg' ).remove(); + this.$element.find( '.ve-init-mw-target-surface' ).before( $( '
' ) + // Bit of a hack: Make sure any redirect note is styled + .addClass( 'redirectMsg mw-content-' + $( 'html' ).attr( 'dir' ) ) + + .addClass( 've-redirect-header' ) + .append( + $( '

' ).text( mw.msg( 'redirectto' ) ), + $( '

    ' ) + .addClass( 'redirectText' ) + .append( $( '
  • ' ).append( $link ) ) + ) + .click( function ( e ) { + var windowAction = ve.ui.actionFactory.create( 'window', target.getSurface() ); + windowAction.open( 'meta', { page: 'settings' } ); + e.preventDefault(); + } ) + ); + } +}; + +/** + * Remove the redirect header when a redirect is removed from the page. + * + * @param {ve.dm.MetaItem} metaItem Item that was removed + * @param {number} offset Linear model offset that the item was at + * @param {number} index Index within that offset the item was at + */ +ve.init.mw.DesktopArticleTarget.prototype.onMetaItemRemoved = function ( metaItem ) { + if ( metaItem.getType() === 'mwRedirect' ) { + this.$element.children( '.redirectMsg' ).remove(); + $( '#contentSub #redirectsub, #contentSub #redirectsub + br' ).remove(); + } +}; + /** * Handle Escape key presses. * @@ -1074,6 +1138,9 @@ ve.init.mw.DesktopArticleTarget.prototype.restorePage = function () { } $( '#ca-view' ).addClass( 'selected' ); + // Remove any VE-added redirectMsg + $( '.ve-redirect-header' ).remove(); + mw.hook( 've.deactivate' ).fire(); this.emit( 'deactivate' ); @@ -1178,12 +1245,20 @@ ve.init.mw.DesktopArticleTarget.prototype.replacePageContent = function ( $editableContent = $( '#mw-content-text' ); } + // Remove any VE-added redirectMsg + $( '.redirectMsg' ).remove(); + mw.hook( 'wikipage.content' ).fire( $editableContent.empty().append( $content ) ); if ( displayTitle ) { $( '#content #firstHeading' ).html( displayTitle ); } $( '#catlinks' ).replaceWith( categoriesHtml ); $( '#contentSub' ).html( contentSub ); + + // Bit of a hack: Make sure any redirect note is styled + $( '.redirectMsg' ) + .addClass( 'mw-content-' + $( 'html' ).attr( 'dir' ) ) + .addClass( 've-redirect-header' ); }; /** @@ -1242,7 +1317,7 @@ ve.init.mw.DesktopArticleTarget.prototype.teardownUnloadHandlers = function () { * Show the meta dialog as needed on load. */ ve.init.mw.DesktopArticleTarget.prototype.maybeShowMetaDialog = function () { - var windowAction, + var windowAction, redirectMetaItems, target = this; if ( this.welcomeDialogPromise ) { @@ -1253,7 +1328,10 @@ ve.init.mw.DesktopArticleTarget.prototype.maybeShowMetaDialog = function () { } ); } - if ( this.getSurface().getModel().metaList.getItemsInGroup( 'mwRedirect' ).length ) { + redirectMetaItems = this.getSurface().getModel().getMetaList().getItemsInGroup( 'mwRedirect' ); + if ( redirectMetaItems.length ) { + this.onMetaItemInserted( redirectMetaItems[ 0 ] ); + windowAction = ve.ui.actionFactory.create( 'window', this.getSurface() ); windowAction.open( 'meta', { page: 'settings' } );