From 5feb69612fc61794dc2fd62feb0451d15fa8a1c2 Mon Sep 17 00:00:00 2001 From: Ed Sanders Date: Thu, 7 May 2020 20:16:14 +0100 Subject: [PATCH] Create a user preference to store visual/source mode Bug: T250523 Depends-On: I5b3b47feeeca2085fcd283d55d7e9ceafa73be70 Change-Id: I18f5d38f9d694b12e5f983be7b3b165f135a890d --- extension.json | 4 +++ includes/Hooks.php | 32 ++++++++++++++++++++ modules/CommentController.js | 8 ++--- modules/dt.ui.ReplyWidget.js | 58 ++++++++++++++++++++++-------------- 4 files changed, 76 insertions(+), 26 deletions(-) diff --git a/extension.json b/extension.json index c3146a137..7ff01304c 100644 --- a/extension.json +++ b/extension.json @@ -237,10 +237,14 @@ "BeforePageDisplay": "\\MediaWiki\\Extension\\DiscussionTools\\Hooks::onBeforePageDisplay", "ResourceLoaderGetConfigVars": "\\MediaWiki\\Extension\\DiscussionTools\\Hooks::onResourceLoaderGetConfigVars", "GetBetaFeaturePreferences": "\\MediaWiki\\Extension\\DiscussionTools\\Hooks::onGetBetaPreferences", + "GetPreferences": "\\MediaWiki\\Extension\\DiscussionTools\\Hooks::onGetPreferences", "ListDefinedTags": "\\MediaWiki\\Extension\\DiscussionTools\\Hooks::onListDefinedTags", "ChangeTagsListActive": "\\MediaWiki\\Extension\\DiscussionTools\\Hooks::onListDefinedTags", "RecentChange_save": "\\MediaWiki\\Extension\\DiscussionTools\\Hooks::onRecentChangeSave" }, + "DefaultUserOptions": { + "discussiontools-editmode": "" + }, "config": { "DiscussionToolsEnable": { "value": true, diff --git a/includes/Hooks.php b/includes/Hooks.php index f22bc3492..391b8b4a7 100644 --- a/includes/Hooks.php +++ b/includes/Hooks.php @@ -17,6 +17,7 @@ use RecentChange; use RequestContext; use Skin; use User; +use VisualEditorHooks; class Hooks { @@ -86,6 +87,21 @@ class Hooks { $output->addModules( [ 'ext.discussionTools.init' ] ); + + $editor = $user->getOption( 'discussiontools-editmode' ); + // User has no preferred editor yet + // If the user has a preferred editor, this will be evaluated in the client + if ( !$editor ) { + // Check which editor we would use for articles + // VE pref is 'visualeditor'/'wikitext'. Here we describe the mode, + // not the editor, so 'visual'/'source' + $editor = VisualEditorHooks::getPreferredEditor( $user, $req ) === 'visualeditor' ? + 'visual' : 'source'; + $output->addJsConfigVars( + 'wgDiscussionToolsFallbackEditMode', + $editor + ); + } } if ( $actionName === 'edit' && $req->getVal( 'dtlinterror' ) ) { @@ -115,6 +131,22 @@ class Hooks { $dtConfig->get( 'DTSchemaEditAttemptStepOversample' ); } + /** + * Handler for the GetPreferences hook, to add and hide user preferences as configured + * + * @param User $user The user object + * @param array &$preferences Their preferences object + */ + public static function onGetPreferences( User $user, array &$preferences ) { + $api = [ 'type' => 'api' ]; + $preferences['discussiontools-editmode'] = [ + 'type' => 'api', + 'validation-callback' => function ( $value ) { + return in_array( $value, [ '', 'source', 'visual' ], true ); + }, + ]; + } + /** * Handler for the GetBetaPreferences hook, to add and hide user beta preferences as configured * diff --git a/modules/CommentController.js b/modules/CommentController.js index 56dc2f937..58402ba4f 100644 --- a/modules/CommentController.js +++ b/modules/CommentController.js @@ -6,12 +6,12 @@ var storage = mw.storage.session, scrollPadding = { top: 10, bottom: 10 }, config = require( './config.json' ), - // TODO: Remember last editor used - defaultVisual = false, - enableVisual = config.enableVisual || ( new mw.Uri() ).query.dtvisual; + enableVisual = config.enableVisual || ( new mw.Uri() ).query.dtvisual, + defaultEditMode = mw.user.options.get( 'discussiontools-editmode' ) || mw.config.get( 'wgDiscussionToolsFallbackEditMode' ), + defaultVisual = enableVisual && defaultEditMode === 'visual'; // Start loading reply widget code -if ( defaultVisual && enableVisual ) { +if ( defaultVisual ) { mw.loader.using( 'ext.discussionTools.ReplyWidgetVisual' ); } else { mw.loader.using( 'ext.discussionTools.ReplyWidgetPlain' ); diff --git a/modules/dt.ui.ReplyWidget.js b/modules/dt.ui.ReplyWidget.js index e9c54b9a4..c2a1eb46a 100644 --- a/modules/dt.ui.ReplyWidget.js +++ b/modules/dt.ui.ReplyWidget.js @@ -48,25 +48,6 @@ function ReplyWidget( commentController, parsoidData, config ) { framed: false } ); - this.modeTabSelect = new OO.ui.TabSelectWidget( { - classes: [ 'dt-ui-replyWidget-modeTabs' ], - items: [ - new OO.ui.TabOptionWidget( { - label: mw.msg( 'discussiontools-replywidget-mode-visual' ), - data: 'visual' - } ), - new OO.ui.TabOptionWidget( { - label: mw.msg( 'discussiontools-replywidget-mode-source' ), - data: 'source' - } ) - ], - framed: false - } ); - - this.modeTabSelect.connect( this, { - choose: 'onModeTabSelectChoose' - } ); - this.$preview = $( '
' ).addClass( 'dt-ui-replyWidget-preview' ).attr( 'data-label', mw.msg( 'discussiontools-replywidget-preview' ) ); this.$actionsWrapper = $( '
' ).addClass( 'dt-ui-replyWidget-actionsWrapper' ); this.$actions = $( '
' ).addClass( 'dt-ui-replyWidget-actions' ).append( @@ -113,6 +94,25 @@ function ReplyWidget( commentController, parsoidData, config ) { ); if ( config.switchable ) { + this.modeTabSelect = new OO.ui.TabSelectWidget( { + classes: [ 'dt-ui-replyWidget-modeTabs' ], + items: [ + new OO.ui.TabOptionWidget( { + label: mw.msg( 'discussiontools-replywidget-mode-visual' ), + data: 'visual' + } ), + new OO.ui.TabOptionWidget( { + label: mw.msg( 'discussiontools-replywidget-mode-source' ), + data: 'source' + } ) + ], + framed: false + } ); + + this.modeTabSelect.connect( this, { + choose: 'onModeTabSelectChoose' + } ); + this.$element.prepend( this.modeTabSelect.$element ); } @@ -202,12 +202,20 @@ ReplyWidget.prototype.setPending = function ( pending ) { } }; +ReplyWidget.prototype.saveEditMode = function ( mode ) { + this.api.saveOption( 'discussiontools-editmode', mode ).then( function () { + mw.user.options.set( 'discussiontools-editmode', mode ); + } ); +}; + ReplyWidget.prototype.onModeTabSelectChoose = function ( option ) { var promise, + mode = option.getData(), widget = this; + this.setPending( true ); this.modeTabSelect.setDisabled( true ); - switch ( option.getData() ) { + switch ( mode ) { case 'source': promise = this.commentController.switchToWikitext(); break; @@ -215,9 +223,12 @@ ReplyWidget.prototype.onModeTabSelectChoose = function ( option ) { promise = this.commentController.switchToVisual(); break; } + // TODO: We rely on #setup to call #saveEditMode, so when we have 2017WTE + // we will need to save the new preference here as switching will not + // reload the editor. promise.then( null, function () { // Switch failed, restore previous tab selection - widget.modeTabSelect.selectItemByData( option.getData() === 'source' ? 'visual' : 'source' ); + widget.modeTabSelect.selectItemByData( mode === 'source' ? 'visual' : 'source' ); } ).always( function () { widget.setPending( false ); widget.modeTabSelect.setDisabled( false ); @@ -232,7 +243,10 @@ ReplyWidget.prototype.onModeTabSelectChoose = function ( option ) { */ ReplyWidget.prototype.setup = function () { this.bindBeforeUnloadHandler(); - this.modeTabSelect.selectItemByData( this.getMode() ); + if ( this.modeTabSelect ) { + this.modeTabSelect.selectItemByData( this.getMode() ); + this.saveEditMode( this.getMode() ); + } // Init preview and button state this.onInputChange(); return this;