mediawiki-extensions-Discus.../modules/NewTopicController.js
Bartosz Dziewoński a2c662d3b2 Move reply link handling out of CommentController
Problems with the current setup:

* Each CommentController must have exactly one link.
  For T277371 we want multiple, and for T282205 we might want zero.

* CommentController objects must be constructed immediately.
  They are implemented to make this pretty fast, but it's still
  unnecessary work to do on page load.

* Only one link may be activated at a time, and activating one affects
  the styling of others, so CommentController has to use global state
  to check if it can set up and to update them.

Instead introduce ReplyLinksController, which knows about all reply
links and which one is active, and emits events that allow
CommentControllers to be constructed on demand.

Change-Id: Iabdeded2e71e598ae78703a6ff9410d0cfba397c
2021-06-30 17:05:55 +02:00

242 lines
7.2 KiB
JavaScript

var
utils = require( './utils.js' ),
CommentController = require( './CommentController.js' ),
HeadingItem = require( './HeadingItem.js' );
function NewTopicController( $pageContainer, parser ) {
this.container = new OO.ui.PanelLayout( {
classes: [ 'ext-discussiontools-ui-newTopic' ],
expanded: false,
padded: true,
framed: true
} );
this.$notices = $( '<div>' ).addClass( 'ext-discussiontools-ui-newTopic-notices' );
this.sectionTitle = new OO.ui.TextInputWidget( {
// Wrap in a <h2> element to inherit heading font styles
$element: $( '<h2>' ),
classes: [ 'ext-discussiontools-ui-newTopic-sectionTitle' ],
placeholder: mw.msg( 'discussiontools-newtopic-placeholder-title' ),
spellcheck: true
} );
this.sectionTitle.$input.attr( 'aria-label', mw.msg( 'discussiontools-newtopic-placeholder-title' ) );
this.sectionTitleField = new OO.ui.FieldLayout( this.sectionTitle, {
align: 'top'
} );
this.prevTitleText = '';
this.container.$element.append( this.$notices, this.sectionTitleField.$element );
// HeadingItem representing the heading being added, so that we can pretend we're replying to it
var comment = new HeadingItem( {
startContainer: this.sectionTitleField.$element[ 0 ],
startOffset: 0,
endContainer: this.sectionTitleField.$element[ 0 ],
endOffset: this.sectionTitleField.$element[ 0 ].childNodes.length
} );
comment.id = utils.NEW_TOPIC_COMMENT_ID;
comment.isNewTopic = true;
NewTopicController.super.call( this, $pageContainer, comment, parser );
}
OO.inheritClass( NewTopicController, CommentController );
/* Static properties */
NewTopicController.static.initType = 'section';
/* Methods */
/**
* @inheritdoc
*/
NewTopicController.prototype.setup = function ( mode ) {
var rootScrollable = OO.ui.Element.static.getRootScrollableElement( document.body );
this.$pageContainer.append( this.container.$element );
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();
};
/**
* @inheritdoc
*/
NewTopicController.prototype.setupReplyWidget = function ( replyWidget, data ) {
NewTopicController.super.prototype.setupReplyWidget.call( this, replyWidget, data );
this.$notices.empty();
for ( var noticeName in this.replyWidget.commentDetails.notices ) {
if (
// Ignored because we have a custom warning for non-logged-in users.
noticeName === 'anoneditwarning' ||
// Ignored because it contains mostly instructions for signing comments using tildes.
// (Does not appear in VE notices right now, but just in case.)
noticeName === 'talkpagetext'
) {
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 );
}
var title = this.replyWidget.storage.get( this.replyWidget.storagePrefix + '/title' );
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' } );
this.sectionTitle.$input.on( 'blur', this.onSectionTitleBlur.bind( this ) );
};
/**
* @inheritdoc
*/
NewTopicController.prototype.focus = function () {
this.sectionTitle.focus();
};
/**
* @inheritdoc
*/
NewTopicController.prototype.teardown = function ( abandoned ) {
NewTopicController.super.prototype.teardown.call( this, abandoned );
if ( this.replyWidget ) {
this.replyWidget.storage.remove( this.replyWidget.storagePrefix + '/title' );
}
this.sectionTitle.setValue( '' );
this.sectionTitleField.setWarnings( [] );
this.container.$element.detach();
};
/**
* @inheritdoc
*/
NewTopicController.prototype.doIndentReplacements = function ( wikitext ) {
// No indent replacements when posting new topics
return wikitext;
};
/**
* @inheritdoc
*/
NewTopicController.prototype.undoIndentReplacements = function ( wikitext ) {
// No indent replacements when posting new topics
return wikitext;
};
/**
* @inheritdoc
*/
NewTopicController.prototype.getUnsupportedNodeSelectors = function () {
// No unsupported nodes when posting new topics
return {};
};
/**
* @inheritdoc
*/
NewTopicController.prototype.getApiQuery = function ( comment, pageName, checkboxes ) {
var data = NewTopicController.super.prototype.getApiQuery.call( this, comment, pageName, checkboxes );
// 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' );
data = $.extend( {}, data, {
paction: 'addtopic',
sectiontitle: this.sectionTitle.getValue(),
dttags: tags.join( ',' )
} );
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 () {
var titleText = this.sectionTitle.getValue();
var prevTitleText = this.prevTitleText;
if ( prevTitleText !== titleText ) {
this.replyWidget.storage.set( this.replyWidget.storagePrefix + '/title', titleText );
var generatedSummary = this.generateSummary( titleText );
var generatedPrevSummary = this.generateSummary( prevTitleText );
var currentSummary = this.replyWidget.editSummaryInput.getValue();
// Fill in edit summary if it was not modified by the user yet
if ( currentSummary === generatedPrevSummary ) {
this.replyWidget.editSummaryInput.setValue( generatedSummary );
}
}
this.prevTitleText = titleText;
this.checkSectionTitleValidity();
};
/**
* Handle 'blur' events for the section title input.
*
* @private
*/
NewTopicController.prototype.onSectionTitleBlur = function () {
var offsetBefore = this.replyWidget.$element.offset().top;
this.checkSectionTitleValidity();
var offsetChange = this.replyWidget.$element.offset().top - offsetBefore;
// Ensure the rest of the widget doesn't move when the validation
// message is triggered by a blur. (T275923)
window.scrollBy( 0, offsetChange );
};
/**
* 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( [] );
}
};
module.exports = NewTopicController;