Support reply tool on mobile

Bug: T270536
Change-Id: I94d04e9cd442f9a4e0c5924da67c43a768417a8b
This commit is contained in:
Ed Sanders 2020-12-15 19:45:05 +00:00
parent 54471b5037
commit 900a01772f
9 changed files with 251 additions and 59 deletions

View file

@ -82,7 +82,6 @@
"moment",
"rangefix",
"oojs-ui-windows",
"mediawiki.action.view.postEdit",
"mediawiki.api",
"mediawiki.Title",
"mediawiki.Uri",
@ -119,6 +118,10 @@
"discussiontools-topicsubscription-notify-subscribed-title",
"discussiontools-topicsubscription-notify-unsubscribed-body",
"discussiontools-topicsubscription-notify-unsubscribed-title"
],
"targets": [
"desktop",
"mobile"
]
},
"ext.discussionTools.debug": {
@ -129,6 +132,10 @@
"styles": "highlighter.less",
"dependencies": [
"ext.discussionTools.init"
],
"targets": [
"desktop",
"mobile"
]
},
"ext.discussionTools.ReplyWidget": {
@ -185,7 +192,11 @@
"optionalDependencies": {
"ConfirmEdit": "ext.confirmEdit.CaptchaInputWidget"
},
"factory": "\\MediaWiki\\Extension\\DiscussionTools\\Data::addOptionalDependencies"
"factory": "\\MediaWiki\\Extension\\DiscussionTools\\Data::addOptionalDependencies",
"targets": [
"desktop",
"mobile"
]
},
"ext.discussionTools.ReplyWidgetPlain": {
"packageFiles": [
@ -195,7 +206,12 @@
"ext.discussionTools.ReplyWidget",
"mediawiki.editfont.styles",
"mediawiki.user",
"mediawiki.jqueryMsg"
"mediawiki.jqueryMsg",
"ext.visualEditor.switching"
],
"targets": [
"desktop",
"mobile"
]
},
"ext.discussionTools.ReplyWidgetVisual": {
@ -224,9 +240,11 @@
"ext.visualEditor.mwcore",
"ext.visualEditor.mwsignature",
"ext.visualEditor.mwwikitext",
"ext.visualEditor.core.desktop",
"ext.visualEditor.desktopTarget",
"ext.visualEditor.mwextensions.desktop"
"ext.visualEditor.articleTarget"
],
"targets": [
"desktop",
"mobile"
]
}
},
@ -489,6 +507,10 @@
"value": false,
"description": "A/B test DiscussionTools features for logged in users. false, 'replytool', 'newtopictool', or 'all'"
},
"DiscussionToolsEnableMobile": {
"value": false,
"description": "Enable DiscussionTools on mobile talk pages."
},
"DiscussionTools_replytool": {
"value": "default",
"description": "Override availability of DiscussionTools reply tool. 'default', 'available', or 'unavailable'."

View file

@ -211,12 +211,21 @@ class HookUtils {
return false;
}
// Don't show on mobile
$isMobile = false;
if ( ExtensionRegistry::getInstance()->isLoaded( 'MobileFrontend' ) ) {
$mobFrontContext = MediaWikiServices::getInstance()->getService( 'MobileFrontend.Context' );
if ( $mobFrontContext->shouldDisplayMobileView() ) {
return false;
}
$isMobile = $mobFrontContext->shouldDisplayMobileView();
}
$dtConfig = MediaWikiServices::getInstance()->getConfigFactory()->makeConfig( 'discussiontools' );
if ( $isMobile && (
!$dtConfig->get( 'DiscussionToolsEnableMobile' ) ||
// Still disable some features for now
$feature === self::TOPICSUBSCRIPTION ||
$feature === self::NEWTOPICTOOL
) ) {
return false;
}
// Topic subscription is not available on your own talk page, as you will
@ -230,7 +239,6 @@ class HookUtils {
// Extra hack for parses from API, where this parameter isn't passed to derivative requests
RequestContext::getMain()->getRequest()->getRawVal( 'dtenable' );
$dtConfig = MediaWikiServices::getInstance()->getConfigFactory()->makeConfig( 'discussiontools' );
if (
$feature === self::TOPICSUBSCRIPTION &&
!$dtConfig->get( 'DiscussionToolsEnableTopicSubscriptionBackend' )

View file

@ -12,6 +12,19 @@ var
.concat( conf.pluginModules.filter( mw.loader.getState ) ),
plainModules = [ 'ext.discussionTools.ReplyWidgetPlain' ];
if ( OO.ui.isMobile() ) {
visualModules = [
'ext.visualEditor.core.mobile',
'ext.visualEditor.mwextensions'
].concat( visualModules );
} else {
visualModules = [
'ext.visualEditor.core.desktop',
'ext.visualEditor.desktopTarget',
'ext.visualEditor.mwextensions.desktop'
].concat( visualModules );
}
// Start loading reply widget code
if ( defaultVisual || enable2017Wikitext ) {
mw.loader.using( visualModules );

View file

@ -389,6 +389,9 @@ function init( $container, state ) {
threadItemsById = {},
threadItems = [];
// Lazy-load postEdit module, may be required later (on desktop)
mw.loader.using( 'mediawiki.action.view.postEdit' );
$pageContainer = $container;
linksController = new ReplyLinksController( $pageContainer );
var parser = new Parser( $pageContainer[ 0 ] );
@ -549,9 +552,14 @@ function init( $container, state ) {
var repliedToComment = threadItemsById[ state.repliedTo ];
$highlight = highlight( repliedToComment.replies[ repliedToComment.replies.length - 1 ] );
mw.hook( 'postEdit' ).fire( {
message: mw.msg( 'discussiontools-postedit-confirmation-published', mw.user )
} );
if ( OO.ui.isMobile() ) {
mw.notify( mw.msg( 'discussiontools-postedit-confirmation-published', mw.user ) );
} else {
// postEdit is currently desktop only
mw.hook( 'postEdit' ).fire( {
message: mw.msg( 'discussiontools-postedit-confirmation-published', mw.user )
} );
}
}
if ( $highlight ) {

View file

@ -30,21 +30,57 @@ CommentTarget.static.name = 'discussionTools';
CommentTarget.static.modes = [ 'visual', 'source' ];
CommentTarget.static.toolbarGroups = [
{
name: 'style',
title: OO.ui.deferMsg( 'visualeditor-toolbar-style-tooltip' ),
include: [ 'bold', 'italic', 'moreTextStyle' ]
},
{
name: 'link',
include: [ 'link' ]
},
{
name: 'other',
include: [ 'usernameCompletion' ]
}
];
if ( OO.ui.isMobile() ) {
// Mobile currently expects one tool per group
CommentTarget.static.toolbarGroups = [
{
name: 'style',
classes: [ 've-test-toolbar-style' ],
type: 'list',
icon: 'textStyle',
title: OO.ui.deferMsg( 'visualeditor-toolbar-style-tooltip' ),
label: OO.ui.deferMsg( 'visualeditor-toolbar-style-tooltip' ),
invisibleLabel: true,
include: [ { group: 'textStyle' }, 'language', 'clear' ],
forceExpand: [ 'bold', 'italic', 'clear' ],
promote: [ 'bold', 'italic' ],
demote: [ 'strikethrough', 'code', 'underline', 'language', 'clear' ]
},
{
name: 'link',
include: [ 'link' ]
},
{
name: 'other',
include: [ 'usernameCompletion' ]
},
{
name: 'editMode',
type: 'list',
icon: 'edit',
title: OO.ui.deferMsg( 'visualeditor-mweditmode-tooltip' ),
label: OO.ui.deferMsg( 'visualeditor-mweditmode-tooltip' ),
invisibleLabel: true,
include: [ 'editModeVisual', 'editModeSource' ]
}
];
} else {
CommentTarget.static.toolbarGroups = [
{
name: 'style',
title: OO.ui.deferMsg( 'visualeditor-toolbar-style-tooltip' ),
include: [ 'bold', 'italic', 'moreTextStyle' ]
},
{
name: 'link',
include: [ 'link' ]
},
{
name: 'other',
include: [ 'usernameCompletion' ]
}
];
}
CommentTarget.static.importRules = ve.copy( CommentTarget.static.importRules );
@ -73,7 +109,7 @@ CommentTarget.static.importRules.external.blacklist = ve.extendObject(
CommentTarget.prototype.attachToolbar = function () {
this.replyWidget.$headerWrapper.append(
this.getToolbar().$element.append( this.replyWidget.modeTabSelect.$element )
this.getToolbar().$element.append( this.replyWidget.modeTabSelect ? this.replyWidget.modeTabSelect.$element : null )
);
this.getToolbar().$element.prepend( this.getSurface().getToolbarDialogs().$element );
};
@ -89,6 +125,14 @@ CommentTarget.prototype.getSurfaceConfig = function ( config ) {
}, config ) );
};
CommentTarget.prototype.editSource = function () {
this.replyWidget.switch( 'source' );
};
CommentTarget.prototype.switchToVisualEditor = function () {
this.replyWidget.switch( 'visual' );
};
/* Registration */
ve.init.mw.targetFactory.register( CommentTarget );

View file

@ -33,6 +33,12 @@ span[ data-mw-comment-start ] {
}
}
// No support for reply links in the mobile talk overlay
// stylelint-disable-next-line no-descending-specificity, selector-class-pattern
.talk-overlay & {
display: none;
}
.ext-discussiontools-init-replylink {
&-reply {
cursor: pointer;

View file

@ -84,30 +84,35 @@ function ReplyWidget( commentController, comment, commentDetails, config ) {
)
} );
this.modeTabSelect = new ModeTabSelectWidget( {
classes: [ 'ext-discussiontools-ui-replyWidget-modeTabs' ],
items: [
new ModeTabOptionWidget( {
label: mw.msg( 'discussiontools-replywidget-mode-visual' ),
data: 'visual'
} ),
new ModeTabOptionWidget( {
label: mw.msg( 'discussiontools-replywidget-mode-source' ),
data: 'source'
} )
],
framed: false
} );
this.modeTabSelect.$element.attr( 'aria-label', mw.msg( 'visualeditor-mweditmode-tooltip' ) );
// Make the option for the current mode disabled, to make it un-interactable
// (we override the styles to make it look as if it was selected)
this.modeTabSelect.findItemFromData( this.getMode() ).setDisabled( true );
this.$headerWrapper = $( '<div>' ).addClass( 'ext-discussiontools-ui-replyWidget-headerWrapper' );
this.$headerWrapper.append(
// Visual mode toolbar attached here by CommentTarget#attachToolbar
this.modeTabSelect.$element
);
if ( !OO.ui.isMobile() ) {
this.modeTabSelect = new ModeTabSelectWidget( {
classes: [ 'ext-discussiontools-ui-replyWidget-modeTabs' ],
items: [
new ModeTabOptionWidget( {
label: mw.msg( 'discussiontools-replywidget-mode-visual' ),
data: 'visual'
} ),
new ModeTabOptionWidget( {
label: mw.msg( 'discussiontools-replywidget-mode-source' ),
data: 'source'
} )
],
framed: false
} );
this.modeTabSelect.$element.attr( 'aria-label', mw.msg( 'visualeditor-mweditmode-tooltip' ) );
// Make the option for the current mode disabled, to make it un-interactable
// (we override the styles to make it look as if it was selected)
this.modeTabSelect.findItemFromData( this.getMode() ).setDisabled( true );
this.modeTabSelect.connect( this, {
choose: 'onModeTabSelectChoose'
} );
this.$headerWrapper.append(
// Visual mode toolbar attached here by CommentTarget#attachToolbar
this.modeTabSelect.$element
);
}
this.$preview = $( '<div>' )
.addClass( 'ext-discussiontools-ui-replyWidget-preview' )
@ -195,9 +200,6 @@ function ReplyWidget( commentController, comment, commentDetails, config ) {
this.beforeUnloadHandler = this.onBeforeUnload.bind( this );
this.unloadHandler = this.onUnload.bind( this );
this.onWatchToggleHandler = this.onWatchToggle.bind( this );
this.modeTabSelect.connect( this, {
choose: 'onModeTabSelectChoose'
} );
this.advancedToggle.connect( this, { click: 'onAdvancedToggleClick' } );
this.editSummaryInput.connect( this, { change: 'onEditSummaryChange' } );
this.editSummaryInput.$input.on( 'keydown', this.onKeyDown.bind( this, false ) );
@ -503,6 +505,11 @@ ReplyWidget.prototype.setup = function ( data ) {
if ( this.isNewTopic ) {
this.commentController.sectionTitle.connect( this, { change: 'onInputChangeThrottled' } );
} else {
// De-indent replies on mobile
if ( OO.ui.isMobile() ) {
this.$element.css( 'margin-left', -this.$element.position().left );
}
}
// eslint-disable-next-line no-jquery/no-global-selector
@ -581,7 +588,9 @@ ReplyWidget.prototype.teardown = function ( abandoned ) {
}
// Make sure that the selector is blurred before it gets removed from the document, otherwise
// event handlers for arrow keys are not removed, and it keeps trying to switch modes (T274423)
this.modeTabSelect.blur();
if ( this.modeTabSelect ) {
this.modeTabSelect.blur();
}
this.unbindBeforeUnloadHandler();
// eslint-disable-next-line no-jquery/no-global-selector
$( '#ca-watch, #ca-unwatch' ).off( 'watchpage.mw', this.onWatchToggleHandler );

View file

@ -1,3 +1,5 @@
@width-breakpoint-tablet: 720px;
.ext-discussiontools-ui-replyWidget {
margin-bottom: 1em;
position: relative;
@ -37,6 +39,8 @@
background: none;
box-shadow: none;
border: 0;
// Stretch to all available space
flex-grow: 1;
> .oo-ui-toolbar-actions {
display: none;
@ -63,12 +67,19 @@
}
}
.skin-minerva & .ve-ui-surface-visual .ve-ce-paragraphNode {
// Reduce paragraph spacing in editor, as replies will actually generate <dd> not <p>
margin: 0;
&:first-child {
margin-top: 0.5em;
}
}
&-modeTabs {
box-shadow: none;
height: 3em;
text-align: right;
// Stretch to all available space
flex-grow: 1;
// Hide outline that can appear after switching modes via keyboard
outline: 0;
@ -91,6 +102,19 @@
}
}
&-editSwitch {
text-align: right;
.oo-ui-toolbar-bar { /* stylelint-disable-line no-descending-specificity */
border: 0;
box-shadow: none;
}
.oo-ui-toolbar-popups {
text-align: left;
}
}
&-actionsWrapper {
margin-top: 0.5em;
display: flex;
@ -164,6 +188,10 @@
.ext-discussiontools-ui-replyWidget:not( .ext-discussiontools-ui-replyWidget-newTopic ) & > .mw-parser-output {
margin-left: -1.6em;
.skin-minerva & {
margin-left: -1em;
}
}
> .mw-parser-output > h2:first-child {
@ -244,6 +272,23 @@
// Force to a separate line
width: 100%;
}
// Change field layout and order on mobile
@media all and ( max-width: @width-breakpoint-tablet ) {
.oo-ui-fieldLayout-header {
order: 1;
}
.oo-ui-fieldLayout-field {
order: 2;
}
.ext-discussiontools-ui-replyWidget-checkboxes {
order: 3;
flex-grow: 1;
margin-top: 1em;
}
}
}
&-editSummary {
@ -267,3 +312,9 @@
}
}
}
.skin-minerva .ext-discussiontools-init-replylink-open dl dd {
// Disable overflow hack for scrolling images in <dd> in Minerva
// while the tool is open, as we need negative margins on the widget.
overflow: visible;
}

View file

@ -12,9 +12,40 @@ var utils = require( 'ext.discussionTools.init' ).utils;
* @param {Object} [config]
*/
function ReplyWidgetPlain() {
var widget = this;
// Parent constructor
ReplyWidgetPlain.super.apply( this, arguments );
if ( OO.ui.isMobile() ) {
var toolFactory = new OO.ui.ToolFactory(),
toolGroupFactory = new OO.ui.ToolGroupFactory();
toolFactory.register( mw.libs.ve.MWEditModeVisualTool );
toolFactory.register( mw.libs.ve.MWEditModeSourceTool );
this.switchToolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory, {
classes: [ 'ext-discussiontools-ui-replyWidget-editSwitch' ]
} );
this.switchToolbar.on( 'switchEditor', function ( mode ) {
widget.switch( mode );
} );
this.switchToolbar.setup( [ {
name: 'editMode',
type: 'list',
icon: 'edit',
title: mw.msg( 'visualeditor-mweditmode-tooltip' ),
label: mw.msg( 'visualeditor-mweditmode-tooltip' ),
invisibleLabel: true,
include: [ 'editModeVisual', 'editModeSource' ]
} ] );
this.switchToolbar.emit( 'updateState' );
this.$headerWrapper.append( this.switchToolbar.$element );
}
this.$element.addClass( 'ext-discussiontools-ui-replyWidget-plain' );
}