2020-08-29 12:00:51 +00:00
|
|
|
var
|
2021-06-24 16:21:31 +00:00
|
|
|
utils = require( './utils.js' ),
|
2021-10-18 08:32:17 +00:00
|
|
|
logger = require( './logger.js' ),
|
2021-11-15 06:17:21 +00:00
|
|
|
controller = require( './controller.js' ),
|
2020-08-29 12:00:51 +00:00
|
|
|
CommentController = require( './CommentController.js' ),
|
|
|
|
HeadingItem = require( './HeadingItem.js' );
|
|
|
|
|
2021-12-20 17:06:24 +00:00
|
|
|
/**
|
|
|
|
* Handles setup, save and teardown of new topic widget
|
|
|
|
*
|
|
|
|
* @param {jQuery} $pageContainer Page container
|
2022-02-19 06:31:34 +00:00
|
|
|
* @param {ThreadItemSet} threadItemSet
|
2021-12-20 17:06:24 +00:00
|
|
|
*/
|
2022-02-19 06:31:34 +00:00
|
|
|
function NewTopicController( $pageContainer, threadItemSet ) {
|
2021-01-13 20:54:59 +00:00
|
|
|
this.container = new OO.ui.PanelLayout( {
|
2021-03-13 14:39:39 +00:00
|
|
|
classes: [ 'ext-discussiontools-ui-newTopic' ],
|
2021-01-13 20:54:59 +00:00
|
|
|
expanded: false,
|
|
|
|
padded: true,
|
|
|
|
framed: true
|
|
|
|
} );
|
2020-12-03 22:32:35 +00:00
|
|
|
this.$notices = $( '<div>' ).addClass( 'ext-discussiontools-ui-newTopic-notices' );
|
2020-08-29 12:00:51 +00:00
|
|
|
|
|
|
|
this.sectionTitle = new OO.ui.TextInputWidget( {
|
|
|
|
// Wrap in a <h2> element to inherit heading font styles
|
|
|
|
$element: $( '<h2>' ),
|
2021-03-13 14:39:39 +00:00
|
|
|
classes: [ 'ext-discussiontools-ui-newTopic-sectionTitle' ],
|
2020-08-29 12:00:51 +00:00
|
|
|
placeholder: mw.msg( 'discussiontools-newtopic-placeholder-title' ),
|
|
|
|
spellcheck: true
|
|
|
|
} );
|
2021-03-17 17:54:55 +00:00
|
|
|
this.sectionTitle.$input.attr( 'aria-label', mw.msg( 'discussiontools-newtopic-placeholder-title' ) );
|
2020-08-29 12:00:51 +00:00
|
|
|
this.sectionTitleField = new OO.ui.FieldLayout( this.sectionTitle, {
|
|
|
|
align: 'top'
|
|
|
|
} );
|
|
|
|
this.prevTitleText = '';
|
|
|
|
|
2020-12-03 22:32:35 +00:00
|
|
|
this.container.$element.append( this.$notices, this.sectionTitleField.$element );
|
2020-08-29 12:00:51 +00:00
|
|
|
|
|
|
|
// HeadingItem representing the heading being added, so that we can pretend we're replying to it
|
2021-12-20 17:06:24 +00:00
|
|
|
var threadItem = new HeadingItem( {
|
2020-08-29 12:00:51 +00:00
|
|
|
startContainer: this.sectionTitleField.$element[ 0 ],
|
|
|
|
startOffset: 0,
|
|
|
|
endContainer: this.sectionTitleField.$element[ 0 ],
|
|
|
|
endOffset: this.sectionTitleField.$element[ 0 ].childNodes.length
|
|
|
|
} );
|
2021-12-20 17:06:24 +00:00
|
|
|
threadItem.id = utils.NEW_TOPIC_COMMENT_ID;
|
|
|
|
threadItem.isNewTopic = true;
|
2020-08-29 12:00:51 +00:00
|
|
|
|
2022-02-19 06:31:34 +00:00
|
|
|
NewTopicController.super.call( this, $pageContainer, threadItem, threadItemSet );
|
2020-08-29 12:00:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
OO.inheritClass( NewTopicController, CommentController );
|
|
|
|
|
2021-01-12 06:34:24 +00:00
|
|
|
/* Static properties */
|
|
|
|
|
|
|
|
NewTopicController.static.initType = 'section';
|
|
|
|
|
2021-07-29 06:12:10 +00:00
|
|
|
NewTopicController.static.suppressedEditNotices = [
|
2021-12-16 11:51:04 +00:00
|
|
|
// Our own notice, meant for the other interfaces only
|
|
|
|
'discussiontools-newtopic-legacy-hint-return',
|
2021-07-29 06:12:10 +00:00
|
|
|
// Ignored because we have a custom warning for non-logged-in users.
|
|
|
|
'anoneditwarning',
|
|
|
|
// Ignored because it contains mostly instructions for signing comments using tildes.
|
|
|
|
// (Does not appear in VE notices right now, but just in case.)
|
|
|
|
'talkpagetext',
|
|
|
|
// Ignored because the empty state takeover has already explained
|
|
|
|
// that this is a new article.
|
|
|
|
'newarticletext',
|
|
|
|
'newarticletextanon'
|
|
|
|
];
|
|
|
|
|
2020-08-29 12:00:51 +00:00
|
|
|
/* Methods */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
NewTopicController.prototype.setup = function ( mode ) {
|
|
|
|
var rootScrollable = OO.ui.Element.static.getRootScrollableElement( document.body );
|
|
|
|
|
2021-01-13 20:54:59 +00:00
|
|
|
this.$pageContainer.append( this.container.$element );
|
2020-08-29 12:00:51 +00:00
|
|
|
NewTopicController.super.prototype.setup.call( this, mode );
|
|
|
|
|
|
|
|
// The section title field is added to the page immediately, we can scroll to the bottom and focus
|
|
|
|
// it while the content field is still loading.
|
|
|
|
rootScrollable.scrollTop = rootScrollable.scrollHeight;
|
|
|
|
this.focus();
|
2021-11-15 06:17:21 +00:00
|
|
|
|
2021-12-10 17:39:34 +00:00
|
|
|
var firstUse = !mw.user.options.get( 'discussiontools-newtopictool-opened' );
|
|
|
|
if (
|
|
|
|
( firstUse || mw.user.options.get( 'discussiontools-newtopictool-hint-shown' ) ) &&
|
|
|
|
mw.config.get( 'wgUserId' ) && mw.config.get( 'wgUserEditCount', 0 ) >= 500
|
|
|
|
) {
|
|
|
|
// Topic hint should be shown to logged in users who have more than
|
|
|
|
// 500 edits on their first use of the tool, and should persist until
|
|
|
|
// they deliberately close it.
|
|
|
|
this.setupTopicHint();
|
|
|
|
}
|
|
|
|
if ( firstUse ) {
|
2021-11-15 06:17:21 +00:00
|
|
|
controller.getApi().saveOption( 'discussiontools-newtopictool-opened', '1' ).then( function () {
|
|
|
|
mw.user.options.set( 'discussiontools-newtopictool-opened', '1' );
|
|
|
|
} );
|
|
|
|
}
|
2020-08-29 12:00:51 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
NewTopicController.prototype.setupReplyWidget = function ( replyWidget, data ) {
|
|
|
|
NewTopicController.super.prototype.setupReplyWidget.call( this, replyWidget, data );
|
|
|
|
|
2020-12-03 22:32:35 +00:00
|
|
|
this.$notices.empty();
|
|
|
|
for ( var noticeName in this.replyWidget.commentDetails.notices ) {
|
2021-07-29 06:12:10 +00:00
|
|
|
if ( this.constructor.static.suppressedEditNotices.indexOf( noticeName ) !== -1 ) {
|
2020-12-03 22:32:35 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
var noticeItem = this.replyWidget.commentDetails.notices[ noticeName ];
|
|
|
|
var $noticeElement = $( '<div>' )
|
|
|
|
.addClass( 'ext-discussiontools-ui-replyWidget-notice' )
|
|
|
|
.html( typeof noticeItem === 'string' ? noticeItem : noticeItem.message );
|
|
|
|
this.$notices.append( $noticeElement );
|
|
|
|
}
|
|
|
|
|
2021-04-08 13:46:09 +00:00
|
|
|
var title = this.replyWidget.storage.get( this.replyWidget.storagePrefix + '/title' );
|
2020-08-29 12:00:51 +00:00
|
|
|
if ( title && !this.sectionTitle.getValue() ) {
|
|
|
|
// Don't overwrite if the user has already typed something in while the widget was loading.
|
|
|
|
// TODO This should happen immediately rather than waiting for the reply widget to load,
|
|
|
|
// then we wouldn't need this check, but the autosave code is in ReplyWidget.
|
|
|
|
this.sectionTitle.setValue( title );
|
|
|
|
}
|
|
|
|
|
|
|
|
this.sectionTitle.connect( this, { change: 'onSectionTitleChange' } );
|
2021-09-01 19:04:57 +00:00
|
|
|
this.replyWidget.connect( this, { bodyFocus: 'onBodyFocus' } );
|
2021-08-06 00:31:50 +00:00
|
|
|
|
|
|
|
replyWidget.connect( this, {
|
|
|
|
clear: 'clear',
|
|
|
|
clearStorage: 'clearStorage'
|
|
|
|
} );
|
2020-08-29 12:00:51 +00:00
|
|
|
};
|
|
|
|
|
2021-12-10 17:39:34 +00:00
|
|
|
/**
|
|
|
|
* Create and display a hint dialog that redirects users to the non-DT version of this tool
|
|
|
|
*/
|
|
|
|
NewTopicController.prototype.setupTopicHint = function () {
|
2022-01-27 17:47:44 +00:00
|
|
|
var topicController = this;
|
2021-12-10 17:39:34 +00:00
|
|
|
var legacyURI;
|
|
|
|
try {
|
|
|
|
legacyURI = new mw.Uri();
|
|
|
|
} catch ( err ) {
|
|
|
|
// T106244: URL encoded values using fallback 8-bit encoding (invalid UTF-8) cause mediawiki.Uri to crash
|
|
|
|
return;
|
|
|
|
}
|
2022-02-22 15:18:14 +00:00
|
|
|
var fragment = '';
|
|
|
|
if ( OO.ui.isMobile() ) {
|
|
|
|
// mw.Uri encodes link fragments, converting to '/' to '%2F', which breaks the router
|
|
|
|
fragment = '#/talk/new';
|
|
|
|
delete legacyURI.query.action;
|
|
|
|
delete legacyURI.query.section;
|
|
|
|
} else {
|
|
|
|
legacyURI.query.action = 'edit';
|
|
|
|
legacyURI.query.section = 'new';
|
|
|
|
}
|
2021-12-10 17:39:34 +00:00
|
|
|
legacyURI.query.dtenable = '0';
|
2021-12-16 11:51:04 +00:00
|
|
|
// This is not a real valid value for 'editintro', but we look for it elsewhere to generate our own edit notice
|
|
|
|
legacyURI.query.editintro = 'mw-dt-topic-hint';
|
2022-02-22 15:18:14 +00:00
|
|
|
|
2021-12-10 17:39:34 +00:00
|
|
|
this.topicHint = new OO.ui.MessageWidget( {
|
2022-02-22 15:18:14 +00:00
|
|
|
label: mw.message( 'discussiontools-newtopic-legacy-hint', legacyURI.toString() + fragment ).parseDom(),
|
2021-12-10 17:39:34 +00:00
|
|
|
icon: 'article'
|
|
|
|
} );
|
|
|
|
this.topicHint.$element.addClass( 'ext-discussiontools-ui-newTopic-hint' );
|
|
|
|
// TODO: Once showClose lands in OOUI's MessageWidget this can be replaced:
|
|
|
|
var dismissButton = new OO.ui.ButtonWidget( {
|
|
|
|
icon: 'close',
|
|
|
|
framed: false,
|
|
|
|
title: mw.msg( 'discussiontools-newtopic-legacy-hint-close' )
|
|
|
|
} ).connect( this, { click: 'onTopicHintCloseClick' } );
|
|
|
|
this.topicHint.$element.prepend( dismissButton.$element );
|
2022-01-27 17:47:44 +00:00
|
|
|
this.topicHint.$element.find( 'a' ).on( 'click', function () {
|
|
|
|
// Clicking to follow this link should immediately discard the
|
|
|
|
// autosave. We can do this before the onBeforeUnload handler asks
|
|
|
|
// them to confirm, because if they decide to cancel the navigation
|
|
|
|
// then the autosave will occur again.
|
|
|
|
topicController.clearStorage();
|
|
|
|
topicController.replyWidget.clearStorage();
|
|
|
|
} );
|
2021-12-10 17:39:34 +00:00
|
|
|
this.container.$element.before( this.topicHint.$element );
|
|
|
|
|
|
|
|
this.topicHint.toggle( true );
|
|
|
|
|
|
|
|
// This needs to persist once it's shown
|
|
|
|
controller.getApi().saveOption( 'discussiontools-newtopictool-hint-shown', 1 ).then( function () {
|
|
|
|
mw.user.options.set( 'discussiontools-newtopictool-hint-shown', 1 );
|
|
|
|
} );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle clicks on the close button for the hint dialog
|
|
|
|
*/
|
|
|
|
NewTopicController.prototype.onTopicHintCloseClick = function () {
|
2021-12-23 18:58:50 +00:00
|
|
|
this.topicHint.toggle( false );
|
2021-12-10 17:39:34 +00:00
|
|
|
controller.getApi().saveOption( 'discussiontools-newtopictool-hint-shown', null ).then( function () {
|
|
|
|
mw.user.options.set( 'discussiontools-newtopictool-hint-shown', null );
|
|
|
|
} );
|
|
|
|
};
|
|
|
|
|
2020-08-29 12:00:51 +00:00
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
NewTopicController.prototype.focus = function () {
|
|
|
|
this.sectionTitle.focus();
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2021-08-06 00:31:50 +00:00
|
|
|
* Restore the widget to its original state
|
2020-08-29 12:00:51 +00:00
|
|
|
*/
|
2021-08-06 00:31:50 +00:00
|
|
|
NewTopicController.prototype.clear = function () {
|
|
|
|
// This is going to get called as part of the teardown chain from replywidget
|
|
|
|
this.sectionTitle.setValue( '' );
|
|
|
|
this.sectionTitleField.setWarnings( [] );
|
|
|
|
};
|
2020-08-29 12:00:51 +00:00
|
|
|
|
2021-08-06 00:31:50 +00:00
|
|
|
/**
|
|
|
|
* Remove any storage that the widget is using
|
|
|
|
*/
|
|
|
|
NewTopicController.prototype.clearStorage = function () {
|
|
|
|
// This is going to get called as part of the teardown chain from replywidget
|
2021-03-24 18:41:39 +00:00
|
|
|
if ( this.replyWidget ) {
|
|
|
|
this.replyWidget.storage.remove( this.replyWidget.storagePrefix + '/title' );
|
|
|
|
}
|
2021-08-06 00:31:50 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
NewTopicController.prototype.teardown = function ( abandoned ) {
|
|
|
|
NewTopicController.super.prototype.teardown.call( this, abandoned );
|
|
|
|
|
2021-01-13 20:54:59 +00:00
|
|
|
this.container.$element.detach();
|
2021-12-10 17:39:34 +00:00
|
|
|
if ( this.topicHint ) {
|
|
|
|
this.topicHint.$element.detach();
|
|
|
|
}
|
2021-07-28 10:36:58 +00:00
|
|
|
|
|
|
|
if ( mw.config.get( 'wgDiscussionToolsStartNewTopicTool' ) ) {
|
|
|
|
var uri;
|
|
|
|
try {
|
|
|
|
uri = new mw.Uri();
|
|
|
|
} catch ( err ) {
|
|
|
|
// T106244: URL encoded values using fallback 8-bit encoding (invalid UTF-8) cause mediawiki.Uri to crash
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
delete uri.query.action;
|
|
|
|
delete uri.query.veaction;
|
|
|
|
delete uri.query.section;
|
|
|
|
history.replaceState( null, document.title, uri.toString() );
|
|
|
|
mw.config.set( 'wgDiscussionToolsStartNewTopicTool', false );
|
|
|
|
}
|
2020-08-29 12:00:51 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
2021-02-17 23:42:57 +00:00
|
|
|
NewTopicController.prototype.doIndentReplacements = function ( wikitext ) {
|
|
|
|
// No indent replacements when posting new topics
|
2020-08-29 12:00:51 +00:00
|
|
|
return wikitext;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
2021-02-17 23:42:57 +00:00
|
|
|
NewTopicController.prototype.undoIndentReplacements = function ( wikitext ) {
|
|
|
|
// No indent replacements when posting new topics
|
2020-08-29 12:00:51 +00:00
|
|
|
return wikitext;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
NewTopicController.prototype.getUnsupportedNodeSelectors = function () {
|
|
|
|
// No unsupported nodes when posting new topics
|
|
|
|
return {};
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
2021-12-20 17:06:24 +00:00
|
|
|
NewTopicController.prototype.getApiQuery = function ( pageName, checkboxes ) {
|
|
|
|
var data = NewTopicController.super.prototype.getApiQuery.call( this, pageName, checkboxes );
|
2020-08-29 12:00:51 +00:00
|
|
|
|
2021-05-03 17:42:16 +00:00
|
|
|
// Rebuild the tags array and remove the reply tag
|
|
|
|
var tags = ( data.dttags || '' ).split( ',' );
|
|
|
|
var replyTag = tags.indexOf( 'discussiontools-reply' );
|
|
|
|
if ( replyTag !== -1 ) {
|
|
|
|
tags.splice( replyTag, 1 );
|
|
|
|
}
|
|
|
|
// Add the newtopic tag
|
|
|
|
tags.push( 'discussiontools-newtopic' );
|
|
|
|
|
2020-08-29 12:00:51 +00:00
|
|
|
data = $.extend( {}, data, {
|
|
|
|
paction: 'addtopic',
|
|
|
|
sectiontitle: this.sectionTitle.getValue(),
|
2021-10-18 08:32:17 +00:00
|
|
|
dttags: tags.join( ',' ),
|
|
|
|
editingStatsId: logger.getSessionId()
|
2020-08-29 12:00:51 +00:00
|
|
|
} );
|
|
|
|
|
2021-09-21 13:39:10 +00:00
|
|
|
// Allow MediaWiki to generate the summary if it wasn't modified by the user. This avoids
|
|
|
|
// inconsistencies in how wiki markup is stripped from section titles when they're used in
|
|
|
|
// automatic summaries. (T275178)
|
|
|
|
if ( data.summary === this.generateSummary( this.sectionTitle.getValue() ) ) {
|
|
|
|
delete data.summary;
|
|
|
|
}
|
|
|
|
|
2020-08-29 12:00:51 +00:00
|
|
|
return data;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate a default edit summary based on the section title.
|
|
|
|
*
|
|
|
|
* @param {string} titleText Section title
|
|
|
|
* @return {string}
|
|
|
|
*/
|
|
|
|
NewTopicController.prototype.generateSummary = function ( titleText ) {
|
|
|
|
return titleText ? mw.msg( 'newsectionsummary', titleText ) : '';
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle 'change' events for the section title input.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
NewTopicController.prototype.onSectionTitleChange = function () {
|
2021-04-08 13:46:09 +00:00
|
|
|
var titleText = this.sectionTitle.getValue();
|
|
|
|
var prevTitleText = this.prevTitleText;
|
2020-08-29 12:00:51 +00:00
|
|
|
|
|
|
|
if ( prevTitleText !== titleText ) {
|
|
|
|
this.replyWidget.storage.set( this.replyWidget.storagePrefix + '/title', titleText );
|
|
|
|
|
2021-04-08 13:46:09 +00:00
|
|
|
var generatedSummary = this.generateSummary( titleText );
|
|
|
|
var generatedPrevSummary = this.generateSummary( prevTitleText );
|
2020-08-29 12:00:51 +00:00
|
|
|
|
2021-04-08 13:46:09 +00:00
|
|
|
var currentSummary = this.replyWidget.editSummaryInput.getValue();
|
2020-08-29 12:00:51 +00:00
|
|
|
|
|
|
|
// Fill in edit summary if it was not modified by the user yet
|
|
|
|
if ( currentSummary === generatedPrevSummary ) {
|
|
|
|
this.replyWidget.editSummaryInput.setValue( generatedSummary );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.prevTitleText = titleText;
|
2021-01-29 22:01:26 +00:00
|
|
|
|
|
|
|
this.checkSectionTitleValidity();
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2021-09-01 19:04:57 +00:00
|
|
|
* Handle 'focus' events for the description field (regardless of mode).
|
2021-01-29 22:01:26 +00:00
|
|
|
*
|
|
|
|
* @private
|
|
|
|
*/
|
2021-09-01 19:04:57 +00:00
|
|
|
NewTopicController.prototype.onBodyFocus = function () {
|
2021-04-08 13:46:09 +00:00
|
|
|
var offsetBefore = this.replyWidget.$element.offset().top;
|
2021-09-01 19:04:57 +00:00
|
|
|
var rootScrollable = OO.ui.Element.static.getRootScrollableElement( document.body );
|
|
|
|
var scrollBefore = rootScrollable.scrollTop;
|
2021-02-28 15:00:12 +00:00
|
|
|
|
2021-01-29 22:01:26 +00:00
|
|
|
this.checkSectionTitleValidity();
|
2021-02-28 15:00:12 +00:00
|
|
|
|
2021-04-08 13:46:09 +00:00
|
|
|
var offsetChange = this.replyWidget.$element.offset().top - offsetBefore;
|
2021-02-28 15:00:12 +00:00
|
|
|
// Ensure the rest of the widget doesn't move when the validation
|
2021-09-01 19:04:57 +00:00
|
|
|
// message is triggered by a focus. (T275923)
|
|
|
|
// Browsers sometimes also scroll in response to focus events,
|
|
|
|
// so use the old scrollTop value for consistent results.
|
|
|
|
rootScrollable.scrollTop = scrollBefore + offsetChange;
|
2021-01-29 22:01:26 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if the section title is valid, and display a warning message.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
NewTopicController.prototype.checkSectionTitleValidity = function () {
|
|
|
|
if ( !this.sectionTitle.getValue() ) {
|
|
|
|
// Show warning about missing title
|
|
|
|
this.sectionTitleField.setWarnings( [
|
|
|
|
mw.msg( 'discussiontools-newtopic-missing-title' )
|
|
|
|
] );
|
|
|
|
} else {
|
|
|
|
this.sectionTitleField.setWarnings( [] );
|
|
|
|
}
|
2020-08-29 12:00:51 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
module.exports = NewTopicController;
|