ESLint: Autofix no-var rule

Leave rule off for now as manual fixes are required.

Also temporarily disable prefer-const rule as that
will also require some manual fixes.

Change-Id: I8c3478f26f51287acb943bd38c9c1020c06b9f39
This commit is contained in:
Ed Sanders 2024-05-24 13:20:50 +01:00
parent 9f68ee30af
commit ca5157156a
36 changed files with 512 additions and 510 deletions

View file

@ -4,6 +4,7 @@
# Build # Build
/vendor/ /vendor/
/coverage/ /coverage/
/docs/
# Language files written automatically by TranslateWiki # Language files written automatically by TranslateWiki
/i18n/**/*.json /i18n/**/*.json

View file

@ -12,6 +12,7 @@
"rules": { "rules": {
"no-implicit-globals": "off", "no-implicit-globals": "off",
"no-var": "off", "no-var": "off",
"prefer-const": "off",
"max-len": "off", "max-len": "off",
"prefer-arrow-callback": "error", "prefer-arrow-callback": "error",
"implicit-arrow-linebreak": "error", "implicit-arrow-linebreak": "error",

View file

@ -64,12 +64,12 @@ function getLatestRevId( pageName ) {
* @return {jQuery.Promise} Promise which resolves with a CommentDetails object, or rejects with an error * @return {jQuery.Promise} Promise which resolves with a CommentDetails object, or rejects with an error
*/ */
CommentController.prototype.getTranscludedFromSource = function () { CommentController.prototype.getTranscludedFromSource = function () {
var pageName = mw.config.get( 'wgRelevantPageName' ), const pageName = mw.config.get( 'wgRelevantPageName' ),
oldId = mw.config.get( 'wgCurRevisionId' ), oldId = mw.config.get( 'wgCurRevisionId' ),
threadItem = this.getThreadItem(); threadItem = this.getThreadItem();
function followTransclusion( recursionLimit, code, data ) { function followTransclusion( recursionLimit, code, data ) {
var errorData; let errorData;
if ( recursionLimit > 0 && code === 'comment-is-transcluded' ) { if ( recursionLimit > 0 && code === 'comment-is-transcluded' ) {
errorData = data.errors[ 0 ].data; errorData = data.errors[ 0 ].data;
if ( errorData.follow && typeof errorData.transcludedFrom === 'string' ) { if ( errorData.follow && typeof errorData.transcludedFrom === 'string' ) {
@ -85,7 +85,7 @@ CommentController.prototype.getTranscludedFromSource = function () {
// Arbitrary limit of 10 steps, which should be more than anyone could ever need // Arbitrary limit of 10 steps, which should be more than anyone could ever need
// (there are reasonable use cases for at least 2) // (there are reasonable use cases for at least 2)
var promise = controller.checkThreadItemOnPage( pageName, oldId, threadItem ) const promise = controller.checkThreadItemOnPage( pageName, oldId, threadItem )
.catch( followTransclusion.bind( null, 10 ) ); .catch( followTransclusion.bind( null, 10 ) );
return promise; return promise;
@ -114,7 +114,7 @@ CommentController.static.initType = 'page';
* @param {boolean} [suppressNotifications] Don't notify the user if recovering auto-save * @param {boolean} [suppressNotifications] Don't notify the user if recovering auto-save
*/ */
CommentController.prototype.setup = function ( mode, hideErrors, suppressNotifications ) { CommentController.prototype.setup = function ( mode, hideErrors, suppressNotifications ) {
var threadItem = this.getThreadItem(); const threadItem = this.getThreadItem();
if ( mode === undefined ) { if ( mode === undefined ) {
mode = mw.user.options.get( 'discussiontools-editmode' ) || mode = mw.user.options.get( 'discussiontools-editmode' ) ||
@ -168,7 +168,7 @@ CommentController.prototype.setup = function ( mode, hideErrors, suppressNotific
// we wrap this loading message in another element. // we wrap this loading message in another element.
$( '<span>' ).text( mw.msg( 'discussiontools-replywidget-loading' ) ) $( '<span>' ).text( mw.msg( 'discussiontools-replywidget-loading' ) )
); );
var scrollPaddingCollapsed = OO.copy( scrollPadding ); const scrollPaddingCollapsed = OO.copy( scrollPadding );
// We don't know exactly how tall the widge will be, but leave room for one line // We don't know exactly how tall the widge will be, but leave room for one line
// of preview in source mode (~270px). Visual mode is ~250px. // of preview in source mode (~270px). Visual mode is ~250px.
scrollPaddingCollapsed.bottom += 270; scrollPaddingCollapsed.bottom += 270;
@ -230,8 +230,8 @@ CommentController.prototype.onVisibilityChange = function () {
CommentController.prototype.startPoll = function ( nextDelay ) { CommentController.prototype.startPoll = function ( nextDelay ) {
nextDelay = nextDelay || 5000; nextDelay = nextDelay || 5000;
var threadItemId = this.threadItem.id; const threadItemId = this.threadItem.id;
var subscribableHeadingId = this.threadItem.getSubscribableHeading().id; const subscribableHeadingId = this.threadItem.getSubscribableHeading().id;
this.pollApiRequest = controller.getApi().get( { this.pollApiRequest = controller.getApi().get( {
action: 'discussiontoolscompare', action: 'discussiontoolscompare',
@ -245,18 +245,18 @@ CommentController.prototype.startPoll = function ( nextDelay ) {
cmt.author !== mw.user.getName(); cmt.author !== mw.user.getName();
} }
var result = OO.getProp( response, 'discussiontoolscompare' ) || {}; const result = OO.getProp( response, 'discussiontoolscompare' ) || {};
var addedComments = result.addedcomments.filter( relevantCommentFilter ); const addedComments = result.addedcomments.filter( relevantCommentFilter );
var removedComments = result.removedcomments.filter( relevantCommentFilter ); const removedComments = result.removedcomments.filter( relevantCommentFilter );
if ( addedComments.length || removedComments.length ) { if ( addedComments.length || removedComments.length ) {
this.updateNewCommentsWarning( addedComments, removedComments ); this.updateNewCommentsWarning( addedComments, removedComments );
} }
// Parent comment was deleted // Parent comment was deleted
var isParentRemoved = result.removedcomments.some( ( cmt ) => cmt.id === threadItemId ); const isParentRemoved = result.removedcomments.some( ( cmt ) => cmt.id === threadItemId );
// Parent comment was deleted then added back (e.g. reverted vandalism) // Parent comment was deleted then added back (e.g. reverted vandalism)
var isParentAdded = result.addedcomments.some( ( cmt ) => cmt.id === threadItemId ); const isParentAdded = result.addedcomments.some( ( cmt ) => cmt.id === threadItemId );
if ( isParentAdded ) { if ( isParentAdded ) {
this.setParentRemoved( false ); this.setParentRemoved( false );
@ -410,12 +410,12 @@ CommentController.prototype.onReplyWidgetTeardown = function ( mode ) {
* @return {Object.<string,string>} API query data * @return {Object.<string,string>} API query data
*/ */
CommentController.prototype.getApiQuery = function ( pageName, checkboxes ) { CommentController.prototype.getApiQuery = function ( pageName, checkboxes ) {
var threadItem = this.getThreadItem(); const threadItem = this.getThreadItem();
var replyWidget = this.replyWidget; const replyWidget = this.replyWidget;
var sameNameComments = this.threadItemSet.findCommentsByName( threadItem.name ); const sameNameComments = this.threadItemSet.findCommentsByName( threadItem.name );
var mode = replyWidget.getMode(); const mode = replyWidget.getMode();
var tags = [ const tags = [
'discussiontools', 'discussiontools',
'discussiontools-reply', 'discussiontools-reply',
'discussiontools-' + mode 'discussiontools-' + mode
@ -425,7 +425,7 @@ CommentController.prototype.getApiQuery = function ( pageName, checkboxes ) {
tags.push( 'discussiontools-source-enhanced' ); tags.push( 'discussiontools-source-enhanced' );
} }
var data = { const data = {
action: 'discussiontoolsedit', action: 'discussiontoolsedit',
paction: 'addcomment', paction: 'addcomment',
page: pageName, page: pageName,
@ -448,7 +448,7 @@ CommentController.prototype.getApiQuery = function ( pageName, checkboxes ) {
data.html = replyWidget.getValue(); data.html = replyWidget.getValue();
} }
var captchaInput = replyWidget.captchaInput; const captchaInput = replyWidget.captchaInput;
if ( captchaInput ) { if ( captchaInput ) {
data.captchaid = captchaInput.getCaptchaId(); data.captchaid = captchaInput.getCaptchaId();
data.captchaword = captchaInput.getCaptchaWord(); data.captchaword = captchaInput.getCaptchaWord();
@ -470,11 +470,11 @@ CommentController.prototype.getApiQuery = function ( pageName, checkboxes ) {
* @return {jQuery.Promise} Promise which resolves when the save is complete * @return {jQuery.Promise} Promise which resolves when the save is complete
*/ */
CommentController.prototype.save = function ( pageName ) { CommentController.prototype.save = function ( pageName ) {
var replyWidget = this.replyWidget, const replyWidget = this.replyWidget,
threadItem = this.getThreadItem(); threadItem = this.getThreadItem();
return this.replyWidget.checkboxesPromise.then( ( checkboxes ) => { return this.replyWidget.checkboxesPromise.then( ( checkboxes ) => {
var data = this.getApiQuery( pageName, checkboxes ); const data = this.getApiQuery( pageName, checkboxes );
if ( if (
// We're saving the first comment on a page that previously didn't exist. // We're saving the first comment on a page that previously didn't exist.
@ -491,7 +491,7 @@ CommentController.prototype.save = function ( pageName ) {
// This means that we might need to redirect to an opaque URL, // This means that we might need to redirect to an opaque URL,
// so we must set up query parameters we want ahead of time. // so we must set up query parameters we want ahead of time.
data.returnto = pageName; data.returnto = pageName;
var params = new URLSearchParams(); const params = new URLSearchParams();
params.set( 'dtrepliedto', this.getThreadItem().id ); params.set( 'dtrepliedto', this.getThreadItem().id );
params.set( 'dttempusercreated', '1' ); params.set( 'dttempusercreated', '1' );
data.returntoquery = params.toString(); data.returntoquery = params.toString();
@ -499,9 +499,9 @@ CommentController.prototype.save = function ( pageName ) {
// No timeout. Huge talk pages can take a long time to save, and falsely reporting an error // No timeout. Huge talk pages can take a long time to save, and falsely reporting an error
// could result in duplicate messages if the user retries. (T249071) // could result in duplicate messages if the user retries. (T249071)
var defaults = OO.copy( controller.getApi().defaults ); const defaults = OO.copy( controller.getApi().defaults );
defaults.ajax.timeout = 0; defaults.ajax.timeout = 0;
var noTimeoutApi = new mw.Api( defaults ); const noTimeoutApi = new mw.Api( defaults );
return mw.libs.ve.targetSaver.postContent( return mw.libs.ve.targetSaver.postContent(
data, { api: noTimeoutApi } data, { api: noTimeoutApi }
@ -541,7 +541,7 @@ CommentController.prototype.updateNewCommentsWarning = function ( addedComments,
this.newComments.push.apply( this.newComments, addedComments ); this.newComments.push.apply( this.newComments, addedComments );
// Delete any comments which have since been deleted (e.g. posted then reverted) // Delete any comments which have since been deleted (e.g. posted then reverted)
var removedCommentIds = removedComments.filter( ( cmt ) => cmt.id ); const removedCommentIds = removedComments.filter( ( cmt ) => cmt.id );
this.newComments = this.newComments.filter( this.newComments = this.newComments.filter(
// If comment ID is not in removedCommentIds, keep it // If comment ID is not in removedCommentIds, keep it
( cmt ) => removedCommentIds.indexOf( cmt.id ) === -1 ( cmt ) => removedCommentIds.indexOf( cmt.id ) === -1
@ -571,14 +571,14 @@ CommentController.prototype.setParentRemoved = function ( parentRemoved ) {
* @return {jQuery.Promise} Promise which resolves when switch is complete * @return {jQuery.Promise} Promise which resolves when switch is complete
*/ */
CommentController.prototype.switchToWikitext = function () { CommentController.prototype.switchToWikitext = function () {
var oldWidget = this.replyWidget, const oldWidget = this.replyWidget,
target = oldWidget.replyBodyWidget.target, target = oldWidget.replyBodyWidget.target,
oldShowAdvanced = oldWidget.showAdvanced, oldShowAdvanced = oldWidget.showAdvanced,
oldEditSummary = oldWidget.getEditSummary(), oldEditSummary = oldWidget.getEditSummary(),
previewDeferred = $.Deferred(); previewDeferred = $.Deferred();
// TODO: We may need to pass oldid/etag when editing is supported // TODO: We may need to pass oldid/etag when editing is supported
var wikitextPromise = target.getWikitextFragment( target.getSurface().getModel().getDocument() ); const wikitextPromise = target.getWikitextFragment( target.getSurface().getModel().getDocument() );
this.replyWidgetPromise = this.createReplyWidget( this.replyWidgetPromise = this.createReplyWidget(
oldWidget.commentDetails, oldWidget.commentDetails,
{ mode: 'source' } { mode: 'source' }
@ -633,7 +633,7 @@ CommentController.prototype.doIndentReplacements = function ( wikitext, indent )
* @param {Node} rootNode Node potentially containing definition lists (modified in-place) * @param {Node} rootNode Node potentially containing definition lists (modified in-place)
*/ */
CommentController.prototype.undoIndentReplacements = function ( rootNode ) { CommentController.prototype.undoIndentReplacements = function ( rootNode ) {
var children = Array.prototype.slice.call( rootNode.childNodes ); const children = Array.prototype.slice.call( rootNode.childNodes );
// There may be multiple lists when some lines are template generated // There may be multiple lists when some lines are template generated
children.forEach( ( child ) => { children.forEach( ( child ) => {
if ( child.nodeType === Node.ELEMENT_NODE ) { if ( child.nodeType === Node.ELEMENT_NODE ) {
@ -671,7 +671,7 @@ CommentController.prototype.getUnsupportedNodeSelectors = function () {
* @return {jQuery.Promise} Promise which resolves when switch is complete * @return {jQuery.Promise} Promise which resolves when switch is complete
*/ */
CommentController.prototype.switchToVisual = function () { CommentController.prototype.switchToVisual = function () {
var oldWidget = this.replyWidget, let oldWidget = this.replyWidget,
oldShowAdvanced = oldWidget.showAdvanced, oldShowAdvanced = oldWidget.showAdvanced,
oldEditSummary = oldWidget.getEditSummary(), oldEditSummary = oldWidget.getEditSummary(),
wikitext = oldWidget.getValue(); wikitext = oldWidget.getValue();
@ -684,7 +684,7 @@ CommentController.prototype.switchToVisual = function () {
'$1<span data-dtsignatureforswitching="1"></span>$2' '$1<span data-dtsignatureforswitching="1"></span>$2'
); );
var parsePromise; let parsePromise;
if ( wikitext ) { if ( wikitext ) {
wikitext = this.doIndentReplacements( wikitext, dtConf.replyIndentation === 'invisible' ? ':' : '*' ); wikitext = this.doIndentReplacements( wikitext, dtConf.replyIndentation === 'invisible' ? ':' : '*' );
@ -705,17 +705,17 @@ CommentController.prototype.switchToVisual = function () {
); );
return $.when( parsePromise, this.replyWidgetPromise ).then( ( html, replyWidget ) => { return $.when( parsePromise, this.replyWidgetPromise ).then( ( html, replyWidget ) => {
var unsupportedSelectors = this.getUnsupportedNodeSelectors(); const unsupportedSelectors = this.getUnsupportedNodeSelectors();
var doc; let doc;
if ( html ) { if ( html ) {
doc = replyWidget.replyBodyWidget.target.parseDocument( html ); doc = replyWidget.replyBodyWidget.target.parseDocument( html );
// Remove RESTBase IDs (T253584) // Remove RESTBase IDs (T253584)
mw.libs.ve.stripRestbaseIds( doc ); mw.libs.ve.stripRestbaseIds( doc );
// Check for tables, headings, images, templates // Check for tables, headings, images, templates
for ( var type in unsupportedSelectors ) { for ( const type in unsupportedSelectors ) {
if ( doc.querySelector( unsupportedSelectors[ type ] ) ) { if ( doc.querySelector( unsupportedSelectors[ type ] ) ) {
var $msg = $( '<div>' ).html( const $msg = $( '<div>' ).html(
mw.message( mw.message(
'discussiontools-error-noswitchtove', 'discussiontools-error-noswitchtove',
// The following messages are used here: // The following messages are used here:

View file

@ -51,8 +51,8 @@ OO.inheritClass( CommentItem, ThreadItem );
* @return {string} Comment timestamp in standard format * @return {string} Comment timestamp in standard format
*/ */
CommentItem.prototype.getTimestampString = function () { CommentItem.prototype.getTimestampString = function () {
var dtConfig = require( './config.json' ); const dtConfig = require( './config.json' );
var switchTime = moment.utc( dtConfig.switchTime ); const switchTime = moment.utc( dtConfig.switchTime );
if ( this.timestamp < switchTime ) { if ( this.timestamp < switchTime ) {
return this.timestamp.utc().toISOString(); return this.timestamp.utc().toISOString();
} else { } else {
@ -65,7 +65,7 @@ CommentItem.prototype.getTimestampString = function () {
* @return {HeadingItem} Closest ancestor which is a HeadingItem * @return {HeadingItem} Closest ancestor which is a HeadingItem
*/ */
CommentItem.prototype.getHeading = function () { CommentItem.prototype.getHeading = function () {
var parent = this; let parent = this;
while ( parent && parent.type !== 'heading' ) { while ( parent && parent.type !== 'heading' ) {
parent = parent.parent; parent = parent.parent;
} }
@ -76,7 +76,7 @@ CommentItem.prototype.getHeading = function () {
* @return {HeadingItem|null} Closest heading that can be used for topic subscriptions * @return {HeadingItem|null} Closest heading that can be used for topic subscriptions
*/ */
CommentItem.prototype.getSubscribableHeading = function () { CommentItem.prototype.getSubscribableHeading = function () {
var heading = this.getHeading(); let heading = this.getHeading();
while ( heading && heading.type === 'heading' && !heading.isSubscribable() ) { while ( heading && heading.type === 'heading' && !heading.isSubscribable() ) {
heading = heading.parent; heading = heading.parent;
} }

View file

@ -22,11 +22,11 @@ function HeadingItem( range, headingLevel ) {
OO.inheritClass( HeadingItem, ThreadItem ); OO.inheritClass( HeadingItem, ThreadItem );
HeadingItem.prototype.getLinkableTitle = function () { HeadingItem.prototype.getLinkableTitle = function () {
var title = ''; let title = '';
// If this comment is in 0th section, there's no section title for the edit summary // If this comment is in 0th section, there's no section title for the edit summary
if ( !this.placeholderHeading ) { if ( !this.placeholderHeading ) {
var headline = this.range.startContainer; const headline = this.range.startContainer;
var id = headline.getAttribute( 'id' ); const id = headline.getAttribute( 'id' );
if ( id ) { if ( id ) {
// Replace underscores with spaces to undo Sanitizer::escapeIdInternal(). // Replace underscores with spaces to undo Sanitizer::escapeIdInternal().
// This assumes that $wgFragmentMode is [ 'html5', 'legacy' ] or [ 'html5' ], // This assumes that $wgFragmentMode is [ 'html5', 'legacy' ] or [ 'html5' ],

View file

@ -40,7 +40,7 @@ LedeSectionDialog.prototype.getSetupProcess = function ( data ) {
// Enable collapsible content (T323639), which is normally not handled on mobile (T111565). // Enable collapsible content (T323639), which is normally not handled on mobile (T111565).
// It's safe to do this twice if that changes (makeCollapsible() checks if each element was // It's safe to do this twice if that changes (makeCollapsible() checks if each element was
// already handled). Using the same approach as in 'mediawiki.page.ready' in MediaWiki core. // already handled). Using the same approach as in 'mediawiki.page.ready' in MediaWiki core.
var $collapsible = this.contentLayout.$element.find( '.mw-collapsible' ); const $collapsible = this.contentLayout.$element.find( '.mw-collapsible' );
if ( $collapsible.length ) { if ( $collapsible.length ) {
// This module is also preloaded in PageHooks to avoid visual jumps when things collapse. // This module is also preloaded in PageHooks to avoid visual jumps when things collapse.
mw.loader.using( 'jquery.makeCollapsible' ).then( () => { mw.loader.using( 'jquery.makeCollapsible' ).then( () => {

View file

@ -67,12 +67,12 @@ NewTopicController.static.suppressedEditNotices = [
* @inheritdoc * @inheritdoc
*/ */
NewTopicController.prototype.setup = function ( mode ) { NewTopicController.prototype.setup = function ( mode ) {
var rootScrollable = OO.ui.Element.static.getRootScrollableElement( document.body ); const rootScrollable = OO.ui.Element.static.getRootScrollableElement( document.body );
// Insert directly after the page content on already existing pages // Insert directly after the page content on already existing pages
// (.mw-parser-output is missing on non-existent pages) // (.mw-parser-output is missing on non-existent pages)
var $parserOutput = this.$pageContainer.find( '.mw-parser-output' ).first(); const $parserOutput = this.$pageContainer.find( '.mw-parser-output' ).first();
var $mobileAddTopicWrapper = this.$pageContainer.find( '.ext-discussiontools-init-new-topic' ); const $mobileAddTopicWrapper = this.$pageContainer.find( '.ext-discussiontools-init-new-topic' );
if ( $parserOutput.length ) { if ( $parserOutput.length ) {
$parserOutput.after( this.container.$element ); $parserOutput.after( this.container.$element );
} else if ( $mobileAddTopicWrapper.length ) { } else if ( $mobileAddTopicWrapper.length ) {
@ -109,19 +109,19 @@ NewTopicController.prototype.setupReplyWidget = function ( replyWidget, data ) {
NewTopicController.super.prototype.setupReplyWidget.apply( this, arguments ); NewTopicController.super.prototype.setupReplyWidget.apply( this, arguments );
this.$notices.empty(); this.$notices.empty();
for ( var noticeName in this.replyWidget.commentDetails.notices ) { for ( const noticeName in this.replyWidget.commentDetails.notices ) {
if ( this.constructor.static.suppressedEditNotices.indexOf( noticeName ) !== -1 ) { if ( this.constructor.static.suppressedEditNotices.indexOf( noticeName ) !== -1 ) {
continue; continue;
} }
var noticeItem = this.replyWidget.commentDetails.notices[ noticeName ]; const noticeItem = this.replyWidget.commentDetails.notices[ noticeName ];
var $noticeElement = $( '<div>' ) const $noticeElement = $( '<div>' )
.addClass( 'ext-discussiontools-ui-replyWidget-notice' ) .addClass( 'ext-discussiontools-ui-replyWidget-notice' )
.html( typeof noticeItem === 'string' ? noticeItem : noticeItem.message ); .html( typeof noticeItem === 'string' ? noticeItem : noticeItem.message );
this.$notices.append( $noticeElement ); this.$notices.append( $noticeElement );
} }
mw.hook( 'wikipage.content' ).fire( this.$notices ); mw.hook( 'wikipage.content' ).fire( this.$notices );
var title = this.replyWidget.storage.get( 'title' ); const title = this.replyWidget.storage.get( 'title' );
if ( title && !this.sectionTitle.getValue() ) { if ( title && !this.sectionTitle.getValue() ) {
// Don't overwrite if the user has already typed something in while the widget was loading. // 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, // TODO This should happen immediately rather than waiting for the reply widget to load,
@ -130,7 +130,7 @@ NewTopicController.prototype.setupReplyWidget = function ( replyWidget, data ) {
this.prevTitleText = title; this.prevTitleText = title;
if ( this.replyWidget.storage.get( 'summary' ) === null ) { if ( this.replyWidget.storage.get( 'summary' ) === null ) {
var generatedSummary = this.generateSummary( title ); const generatedSummary = this.generateSummary( title );
this.replyWidget.editSummaryInput.setValue( generatedSummary ); this.replyWidget.editSummaryInput.setValue( generatedSummary );
} }
} }
@ -180,8 +180,8 @@ NewTopicController.prototype.onReplyWidgetClearStorage = function () {
NewTopicController.prototype.storeEditSummary = function () { NewTopicController.prototype.storeEditSummary = function () {
if ( this.replyWidget ) { if ( this.replyWidget ) {
var currentSummary = this.replyWidget.editSummaryInput.getValue(); const currentSummary = this.replyWidget.editSummaryInput.getValue();
var generatedSummary = this.generateSummary( this.sectionTitle.getValue() ); const generatedSummary = this.generateSummary( this.sectionTitle.getValue() );
if ( currentSummary === generatedSummary ) { if ( currentSummary === generatedSummary ) {
// Do not store generated summaries (T315730) // Do not store generated summaries (T315730)
this.replyWidget.storage.remove( 'summary' ); this.replyWidget.storage.remove( 'summary' );
@ -202,7 +202,7 @@ NewTopicController.prototype.onReplyWidgetTeardown = function ( abandoned ) {
this.container.$element.detach(); this.container.$element.detach();
if ( mw.config.get( 'wgDiscussionToolsStartNewTopicTool' ) ) { if ( mw.config.get( 'wgDiscussionToolsStartNewTopicTool' ) ) {
var url = new URL( location.href ); const url = new URL( location.href );
url.searchParams.delete( 'action' ); url.searchParams.delete( 'action' );
url.searchParams.delete( 'veaction' ); url.searchParams.delete( 'veaction' );
url.searchParams.delete( 'section' ); url.searchParams.delete( 'section' );
@ -244,11 +244,11 @@ NewTopicController.prototype.getUnsupportedNodeSelectors = function () {
* @inheritdoc * @inheritdoc
*/ */
NewTopicController.prototype.getApiQuery = function ( pageName, checkboxes ) { NewTopicController.prototype.getApiQuery = function ( pageName, checkboxes ) {
var data = NewTopicController.super.prototype.getApiQuery.call( this, pageName, checkboxes ); let data = NewTopicController.super.prototype.getApiQuery.call( this, pageName, checkboxes );
// Rebuild the tags array and remove the reply tag // Rebuild the tags array and remove the reply tag
var tags = ( data.dttags || '' ).split( ',' ); const tags = ( data.dttags || '' ).split( ',' );
var replyTag = tags.indexOf( 'discussiontools-reply' ); const replyTag = tags.indexOf( 'discussiontools-reply' );
if ( replyTag !== -1 ) { if ( replyTag !== -1 ) {
tags.splice( replyTag, 1 ); tags.splice( replyTag, 1 );
} }
@ -290,16 +290,16 @@ NewTopicController.prototype.generateSummary = function ( titleText ) {
* @private * @private
*/ */
NewTopicController.prototype.onSectionTitleChange = function () { NewTopicController.prototype.onSectionTitleChange = function () {
var titleText = this.sectionTitle.getValue(); const titleText = this.sectionTitle.getValue();
var prevTitleText = this.prevTitleText; const prevTitleText = this.prevTitleText;
if ( prevTitleText !== titleText ) { if ( prevTitleText !== titleText ) {
this.replyWidget.storage.set( 'title', titleText ); this.replyWidget.storage.set( 'title', titleText );
var generatedSummary = this.generateSummary( titleText ); const generatedSummary = this.generateSummary( titleText );
var generatedPrevSummary = this.generateSummary( prevTitleText ); const generatedPrevSummary = this.generateSummary( prevTitleText );
var currentSummary = this.replyWidget.editSummaryInput.getValue(); const currentSummary = this.replyWidget.editSummaryInput.getValue();
// Fill in edit summary if it was not modified by the user yet // Fill in edit summary if it was not modified by the user yet
if ( currentSummary === generatedPrevSummary ) { if ( currentSummary === generatedPrevSummary ) {
@ -318,13 +318,13 @@ NewTopicController.prototype.onSectionTitleChange = function () {
* @private * @private
*/ */
NewTopicController.prototype.onBodyFocus = function () { NewTopicController.prototype.onBodyFocus = function () {
var offsetBefore = this.replyWidget.$element.offset().top; const offsetBefore = this.replyWidget.$element.offset().top;
var rootScrollable = OO.ui.Element.static.getRootScrollableElement( document.body ); const rootScrollable = OO.ui.Element.static.getRootScrollableElement( document.body );
var scrollBefore = rootScrollable.scrollTop; const scrollBefore = rootScrollable.scrollTop;
this.checkSectionTitleValidity(); this.checkSectionTitleValidity();
var offsetChange = this.replyWidget.$element.offset().top - offsetBefore; const offsetChange = this.replyWidget.$element.offset().top - offsetBefore;
// Ensure the rest of the widget doesn't move when the validation // Ensure the rest of the widget doesn't move when the validation
// message is triggered by a focus. (T275923) // message is triggered by a focus. (T275923)
// Browsers sometimes also scroll in response to focus events, // Browsers sometimes also scroll in response to focus events,

View file

@ -44,7 +44,7 @@ Parser.prototype.parse = function ( rootNode, title ) {
this.rootNode = rootNode; this.rootNode = rootNode;
this.title = title; this.title = title;
var result = this.buildThreadItems(); const result = this.buildThreadItems();
this.buildThreads( result ); this.buildThreads( result );
this.computeIdsAndNames( result ); this.computeIdsAndNames( result );
@ -90,12 +90,12 @@ Parser.prototype.getTimestampRegexp = function ( contLangVariant, format, digits
return '(' + array.map( mw.util.escapeRegExp ).join( '|' ) + ')'; return '(' + array.map( mw.util.escapeRegExp ).join( '|' ) + ')';
} }
var s = ''; let s = '';
var raw = false; let raw = false;
// Adapted from Language::sprintfDate() // Adapted from Language::sprintfDate()
for ( var p = 0; p < format.length; p++ ) { for ( let p = 0; p < format.length; p++ ) {
var num = false; let num = false;
var code = format[ p ]; let code = format[ p ];
if ( code === 'x' && p < format.length - 1 ) { if ( code === 'x' && p < format.length - 1 ) {
code += format[ ++p ]; code += format[ ++p ];
} }
@ -182,7 +182,7 @@ Parser.prototype.getTimestampRegexp = function ( contLangVariant, format, digits
case '"': case '"':
// Quoted literal // Quoted literal
if ( p < format.length - 1 ) { if ( p < format.length - 1 ) {
var endQuote = format.indexOf( '"', p + 1 ); const endQuote = format.indexOf( '"', p + 1 );
if ( endQuote === -1 ) { if ( endQuote === -1 ) {
// No terminating quote, assume literal " // No terminating quote, assume literal "
s += '"'; s += '"';
@ -213,10 +213,10 @@ Parser.prototype.getTimestampRegexp = function ( contLangVariant, format, digits
s += '[\\u200E\\u200F]?'; s += '[\\u200E\\u200F]?';
} }
var tzRegexp = regexpAlternateGroup( Object.keys( tzAbbrs ) ); const tzRegexp = regexpAlternateGroup( Object.keys( tzAbbrs ) );
// Hard-coded parentheses and space like in Parser::pstPass2 // Hard-coded parentheses and space like in Parser::pstPass2
// Ignore some invisible Unicode characters that often sneak into copy-pasted timestamps (T245784) // Ignore some invisible Unicode characters that often sneak into copy-pasted timestamps (T245784)
var regexp = s + ' [\\u200E\\u200F]?\\(' + tzRegexp + '\\)'; const regexp = s + ' [\\u200E\\u200F]?\\(' + tzRegexp + '\\)';
return regexp; return regexp;
}; };
@ -235,9 +235,9 @@ Parser.prototype.getTimestampRegexp = function ( contLangVariant, format, digits
* @return {TimestampParser} Timestamp parser function * @return {TimestampParser} Timestamp parser function
*/ */
Parser.prototype.getTimestampParser = function ( contLangVariant, format, digits, localTimezone, tzAbbrs ) { Parser.prototype.getTimestampParser = function ( contLangVariant, format, digits, localTimezone, tzAbbrs ) {
var matchingGroups = []; const matchingGroups = [];
for ( var p = 0; p < format.length; p++ ) { for ( let p = 0; p < format.length; p++ ) {
var code = format[ p ]; let code = format[ p ];
if ( code === 'x' && p < format.length - 1 ) { if ( code === 'x' && p < format.length - 1 ) {
code += format[ ++p ]; code += format[ ++p ];
} }
@ -275,7 +275,7 @@ Parser.prototype.getTimestampParser = function ( contLangVariant, format, digits
case '"': case '"':
// Quoted literal // Quoted literal
if ( p < format.length - 1 ) { if ( p < format.length - 1 ) {
var endQuote = format.indexOf( '"', p + 1 ); const endQuote = format.indexOf( '"', p + 1 );
if ( endQuote !== -1 ) { if ( endQuote !== -1 ) {
p = endQuote; p = endQuote;
} }
@ -312,16 +312,16 @@ Parser.prototype.getTimestampParser = function ( contLangVariant, format, digits
* - {string|null} warning Warning message if the input wasn't correctly formed * - {string|null} warning Warning message if the input wasn't correctly formed
*/ */
return ( match ) => { return ( match ) => {
var let
year = 0, year = 0,
monthIdx = 0, monthIdx = 0,
day = 0, day = 0,
hour = 0, hour = 0,
minute = 0; minute = 0;
for ( var i = 0; i < matchingGroups.length; i++ ) { for ( let i = 0; i < matchingGroups.length; i++ ) {
var code2 = matchingGroups[ i ]; const code2 = matchingGroups[ i ];
var text = match[ i + 1 ]; const text = match[ i + 1 ];
switch ( code2 ) { switch ( code2 ) {
case 'xg': case 'xg':
@ -378,11 +378,11 @@ Parser.prototype.getTimestampParser = function ( contLangVariant, format, digits
} }
} }
// The last matching group is the timezone abbreviation // The last matching group is the timezone abbreviation
var tzAbbr = tzAbbrs[ match[ match.length - 1 ] ]; const tzAbbr = tzAbbrs[ match[ match.length - 1 ] ];
// Most of the time, the timezone abbreviation is not necessary to parse the date, since we // Most of the time, the timezone abbreviation is not necessary to parse the date, since we
// can assume all times are in the wiki's local timezone. // can assume all times are in the wiki's local timezone.
var date = moment.tz( [ year, monthIdx, day, hour, minute ], localTimezone ); let date = moment.tz( [ year, monthIdx, day, hour, minute ], localTimezone );
// But during the "fall back" at the end of DST, some times will happen twice. Per the docs, // But during the "fall back" at the end of DST, some times will happen twice. Per the docs,
// "Moment Timezone handles this by always using the earlier instance of a duplicated hour." // "Moment Timezone handles this by always using the earlier instance of a duplicated hour."
@ -390,7 +390,7 @@ Parser.prototype.getTimestampParser = function ( contLangVariant, format, digits
// Since the timezone abbreviation disambiguates the DST/non-DST times, we can detect when // Since the timezone abbreviation disambiguates the DST/non-DST times, we can detect when
// that behavior was incorrect... // that behavior was incorrect...
var dateWarning = null; let dateWarning = null;
if ( date.zoneAbbr() !== tzAbbr ) { if ( date.zoneAbbr() !== tzAbbr ) {
// ...and force the correct parsing. I can't find proper documentation for this feature, // ...and force the correct parsing. I can't find proper documentation for this feature,
// but this pull request explains it: https://github.com/moment/moment-timezone/pull/101 // but this pull request explains it: https://github.com/moment/moment-timezone/pull/101
@ -466,7 +466,7 @@ Parser.prototype.getLocalTimestampParsers = function () {
*/ */
function acceptOnlyNodesAllowingComments( node ) { function acceptOnlyNodesAllowingComments( node ) {
if ( node instanceof HTMLElement ) { if ( node instanceof HTMLElement ) {
var tagName = node.tagName.toLowerCase(); const tagName = node.tagName.toLowerCase();
// The table of contents has a heading that gets erroneously detected as a section // The table of contents has a heading that gets erroneously detected as a section
if ( node.id === 'toc' ) { if ( node.id === 'toc' ) {
return NodeFilter.FILTER_REJECT; return NodeFilter.FILTER_REJECT;
@ -493,7 +493,7 @@ function acceptOnlyNodesAllowingComments( node ) {
return NodeFilter.FILTER_REJECT; return NodeFilter.FILTER_REJECT;
} }
} }
var parentNode = node.parentNode; const parentNode = node.parentNode;
// Don't detect comments within headings (but don't reject the headings themselves) // Don't detect comments within headings (but don't reject the headings themselves)
if ( parentNode instanceof HTMLElement && parentNode.tagName.match( /^h([1-6])$/i ) ) { if ( parentNode instanceof HTMLElement && parentNode.tagName.match( /^h([1-6])$/i ) ) {
return NodeFilter.FILTER_REJECT; return NodeFilter.FILTER_REJECT;
@ -515,7 +515,7 @@ function acceptOnlyNodesAllowingComments( node ) {
* - {Object} range Range-like object covering the timestamp * - {Object} range Range-like object covering the timestamp
*/ */
Parser.prototype.findTimestamp = function ( node, timestampRegexps ) { Parser.prototype.findTimestamp = function ( node, timestampRegexps ) {
var matchData, i, let matchData, i,
nodeText = '', nodeText = '',
offset = 0, offset = 0,
// Searched nodes (reverse order) // Searched nodes (reverse order)
@ -562,12 +562,12 @@ Parser.prototype.findTimestamp = function ( node, timestampRegexps ) {
if ( matchData ) { if ( matchData ) {
var timestampLength = matchData[ 0 ].length; var timestampLength = matchData[ 0 ].length;
// Bytes at the end of the last node which aren't part of the match // Bytes at the end of the last node which aren't part of the match
var tailLength = nodeText.length - timestampLength - matchData.index; const tailLength = nodeText.length - timestampLength - matchData.index;
// We are moving right to left, but we start to the right of the end of // We are moving right to left, but we start to the right of the end of
// the timestamp if there is trailing garbage, so that is a negative offset. // the timestamp if there is trailing garbage, so that is a negative offset.
var count = -tailLength; var count = -tailLength;
var endContainer = nodes[ 0 ]; const endContainer = nodes[ 0 ];
var endOffset = endContainer.nodeValue.length - tailLength; const endOffset = endContainer.nodeValue.length - tailLength;
var startContainer, startOffset; var startContainer, startOffset;
// eslint-disable-next-line no-loop-func // eslint-disable-next-line no-loop-func
@ -584,7 +584,7 @@ Parser.prototype.findTimestamp = function ( node, timestampRegexps ) {
return false; return false;
} ); } );
var range = { const range = {
startContainer: startContainer, startContainer: startContainer,
startOffset: startOffset, startOffset: startOffset,
endContainer: endContainer, endContainer: endContainer,
@ -613,12 +613,12 @@ Parser.prototype.findTimestamp = function ( node, timestampRegexps ) {
* - {string|null} displayName Display name (link text if link target was in the user namespace) * - {string|null} displayName Display name (link text if link target was in the user namespace)
*/ */
Parser.prototype.getUsernameFromLink = function ( link ) { Parser.prototype.getUsernameFromLink = function ( link ) {
var title; let title;
// Selflink: use title of current page // Selflink: use title of current page
if ( link.classList.contains( 'mw-selflink' ) ) { if ( link.classList.contains( 'mw-selflink' ) ) {
title = this.title; title = this.title;
} else { } else {
var titleString = utils.getTitleFromUrl( link.href ) || ''; const titleString = utils.getTitleFromUrl( link.href ) || '';
// Performance optimization, skip strings that obviously don't contain a namespace // Performance optimization, skip strings that obviously don't contain a namespace
if ( !titleString || titleString.indexOf( ':' ) === -1 ) { if ( !titleString || titleString.indexOf( ':' ) === -1 ) {
return null; return null;
@ -629,11 +629,11 @@ Parser.prototype.getUsernameFromLink = function ( link ) {
return null; return null;
} }
var username; let username;
var displayName = null; let displayName = null;
var namespaceId = title.getNamespaceId(); const namespaceId = title.getNamespaceId();
var mainText = title.getMainText(); const mainText = title.getMainText();
var namespaceIds = mw.config.get( 'wgNamespaceIds' ); const namespaceIds = mw.config.get( 'wgNamespaceIds' );
if ( if (
namespaceId === namespaceIds.user || namespaceId === namespaceIds.user ||
@ -645,17 +645,17 @@ Parser.prototype.getUsernameFromLink = function ( link ) {
} }
if ( namespaceId === namespaceIds.user ) { if ( namespaceId === namespaceIds.user ) {
// Use regex trim for consistency with PHP implementation // Use regex trim for consistency with PHP implementation
var text = link.textContent.replace( /^[\s]+/, '' ).replace( /[\s]+$/, '' ); const text = link.textContent.replace( /^[\s]+/, '' ).replace( /[\s]+$/, '' );
// Record the display name if it has been customised beyond changing case // Record the display name if it has been customised beyond changing case
if ( text && text.toLowerCase() !== username.toLowerCase() ) { if ( text && text.toLowerCase() !== username.toLowerCase() ) {
displayName = text; displayName = text;
} }
} }
} else if ( namespaceId === namespaceIds.special ) { } else if ( namespaceId === namespaceIds.special ) {
var parts = mainText.split( '/' ); const parts = mainText.split( '/' );
if ( parts.length === 2 && parts[ 0 ] === this.data.specialContributionsName ) { if ( parts.length === 2 && parts[ 0 ] === this.data.specialContributionsName ) {
// Normalize the username: users may link to their contributions with an unnormalized name // Normalize the username: users may link to their contributions with an unnormalized name
var userpage = mw.Title.makeTitle( namespaceIds.user, parts[ 1 ] ); const userpage = mw.Title.makeTitle( namespaceIds.user, parts[ 1 ] );
if ( !userpage ) { if ( !userpage ) {
return null; return null;
} }
@ -692,10 +692,10 @@ Parser.prototype.getUsernameFromLink = function ( link ) {
* - {string|null} username Username, null for unsigned comments * - {string|null} username Username, null for unsigned comments
*/ */
Parser.prototype.findSignature = function ( timestampNode, until ) { Parser.prototype.findSignature = function ( timestampNode, until ) {
var sigUsername = null; let sigUsername = null;
var sigDisplayName = null; let sigDisplayName = null;
var length = 0; let length = 0;
var lastLinkNode = timestampNode; let lastLinkNode = timestampNode;
utils.linearWalkBackwards( utils.linearWalkBackwards(
timestampNode, timestampNode,
@ -724,7 +724,7 @@ Parser.prototype.findSignature = function ( timestampNode, until ) {
// Handle links nested in formatting elements. // Handle links nested in formatting elements.
if ( event === 'leave' && node.nodeType === Node.ELEMENT_NODE && node.tagName.toLowerCase() === 'a' ) { if ( event === 'leave' && node.nodeType === Node.ELEMENT_NODE && node.tagName.toLowerCase() === 'a' ) {
if ( !node.classList.contains( 'ext-discussiontools-init-timestamplink' ) ) { if ( !node.classList.contains( 'ext-discussiontools-init-timestamplink' ) ) {
var user = this.getUsernameFromLink( node ); const user = this.getUsernameFromLink( node );
if ( user ) { if ( user ) {
// Accept the first link to the user namespace, then only accept links to that user // Accept the first link to the user namespace, then only accept links to that user
if ( sigUsername === null ) { if ( sigUsername === null ) {
@ -744,13 +744,13 @@ Parser.prototype.findSignature = function ( timestampNode, until ) {
} }
); );
var range = { const range = {
startContainer: lastLinkNode.parentNode, startContainer: lastLinkNode.parentNode,
startOffset: utils.childIndexOf( lastLinkNode ), startOffset: utils.childIndexOf( lastLinkNode ),
endContainer: timestampNode.parentNode, endContainer: timestampNode.parentNode,
endOffset: utils.childIndexOf( timestampNode ) + 1 endOffset: utils.childIndexOf( timestampNode ) + 1
}; };
var nativeRange = ThreadItem.prototype.getRange.call( { range: range } ); const nativeRange = ThreadItem.prototype.getRange.call( { range: range } );
// Expand the range so that it covers sibling nodes. // Expand the range so that it covers sibling nodes.
// This will include any wrapping formatting elements as part of the signature. // This will include any wrapping formatting elements as part of the signature.
@ -761,7 +761,7 @@ Parser.prototype.findSignature = function ( timestampNode, until ) {
// "« Saper // dyskusja »" // "« Saper // dyskusja »"
// //
// TODO Not sure if this is actually good, might be better to just use the range... // TODO Not sure if this is actually good, might be better to just use the range...
var sigNodes = utils.getCoveredSiblings( nativeRange ).reverse(); const sigNodes = utils.getCoveredSiblings( nativeRange ).reverse();
return { return {
nodes: sigNodes, nodes: sigNodes,
@ -784,9 +784,9 @@ Parser.prototype.findSignature = function ( timestampNode, until ) {
* @return {Node} * @return {Node}
*/ */
Parser.prototype.nextInterestingLeafNode = function ( node ) { Parser.prototype.nextInterestingLeafNode = function ( node ) {
var rootNode = this.rootNode; const rootNode = this.rootNode;
var treeWalker = rootNode.ownerDocument.createTreeWalker( const treeWalker = rootNode.ownerDocument.createTreeWalker(
rootNode, rootNode,
// eslint-disable-next-line no-bitwise // eslint-disable-next-line no-bitwise
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT,
@ -827,14 +827,14 @@ Parser.prototype.nextInterestingLeafNode = function ( node ) {
* @return {Object} Range-like object * @return {Object} Range-like object
*/ */
function adjustSigRange( sigNodes, match, node ) { function adjustSigRange( sigNodes, match, node ) {
var firstSigNode = sigNodes[ sigNodes.length - 1 ]; const firstSigNode = sigNodes[ sigNodes.length - 1 ];
var lastSigNode = sigNodes[ 0 ]; const lastSigNode = sigNodes[ 0 ];
// TODO Document why this needs to be so complicated // TODO Document why this needs to be so complicated
var lastSigNodeOffset = lastSigNode === node ? const lastSigNodeOffset = lastSigNode === node ?
match.matchData.index + match.matchData[ 0 ].length - match.offset : match.matchData.index + match.matchData[ 0 ].length - match.offset :
utils.childIndexOf( lastSigNode ) + 1; utils.childIndexOf( lastSigNode ) + 1;
var sigRange = { const sigRange = {
startContainer: firstSigNode.parentNode, startContainer: firstSigNode.parentNode,
startOffset: utils.childIndexOf( firstSigNode ), startOffset: utils.childIndexOf( firstSigNode ),
endContainer: lastSigNode === node ? node : lastSigNode.parentNode, endContainer: lastSigNode === node ? node : lastSigNode.parentNode,
@ -847,13 +847,13 @@ function adjustSigRange( sigNodes, match, node ) {
* @return {ThreadItemSet} * @return {ThreadItemSet}
*/ */
Parser.prototype.buildThreadItems = function () { Parser.prototype.buildThreadItems = function () {
var result = new ThreadItemSet(); const result = new ThreadItemSet();
var const
dfParsers = this.getLocalTimestampParsers(), dfParsers = this.getLocalTimestampParsers(),
timestampRegexps = this.getLocalTimestampRegexps(); timestampRegexps = this.getLocalTimestampRegexps();
var treeWalker = this.rootNode.ownerDocument.createTreeWalker( const treeWalker = this.rootNode.ownerDocument.createTreeWalker(
this.rootNode, this.rootNode,
// eslint-disable-next-line no-bitwise // eslint-disable-next-line no-bitwise
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT,
@ -861,14 +861,14 @@ Parser.prototype.buildThreadItems = function () {
false false
); );
var curComment, range; let curComment, range;
var curCommentEnd = null; let curCommentEnd = null;
var node; let node;
while ( ( node = treeWalker.nextNode() ) ) { while ( ( node = treeWalker.nextNode() ) ) {
var match; var match;
if ( node.tagName && ( match = node.tagName.match( /^h([1-6])$/i ) ) ) { if ( node.tagName && ( match = node.tagName.match( /^h([1-6])$/i ) ) ) {
var headingNode = utils.getHeadlineNode( node ); const headingNode = utils.getHeadlineNode( node );
range = { range = {
startContainer: headingNode, startContainer: headingNode,
startOffset: 0, startOffset: 0,
@ -880,9 +880,9 @@ Parser.prototype.buildThreadItems = function () {
result.addThreadItem( curComment ); result.addThreadItem( curComment );
curCommentEnd = node; curCommentEnd = node;
} else if ( node.nodeType === Node.TEXT_NODE && ( match = this.findTimestamp( node, timestampRegexps ) ) ) { } else if ( node.nodeType === Node.TEXT_NODE && ( match = this.findTimestamp( node, timestampRegexps ) ) ) {
var warnings = []; const warnings = [];
var foundSignature = this.findSignature( node, curCommentEnd ); const foundSignature = this.findSignature( node, curCommentEnd );
var author = foundSignature.username; const author = foundSignature.username;
if ( !author ) { if ( !author ) {
// Ignore timestamps for which we couldn't find a signature. It's probably not a real // Ignore timestamps for which we couldn't find a signature. It's probably not a real
@ -897,7 +897,7 @@ Parser.prototype.buildThreadItems = function () {
timestampRanges.push( match.range ); timestampRanges.push( match.range );
// Everything from the last comment up to here is the next comment // Everything from the last comment up to here is the next comment
var startNode = this.nextInterestingLeafNode( curCommentEnd ); const startNode = this.nextInterestingLeafNode( curCommentEnd );
var endNode = foundSignature.nodes[ 0 ]; var endNode = foundSignature.nodes[ 0 ];
// Skip to the end of the "paragraph". This only looks at tag names and can be fooled by CSS, but // Skip to the end of the "paragraph". This only looks at tag names and can be fooled by CSS, but
@ -914,7 +914,7 @@ Parser.prototype.buildThreadItems = function () {
endNode, endNode,
// eslint-disable-next-line no-loop-func // eslint-disable-next-line no-loop-func
( event, n ) => { ( event, n ) => {
var match2, foundSignature2; let match2, foundSignature2;
if ( utils.isBlockElement( n ) || utils.isCommentSeparator( n ) ) { if ( utils.isBlockElement( n ) || utils.isCommentSeparator( n ) ) {
// Stop when entering or leaving a block node // Stop when entering or leaving a block node
return true; return true;
@ -940,7 +940,7 @@ Parser.prototype.buildThreadItems = function () {
} }
); );
var length = endNode.nodeType === Node.TEXT_NODE ? const length = endNode.nodeType === Node.TEXT_NODE ?
endNode.textContent.replace( /[\t\n\f\r ]+$/, '' ).length : endNode.textContent.replace( /[\t\n\f\r ]+$/, '' ).length :
endNode.childNodes.length; endNode.childNodes.length;
range = { range = {
@ -950,19 +950,19 @@ Parser.prototype.buildThreadItems = function () {
endOffset: length endOffset: length
}; };
var startLevel = utils.getIndentLevel( startNode, this.rootNode ) + 1; const startLevel = utils.getIndentLevel( startNode, this.rootNode ) + 1;
var endLevel = utils.getIndentLevel( node, this.rootNode ) + 1; const endLevel = utils.getIndentLevel( node, this.rootNode ) + 1;
if ( startLevel !== endLevel ) { if ( startLevel !== endLevel ) {
warnings.push( 'Comment starts and ends with different indentation' ); warnings.push( 'Comment starts and ends with different indentation' );
} }
// Should this use the indent level of `startNode` or `node`? // Should this use the indent level of `startNode` or `node`?
var level = Math.min( startLevel, endLevel ); const level = Math.min( startLevel, endLevel );
var parserResult = dfParsers[ match.parserIndex ]( match.matchData ); const parserResult = dfParsers[ match.parserIndex ]( match.matchData );
if ( !parserResult ) { if ( !parserResult ) {
continue; continue;
} }
var dateTime = parserResult.date; const dateTime = parserResult.date;
if ( parserResult.warning ) { if ( parserResult.warning ) {
warnings.push( parserResult.warning ); warnings.push( parserResult.warning );
} }
@ -989,7 +989,7 @@ Parser.prototype.buildThreadItems = function () {
endContainer: this.rootNode, endContainer: this.rootNode,
endOffset: 0 endOffset: 0
}; };
var fakeHeading = new HeadingItem( range, null ); const fakeHeading = new HeadingItem( range, null );
fakeHeading.rootNode = this.rootNode; fakeHeading.rootNode = this.rootNode;
result.addThreadItem( fakeHeading ); result.addThreadItem( fakeHeading );
} }
@ -1019,7 +1019,7 @@ Parser.prototype.truncateForId = function ( text ) {
* @return {string} * @return {string}
*/ */
Parser.prototype.computeId = function ( threadItem, previousItems ) { Parser.prototype.computeId = function ( threadItem, previousItems ) {
var id, headline; let id, headline;
if ( threadItem instanceof HeadingItem && threadItem.placeholderHeading ) { if ( threadItem instanceof HeadingItem && threadItem.placeholderHeading ) {
// The range points to the root note, using it like below results in silly values // The range points to the root note, using it like below results in silly values
@ -1035,7 +1035,7 @@ Parser.prototype.computeId = function ( threadItem, previousItems ) {
// If there would be multiple comments with the same ID (i.e. the user left multiple comments // If there would be multiple comments with the same ID (i.e. the user left multiple comments
// in one edit, or within a minute), append sequential numbers // in one edit, or within a minute), append sequential numbers
var threadItemParent = threadItem.parent; const threadItemParent = threadItem.parent;
if ( threadItemParent instanceof HeadingItem && !threadItemParent.placeholderHeading ) { if ( threadItemParent instanceof HeadingItem && !threadItemParent.placeholderHeading ) {
headline = threadItemParent.range.startContainer; headline = threadItemParent.range.startContainer;
id += '-' + this.truncateForId( headline.getAttribute( 'id' ) || '' ); id += '-' + this.truncateForId( headline.getAttribute( 'id' ) || '' );
@ -1048,7 +1048,7 @@ Parser.prototype.computeId = function ( threadItem, previousItems ) {
// (e.g. dozens of threads titled "question" on [[Wikipedia:Help desk]]: https://w.wiki/fbN), // (e.g. dozens of threads titled "question" on [[Wikipedia:Help desk]]: https://w.wiki/fbN),
// include the oldest timestamp in the thread (i.e. date the thread was started) in the // include the oldest timestamp in the thread (i.e. date the thread was started) in the
// heading ID. // heading ID.
var oldestComment = threadItem.getOldestReply(); const oldestComment = threadItem.getOldestReply();
if ( oldestComment ) { if ( oldestComment ) {
id += '-' + oldestComment.getTimestampString(); id += '-' + oldestComment.getTimestampString();
} }
@ -1058,7 +1058,7 @@ Parser.prototype.computeId = function ( threadItem, previousItems ) {
// Well, that's tough // Well, that's tough
threadItem.warnings.push( 'Duplicate comment ID' ); threadItem.warnings.push( 'Duplicate comment ID' );
// Finally, disambiguate by adding sequential numbers, to allow replying to both comments // Finally, disambiguate by adding sequential numbers, to allow replying to both comments
var number = 1; let number = 1;
while ( previousItems.findCommentById( id + '-' + number ) ) { while ( previousItems.findCommentById( id + '-' + number ) ) {
number++; number++;
} }
@ -1078,7 +1078,7 @@ Parser.prototype.computeId = function ( threadItem, previousItems ) {
* @return {string} * @return {string}
*/ */
Parser.prototype.computeName = function ( threadItem ) { Parser.prototype.computeName = function ( threadItem ) {
var name, mainComment; let name, mainComment;
if ( threadItem instanceof HeadingItem ) { if ( threadItem instanceof HeadingItem ) {
name = 'h-'; name = 'h-';
@ -1102,10 +1102,10 @@ Parser.prototype.computeName = function ( threadItem ) {
* @param {ThreadItemSet} result * @param {ThreadItemSet} result
*/ */
Parser.prototype.buildThreads = function ( result ) { Parser.prototype.buildThreads = function ( result ) {
var lastHeading = null; let lastHeading = null;
var replies = []; const replies = [];
var i, threadItem; let i, threadItem;
for ( i = 0; i < result.threadItems.length; i++ ) { for ( i = 0; i < result.threadItems.length; i++ ) {
threadItem = result.threadItems[ i ]; threadItem = result.threadItems[ i ];
@ -1122,7 +1122,7 @@ Parser.prototype.buildThreads = function ( result ) {
// New root (thread) // New root (thread)
// Attach as a sub-thread to preceding higher-level heading. // Attach as a sub-thread to preceding higher-level heading.
// Any replies will appear in the tree twice, under the main-thread and the sub-thread. // Any replies will appear in the tree twice, under the main-thread and the sub-thread.
var maybeParent = lastHeading; let maybeParent = lastHeading;
while ( maybeParent && maybeParent.headingLevel >= threadItem.headingLevel ) { while ( maybeParent && maybeParent.headingLevel >= threadItem.headingLevel ) {
maybeParent = maybeParent.parent; maybeParent = maybeParent.parent;
} }
@ -1153,14 +1153,14 @@ Parser.prototype.buildThreads = function ( result ) {
* @param {ThreadItemSet} result * @param {ThreadItemSet} result
*/ */
Parser.prototype.computeIdsAndNames = function ( result ) { Parser.prototype.computeIdsAndNames = function ( result ) {
var i, threadItem; let i, threadItem;
for ( i = 0; i < result.threadItems.length; i++ ) { for ( i = 0; i < result.threadItems.length; i++ ) {
threadItem = result.threadItems[ i ]; threadItem = result.threadItems[ i ];
var name = this.computeName( threadItem ); const name = this.computeName( threadItem );
threadItem.name = name; threadItem.name = name;
var id = this.computeId( threadItem, result ); const id = this.computeId( threadItem, result );
threadItem.id = id; threadItem.id = id;
result.updateIdAndNameMaps( threadItem ); result.updateIdAndNameMaps( threadItem );
@ -1172,17 +1172,17 @@ Parser.prototype.computeIdsAndNames = function ( result ) {
* @return {CommentItem|null} * @return {CommentItem|null}
*/ */
Parser.prototype.getThreadStartComment = function ( threadItem ) { Parser.prototype.getThreadStartComment = function ( threadItem ) {
var oldest = null; let oldest = null;
if ( threadItem instanceof CommentItem ) { if ( threadItem instanceof CommentItem ) {
oldest = threadItem; oldest = threadItem;
} }
// Check all replies. This can't just use the first comment because threads are often summarized // Check all replies. This can't just use the first comment because threads are often summarized
// at the top when the discussion is closed. // at the top when the discussion is closed.
for ( var i = 0; i < threadItem.replies.length; i++ ) { for ( let i = 0; i < threadItem.replies.length; i++ ) {
var comment = threadItem.replies[ i ]; const comment = threadItem.replies[ i ];
// Don't include sub-threads to avoid changing the ID when threads are "merged". // Don't include sub-threads to avoid changing the ID when threads are "merged".
if ( comment instanceof CommentItem ) { if ( comment instanceof CommentItem ) {
var oldestInReplies = this.getThreadStartComment( comment ); const oldestInReplies = this.getThreadStartComment( comment );
if ( !oldest || oldestInReplies.timestamp.isBefore( oldest.timestamp ) ) { if ( !oldest || oldestInReplies.timestamp.isBefore( oldest.timestamp ) ) {
oldest = oldestInReplies; oldest = oldestInReplies;
} }

View file

@ -7,7 +7,7 @@ var featuresEnabled = mw.config.get( 'wgDiscussionToolsFeaturesEnabled' ) || {};
function tryInfuse( $element ) { function tryInfuse( $element ) {
if ( $element.length ) { if ( $element.length ) {
var element = null; let element = null;
// $.data() might have already been cleared by jQuery if the elements were removed, ignore // $.data() might have already been cleared by jQuery if the elements were removed, ignore
// TODO: We should keep references to the OO.ui.ButtonWidget objects instead of infusing again, // TODO: We should keep references to the OO.ui.ButtonWidget objects instead of infusing again,
// which would avoid this issue too // which would avoid this issue too
@ -34,8 +34,8 @@ function ReplyLinksController( $pageContainer ) {
this.$replyLinkSets = $pageContainer.find( '.ext-discussiontools-init-replylink-buttons[ data-mw-thread-id ]:not( :empty )' ); this.$replyLinkSets = $pageContainer.find( '.ext-discussiontools-init-replylink-buttons[ data-mw-thread-id ]:not( :empty )' );
this.$replyLinkSets.each( ( i, replyLinkContainer ) => { this.$replyLinkSets.each( ( i, replyLinkContainer ) => {
var replyButton = tryInfuse( $( replyLinkContainer ).find( '.ext-discussiontools-init-replybutton' ) ); const replyButton = tryInfuse( $( replyLinkContainer ).find( '.ext-discussiontools-init-replybutton' ) );
var $replyLink = $( replyLinkContainer ).find( '.ext-discussiontools-init-replylink-reply' ); const $replyLink = $( replyLinkContainer ).find( '.ext-discussiontools-init-replylink-reply' );
$replyLink.on( 'click keypress', this.onReplyLinkClickHandler ); $replyLink.on( 'click keypress', this.onReplyLinkClickHandler );
if ( replyButton ) { if ( replyButton ) {
replyButton.on( 'click', this.onReplyButtonClickHandler, [ replyButton ] ); replyButton.on( 'click', this.onReplyButtonClickHandler, [ replyButton ] );
@ -49,7 +49,7 @@ function ReplyLinksController( $pageContainer ) {
// "Add topic" link in the skin interface // "Add topic" link in the skin interface
if ( featuresEnabled.newtopictool ) { if ( featuresEnabled.newtopictool ) {
// eslint-disable-next-line no-jquery/no-global-selector // eslint-disable-next-line no-jquery/no-global-selector
var $addSectionTab = $( '#ca-addsection' ); const $addSectionTab = $( '#ca-addsection' );
if ( $addSectionTab.length ) { if ( $addSectionTab.length ) {
this.$addSectionLink = $addSectionTab.find( 'a' ); this.$addSectionLink = $addSectionTab.find( 'a' );
this.$addSectionLink.on( 'click keypress', this.onAddSectionLinkClickHandler ); this.$addSectionLink.on( 'click keypress', this.onAddSectionLinkClickHandler );
@ -85,7 +85,7 @@ ReplyLinksController.prototype.onReplyLinkClick = function ( e ) {
// Browser plugins (such as Google Translate) may add extra tags inside // Browser plugins (such as Google Translate) may add extra tags inside
// the link, so find the containing link tag with the data we need. // the link, so find the containing link tag with the data we need.
var $linkSet = $( e.target ).closest( '[data-mw-thread-id]' ); const $linkSet = $( e.target ).closest( '[data-mw-thread-id]' );
if ( !$linkSet.length ) { if ( !$linkSet.length ) {
return; return;
} }
@ -93,7 +93,7 @@ ReplyLinksController.prototype.onReplyLinkClick = function ( e ) {
}; };
ReplyLinksController.prototype.onReplyButtonClick = function ( button ) { ReplyLinksController.prototype.onReplyButtonClick = function ( button ) {
var $linkSet = button.$element.closest( '[data-mw-thread-id]' ); const $linkSet = button.$element.closest( '[data-mw-thread-id]' );
this.emit( 'link-click', $linkSet.data( 'mw-thread-id' ), $linkSet ); this.emit( 'link-click', $linkSet.data( 'mw-thread-id' ), $linkSet );
}; };
@ -117,12 +117,12 @@ ReplyLinksController.prototype.onAnyLinkClick = function ( e ) {
} }
// Check query parameters to see if this is really a new topic link // Check query parameters to see if this is really a new topic link
var href = e.currentTarget.href; const href = e.currentTarget.href;
if ( !href ) { if ( !href ) {
return; return;
} }
var data = this.parseNewTopicLink( href ); const data = this.parseNewTopicLink( href );
if ( !data ) { if ( !data ) {
return; return;
} }
@ -142,9 +142,9 @@ ReplyLinksController.prototype.onAnyLinkClick = function ( e ) {
* @return {Object|null} `null` if not a new topic link, parameters otherwise * @return {Object|null} `null` if not a new topic link, parameters otherwise
*/ */
ReplyLinksController.prototype.parseNewTopicLink = function ( href ) { ReplyLinksController.prototype.parseNewTopicLink = function ( href ) {
var searchParams = new URL( href ).searchParams; const searchParams = new URL( href ).searchParams;
var title = mw.Title.newFromText( utils.getTitleFromUrl( href ) || '' ); let title = mw.Title.newFromText( utils.getTitleFromUrl( href ) || '' );
if ( !title ) { if ( !title ) {
return null; return null;
} }
@ -156,7 +156,7 @@ ReplyLinksController.prototype.parseNewTopicLink = function ( href ) {
title.getMainText().split( '/' )[ 0 ] === parserData.specialNewSectionName title.getMainText().split( '/' )[ 0 ] === parserData.specialNewSectionName
) { ) {
// Get the real title from the subpage parameter // Get the real title from the subpage parameter
var param = title.getMainText().slice( parserData.specialNewSectionName.length + 1 ); const param = title.getMainText().slice( parserData.specialNewSectionName.length + 1 );
title = mw.Title.newFromText( param ); title = mw.Title.newFromText( param );
if ( !title ) { if ( !title ) {
return null; return null;
@ -181,7 +181,7 @@ ReplyLinksController.prototype.parseNewTopicLink = function ( href ) {
return null; return null;
} }
var data = {}; const data = {};
if ( searchParams.get( 'editintro' ) ) { if ( searchParams.get( 'editintro' ) ) {
data.editintro = searchParams.get( 'editintro' ); data.editintro = searchParams.get( 'editintro' );
} }
@ -221,7 +221,7 @@ ReplyLinksController.prototype.isActivationEvent = function ( e ) {
ReplyLinksController.prototype.focusLink = function ( $linkSet ) { ReplyLinksController.prototype.focusLink = function ( $linkSet ) {
if ( $linkSet.is( this.$replyLinkSets ) ) { if ( $linkSet.is( this.$replyLinkSets ) ) {
var button = tryInfuse( $linkSet.find( '.ext-discussiontools-init-replybutton' ) ); const button = tryInfuse( $linkSet.find( '.ext-discussiontools-init-replybutton' ) );
// Focus whichever is visible, the link or the button // Focus whichever is visible, the link or the button
if ( button ) { if ( button ) {
button.focus(); button.focus();
@ -233,8 +233,8 @@ ReplyLinksController.prototype.focusLink = function ( $linkSet ) {
ReplyLinksController.prototype.setActiveLink = function ( $linkSet ) { ReplyLinksController.prototype.setActiveLink = function ( $linkSet ) {
this.$activeLink = $linkSet; this.$activeLink = $linkSet;
var isNewTopic = false; let isNewTopic = false;
var activeButton; let activeButton;
if ( this.$activeLink.is( this.$replyLinkSets ) ) { if ( this.$activeLink.is( this.$replyLinkSets ) ) {
this.$activeLink.addClass( 'ext-discussiontools-init-replylink-active' ); this.$activeLink.addClass( 'ext-discussiontools-init-replylink-active' );
activeButton = tryInfuse( this.$activeLink.find( '.ext-discussiontools-init-replybutton' ) ); activeButton = tryInfuse( this.$activeLink.find( '.ext-discussiontools-init-replybutton' ) );
@ -248,8 +248,8 @@ ReplyLinksController.prototype.setActiveLink = function ( $linkSet ) {
$( '#ca-view' ).removeClass( 'selected' ); $( '#ca-view' ).removeClass( 'selected' );
} }
var title = mw.Title.newFromText( mw.config.get( 'wgRelevantPageName' ) ); const title = mw.Title.newFromText( mw.config.get( 'wgRelevantPageName' ) );
var pageTitleMsg = mw.message( 'pagetitle', const pageTitleMsg = mw.message( 'pagetitle',
mw.msg( mw.msg(
isNewTopic ? isNewTopic ?
'discussiontools-pagetitle-newtopic' : 'discussiontools-pagetitle-newtopic' :
@ -269,8 +269,8 @@ ReplyLinksController.prototype.setActiveLink = function ( $linkSet ) {
$( document.body ).addClass( 'ext-discussiontools-init-replylink-open' ); $( document.body ).addClass( 'ext-discussiontools-init-replylink-open' );
this.$replyLinkSets.each( ( i, replyLinkContainer ) => { this.$replyLinkSets.each( ( i, replyLinkContainer ) => {
var replyButton = tryInfuse( $( replyLinkContainer ).find( '.ext-discussiontools-init-replybutton' ) ); const replyButton = tryInfuse( $( replyLinkContainer ).find( '.ext-discussiontools-init-replybutton' ) );
var $replyLink = $( replyLinkContainer ).find( '.ext-discussiontools-init-replylink-reply' ); const $replyLink = $( replyLinkContainer ).find( '.ext-discussiontools-init-replylink-reply' );
$replyLink.attr( 'tabindex', -1 ); $replyLink.attr( 'tabindex', -1 );
if ( !replyButton ) { if ( !replyButton ) {
return; return;
@ -289,7 +289,7 @@ ReplyLinksController.prototype.setActiveLink = function ( $linkSet ) {
}; };
ReplyLinksController.prototype.clearActiveLink = function () { ReplyLinksController.prototype.clearActiveLink = function () {
var activeButton; let activeButton;
if ( this.$activeLink.is( this.$replyLinkSets ) ) { if ( this.$activeLink.is( this.$replyLinkSets ) ) {
this.$activeLink.removeClass( 'ext-discussiontools-init-replylink-active' ); this.$activeLink.removeClass( 'ext-discussiontools-init-replylink-active' );
activeButton = tryInfuse( this.$activeLink.find( '.ext-discussiontools-init-replybutton' ) ); activeButton = tryInfuse( this.$activeLink.find( '.ext-discussiontools-init-replybutton' ) );
@ -308,9 +308,9 @@ ReplyLinksController.prototype.clearActiveLink = function () {
$( document.body ).removeClass( 'ext-discussiontools-init-replylink-open' ); $( document.body ).removeClass( 'ext-discussiontools-init-replylink-open' );
this.$replyLinkSets.each( ( i, replyLinkContainer ) => { this.$replyLinkSets.each( ( i, replyLinkContainer ) => {
var $replyLink = $( replyLinkContainer ).find( '.ext-discussiontools-init-replylink-reply' ); const $replyLink = $( replyLinkContainer ).find( '.ext-discussiontools-init-replylink-reply' );
$replyLink.attr( 'tabindex', 0 ); $replyLink.attr( 'tabindex', 0 );
var replyButton = tryInfuse( $( replyLinkContainer ).find( '.ext-discussiontools-init-replybutton' ) ); const replyButton = tryInfuse( $( replyLinkContainer ).find( '.ext-discussiontools-init-replybutton' ) );
if ( !replyButton ) { if ( !replyButton ) {
return; return;
} }
@ -336,11 +336,11 @@ ReplyLinksController.prototype.teardown = function () {
} }
this.$replyLinkSets.each( ( i, replyLinkContainer ) => { this.$replyLinkSets.each( ( i, replyLinkContainer ) => {
var replyButton = tryInfuse( $( replyLinkContainer ).find( '.ext-discussiontools-init-replybutton' ) ); const replyButton = tryInfuse( $( replyLinkContainer ).find( '.ext-discussiontools-init-replybutton' ) );
if ( replyButton ) { if ( replyButton ) {
replyButton.off( 'click', this.onReplyButtonClickHandler ); replyButton.off( 'click', this.onReplyButtonClickHandler );
} }
var $replyLink = $( this ).find( '.ext-discussiontools-init-replylink-reply' ); const $replyLink = $( this ).find( '.ext-discussiontools-init-replylink-reply' );
$replyLink.off( 'click keypress', this.onReplyLinkClickHandler ); $replyLink.off( 'click keypress', this.onReplyLinkClickHandler );
} ); } );

View file

@ -61,9 +61,9 @@ ThreadItem.static.newFromJSON = function ( json, rootNode ) {
// by an older version of our PHP code. Code below must be able to handle that. // by an older version of our PHP code. Code below must be able to handle that.
// See ThreadItem::jsonSerialize() in PHP. // See ThreadItem::jsonSerialize() in PHP.
var hash = typeof json === 'string' ? JSON.parse( json ) : json; const hash = typeof json === 'string' ? JSON.parse( json ) : json;
var item; let item;
switch ( hash.type ) { switch ( hash.type ) {
case 'comment': case 'comment':
// Late require to avoid circular dependency // Late require to avoid circular dependency
@ -102,9 +102,9 @@ ThreadItem.static.newFromJSON = function ( json, rootNode ) {
item.rootNode = rootNode; item.rootNode = rootNode;
var idEscaped = $.escapeSelector( item.id ); const idEscaped = $.escapeSelector( item.id );
var startMarker = document.getElementById( item.id ); const startMarker = document.getElementById( item.id );
var endMarker = document.querySelector( '[data-mw-comment-end="' + idEscaped + '"]' ); const endMarker = document.querySelector( '[data-mw-comment-end="' + idEscaped + '"]' );
item.range = { item.range = {
// Start range after startMarker, because it produces funny results from getBoundingClientRect // Start range after startMarker, because it produces funny results from getBoundingClientRect
@ -125,10 +125,10 @@ ThreadItem.prototype.calculateThreadSummary = function () {
if ( this.authors ) { if ( this.authors ) {
return; return;
} }
var authors = {}; const authors = {};
var commentCount = 0; let commentCount = 0;
var oldestReply = null; let oldestReply = null;
var latestReply = null; let latestReply = null;
function threadScan( comment ) { function threadScan( comment ) {
if ( comment.type === 'comment' ) { if ( comment.type === 'comment' ) {
authors[ comment.author ] = authors[ comment.author ] || { authors[ comment.author ] = authors[ comment.author ] || {
@ -214,7 +214,7 @@ ThreadItem.prototype.getOldestReply = function () {
* @return {ThreadItem[]} Thread items * @return {ThreadItem[]} Thread items
*/ */
ThreadItem.prototype.getThreadItemsBelow = function () { ThreadItem.prototype.getThreadItemsBelow = function () {
var threadItems = []; const threadItems = [];
function getReplies( comment ) { function getReplies( comment ) {
threadItems.push( comment ); threadItems.push( comment );
comment.replies.forEach( getReplies ); comment.replies.forEach( getReplies );
@ -231,8 +231,8 @@ ThreadItem.prototype.getThreadItemsBelow = function () {
* @return {Range} * @return {Range}
*/ */
ThreadItem.prototype.getRange = function () { ThreadItem.prototype.getRange = function () {
var doc = this.range.startContainer.ownerDocument; const doc = this.range.startContainer.ownerDocument;
var nativeRange = doc.createRange(); const nativeRange = doc.createRange();
nativeRange.setStart( this.range.startContainer, this.range.startOffset ); nativeRange.setStart( this.range.startContainer, this.range.startOffset );
nativeRange.setEnd( this.range.endContainer, this.range.endOffset ); nativeRange.setEnd( this.range.endContainer, this.range.endOffset );
return nativeRange; return nativeRange;

View file

@ -26,10 +26,10 @@ OO.initClass( ThreadItemSet );
* @return {ThreadItemSet} * @return {ThreadItemSet}
*/ */
ThreadItemSet.static.newFromJSON = function ( threads, rootNode, parser ) { ThreadItemSet.static.newFromJSON = function ( threads, rootNode, parser ) {
var result = new ThreadItemSet(); const result = new ThreadItemSet();
function infuse( itemHash, parent ) { function infuse( itemHash, parent ) {
var item = ThreadItem.static.newFromJSON( itemHash, rootNode ); const item = ThreadItem.static.newFromJSON( itemHash, rootNode );
result.addThreadItem( item ); result.addThreadItem( item );

View file

@ -56,7 +56,7 @@ function getApi() {
* @return {jQuery.Promise} * @return {jQuery.Promise}
*/ */
function getPageData( pageName, oldId, apiParams ) { function getPageData( pageName, oldId, apiParams ) {
var api = getApi(); const api = getApi();
apiParams = apiParams || {}; apiParams = apiParams || {};
pageDataCache[ pageName ] = pageDataCache[ pageName ] || {}; pageDataCache[ pageName ] = pageDataCache[ pageName ] || {};
@ -64,7 +64,7 @@ function getPageData( pageName, oldId, apiParams ) {
return pageDataCache[ pageName ][ oldId ]; return pageDataCache[ pageName ][ oldId ];
} }
var lintPromise, transcludedFromPromise; let lintPromise, transcludedFromPromise;
if ( oldId ) { if ( oldId ) {
lintPromise = api.get( { lintPromise = api.get( {
action: 'query', action: 'query',
@ -84,13 +84,13 @@ function getPageData( pageName, oldId, apiParams ) {
transcludedFromPromise = $.Deferred().resolve( {} ).promise(); transcludedFromPromise = $.Deferred().resolve( {} ).promise();
} }
var veMetadataPromise = api.get( Object.assign( { const veMetadataPromise = api.get( Object.assign( {
action: 'visualeditor', action: 'visualeditor',
paction: 'metadata', paction: 'metadata',
page: pageName page: pageName
}, apiParams ) ).then( ( response ) => OO.getProp( response, 'visualeditor' ) || [] ); }, apiParams ) ).then( ( response ) => OO.getProp( response, 'visualeditor' ) || [] );
var promise = $.when( lintPromise, transcludedFromPromise, veMetadataPromise ) const promise = $.when( lintPromise, transcludedFromPromise, veMetadataPromise )
.then( ( linterrors, transcludedfrom, metadata ) => ( { .then( ( linterrors, transcludedfrom, metadata ) => ( {
linterrors: linterrors, linterrors: linterrors,
transcludedfrom: transcludedfrom, transcludedfrom: transcludedfrom,
@ -119,9 +119,9 @@ function getPageData( pageName, oldId, apiParams ) {
* Rejects with error data if the comment is transcluded, or there are lint errors on the page. * Rejects with error data if the comment is transcluded, or there are lint errors on the page.
*/ */
function checkThreadItemOnPage( pageName, oldId, threadItem ) { function checkThreadItemOnPage( pageName, oldId, threadItem ) {
var isNewTopic = threadItem.id === utils.NEW_TOPIC_COMMENT_ID; const isNewTopic = threadItem.id === utils.NEW_TOPIC_COMMENT_ID;
var defaultMode = mw.user.options.get( 'discussiontools-editmode' ) || mw.config.get( 'wgDiscussionToolsFallbackEditMode' ); const defaultMode = mw.user.options.get( 'discussiontools-editmode' ) || mw.config.get( 'wgDiscussionToolsFallbackEditMode' );
var apiParams = null; let apiParams = null;
if ( isNewTopic ) { if ( isNewTopic ) {
apiParams = { apiParams = {
section: 'new', section: 'new',
@ -134,7 +134,7 @@ function checkThreadItemOnPage( pageName, oldId, threadItem ) {
return getPageData( pageName, oldId, apiParams ) return getPageData( pageName, oldId, apiParams )
.then( ( response ) => { .then( ( response ) => {
var metadata = response.metadata, const metadata = response.metadata,
lintErrors = response.linterrors, lintErrors = response.linterrors,
transcludedFrom = response.transcludedfrom; transcludedFrom = response.transcludedfrom;
@ -144,7 +144,7 @@ function checkThreadItemOnPage( pageName, oldId, threadItem ) {
// or if a thread item's parent changes. // or if a thread item's parent changes.
// Data by name might be combined from two or more thread items, which would only allow us to // Data by name might be combined from two or more thread items, which would only allow us to
// treat them both as transcluded from unknown source, unless we check ID first. // treat them both as transcluded from unknown source, unless we check ID first.
var isTranscludedFrom = transcludedFrom[ threadItem.id ]; let isTranscludedFrom = transcludedFrom[ threadItem.id ];
if ( isTranscludedFrom === undefined ) { if ( isTranscludedFrom === undefined ) {
isTranscludedFrom = transcludedFrom[ threadItem.name ]; isTranscludedFrom = transcludedFrom[ threadItem.name ];
} }
@ -158,11 +158,11 @@ function checkThreadItemOnPage( pageName, oldId, threadItem ) {
mw.message( 'discussiontools-error-comment-disappeared-reload' ).parse() mw.message( 'discussiontools-error-comment-disappeared-reload' ).parse()
} ] } ).promise(); } ] } ).promise();
} else if ( isTranscludedFrom ) { } else if ( isTranscludedFrom ) {
var mwTitle = isTranscludedFrom === true ? null : mw.Title.newFromText( isTranscludedFrom ); const mwTitle = isTranscludedFrom === true ? null : mw.Title.newFromText( isTranscludedFrom );
// If this refers to a template rather than a subpage, we never want to edit it // If this refers to a template rather than a subpage, we never want to edit it
var follow = mwTitle && mwTitle.getNamespaceId() !== mw.config.get( 'wgNamespaceIds' ).template; const follow = mwTitle && mwTitle.getNamespaceId() !== mw.config.get( 'wgNamespaceIds' ).template;
var transcludedErrMsg; let transcludedErrMsg;
if ( follow ) { if ( follow ) {
transcludedErrMsg = mw.message( transcludedErrMsg = mw.message(
'discussiontools-error-comment-is-transcluded-title', 'discussiontools-error-comment-is-transcluded-title',
@ -192,7 +192,7 @@ function checkThreadItemOnPage( pageName, oldId, threadItem ) {
if ( lintErrors.length ) { if ( lintErrors.length ) {
// We currently only request the first error // We currently only request the first error
var lintType = lintErrors[ 0 ].category; const lintType = lintErrors[ 0 ].category;
return $.Deferred().reject( 'lint', { errors: [ { return $.Deferred().reject( 'lint', { errors: [ {
code: 'lint', code: 'lint',
@ -227,7 +227,7 @@ function getCheckboxesPromise( pageName, oldId ) {
pageName, pageName,
oldId oldId
).then( ( pageData ) => { ).then( ( pageData ) => {
var data = pageData.metadata, const data = pageData.metadata,
checkboxesDef = {}; checkboxesDef = {};
mw.messages.set( data.checkboxesMessages ); mw.messages.set( data.checkboxesMessages );
@ -251,7 +251,7 @@ function getCheckboxesPromise( pageName, oldId ) {
* @return {string[]} * @return {string[]}
*/ */
function getReplyWidgetModules() { function getReplyWidgetModules() {
var veConf = mw.config.get( 'wgVisualEditorConfig' ), let veConf = mw.config.get( 'wgVisualEditorConfig' ),
modules = [ 'ext.discussionTools.ReplyWidget' ] modules = [ 'ext.discussionTools.ReplyWidget' ]
.concat( veConf.pluginModules.filter( mw.loader.getState ) ); .concat( veConf.pluginModules.filter( mw.loader.getState ) );
@ -279,7 +279,7 @@ function getReplyWidgetModules() {
* @param {boolean} [state.tempUserCreated] Whether a temp user was just created * @param {boolean} [state.tempUserCreated] Whether a temp user was just created
*/ */
function init( $container, state ) { function init( $container, state ) {
var let
activeCommentId = null, activeCommentId = null,
activeController = null, activeController = null,
// Loads later to avoid circular dependency // Loads later to avoid circular dependency
@ -315,9 +315,9 @@ function init( $container, state ) {
); );
} ); } );
var parser = new Parser( require( './parser/data.json' ) ); const parser = new Parser( require( './parser/data.json' ) );
var commentNodes = $pageContainer[ 0 ].querySelectorAll( '[data-mw-thread-id]' ); const commentNodes = $pageContainer[ 0 ].querySelectorAll( '[data-mw-thread-id]' );
pageThreads = ThreadItemSet.static.newFromJSON( mw.config.get( 'wgDiscussionToolsPageThreads' ) || [], $pageContainer[ 0 ], parser ); pageThreads = ThreadItemSet.static.newFromJSON( mw.config.get( 'wgDiscussionToolsPageThreads' ) || [], $pageContainer[ 0 ], parser );
if ( featuresEnabled.topicsubscription ) { if ( featuresEnabled.topicsubscription ) {
@ -350,7 +350,7 @@ function init( $container, state ) {
* @param {MemoryStorage} [storage] Storage object for autosave * @param {MemoryStorage} [storage] Storage object for autosave
*/ */
function setupController( comment, $link, mode, hideErrors, suppressNotifications, storage ) { function setupController( comment, $link, mode, hideErrors, suppressNotifications, storage ) {
var commentController, $addSectionLink; let commentController, $addSectionLink;
if ( !storage ) { if ( !storage ) {
storage = new MemoryStorage( mw.storage, 'mw-ext-DiscussionTools-reply/' + comment.id, STORAGE_EXPIRY ); storage = new MemoryStorage( mw.storage, 'mw-ext-DiscussionTools-reply/' + comment.id, STORAGE_EXPIRY );
@ -404,7 +404,7 @@ function init( $container, state ) {
} }
function newTopicComment( data ) { function newTopicComment( data ) {
var comment = new HeadingItem( {}, 2 ); const comment = new HeadingItem( {}, 2 );
comment.id = utils.NEW_TOPIC_COMMENT_ID; comment.id = utils.NEW_TOPIC_COMMENT_ID;
comment.isNewTopic = true; comment.isNewTopic = true;
Object.assign( comment, data ); Object.assign( comment, data );
@ -424,7 +424,7 @@ function init( $container, state ) {
return; return;
} }
var teardownPromise = $.Deferred().resolve(); let teardownPromise = $.Deferred().resolve();
if ( commentId === utils.NEW_TOPIC_COMMENT_ID ) { if ( commentId === utils.NEW_TOPIC_COMMENT_ID ) {
// If this is a new topic link, and a reply widget is open, attempt to close it first. // If this is a new topic link, and a reply widget is open, attempt to close it first.
if ( activeController ) { if ( activeController ) {
@ -447,7 +447,7 @@ function init( $container, state ) {
if ( activeController ) { if ( activeController ) {
return; return;
} }
var comment; let comment;
if ( commentId !== utils.NEW_TOPIC_COMMENT_ID ) { if ( commentId !== utils.NEW_TOPIC_COMMENT_ID ) {
comment = pageThreads.findCommentById( commentId ); comment = pageThreads.findCommentById( commentId );
} else { } else {
@ -462,7 +462,7 @@ function init( $container, state ) {
} ); } );
} ); } );
var mobilePromise = OO.ui.isMobile() && mw.loader.getState( 'mobile.init' ) ? const mobilePromise = OO.ui.isMobile() && mw.loader.getState( 'mobile.init' ) ?
mw.loader.using( 'mobile.init' ) : mw.loader.using( 'mobile.init' ) :
$.Deferred().resolve().promise(); $.Deferred().resolve().promise();
@ -529,7 +529,7 @@ function init( $container, state ) {
} }
} ); } );
var dismissableNotificationPromise = null; let dismissableNotificationPromise = null;
// Page-level handlers only need to be setup once // Page-level handlers only need to be setup once
if ( !pageHandlersSetup ) { if ( !pageHandlersSetup ) {
$( window ).on( 'popstate', () => { $( window ).on( 'popstate', () => {
@ -556,9 +556,9 @@ function init( $container, state ) {
} }
if ( state.firstLoad ) { if ( state.firstLoad ) {
mobilePromise.then( () => { mobilePromise.then( () => {
var findCommentQuery; let findCommentQuery;
var isHeading = false; let isHeading = false;
var highlightResult = highlighter.highlightTargetComment( pageThreads ); const highlightResult = highlighter.highlightTargetComment( pageThreads );
// Hash contains a non-replaced space (should be underscore), maybe due to // Hash contains a non-replaced space (should be underscore), maybe due to
// manual creation or a broken third party tool. Just replace the spaces // manual creation or a broken third party tool. Just replace the spaces
@ -568,7 +568,7 @@ function init( $container, state ) {
// element, but the fixed hash does, to avoid affects on other apps which // element, but the fixed hash does, to avoid affects on other apps which
// may use fragments with spaces. // may use fragments with spaces.
if ( location.hash && !mw.util.getTargetFromFragment() && location.hash.indexOf( '%20' ) !== -1 ) { if ( location.hash && !mw.util.getTargetFromFragment() && location.hash.indexOf( '%20' ) !== -1 ) {
var fixedHash = location.hash.slice( 1 ).replace( /%20/g, '_' ); const fixedHash = location.hash.slice( 1 ).replace( /%20/g, '_' );
if ( mw.util.getTargetFromFragment( fixedHash ) ) { if ( mw.util.getTargetFromFragment( fixedHash ) ) {
location.hash = fixedHash; location.hash = fixedHash;
} }
@ -580,8 +580,8 @@ function init( $container, state ) {
// Not a DT comment // Not a DT comment
highlightResult.highlighted.length === 0 && highlightResult.requested.length === 0 highlightResult.highlighted.length === 0 && highlightResult.requested.length === 0
) { ) {
var fragment = location.hash.slice( 1 ); const fragment = location.hash.slice( 1 );
var ignorePatterns = [ const ignorePatterns = [
// A leading '/' or '!/' usually means a application route, e.g. /media, or /editor. // A leading '/' or '!/' usually means a application route, e.g. /media, or /editor.
// We can't rule out a heading title (T349498), but they are unlikely // We can't rule out a heading title (T349498), but they are unlikely
/^!?\//, /^!?\//,
@ -611,8 +611,8 @@ function init( $container, state ) {
} }
if ( findCommentQuery ) { if ( findCommentQuery ) {
// TODO: Support multiple commentIds being requested and not all being found // TODO: Support multiple commentIds being requested and not all being found
var dtConf = require( './config.json' ); const dtConf = require( './config.json' );
var findCommentRequest = dtConf.enablePermalinksFrontend ? const findCommentRequest = dtConf.enablePermalinksFrontend ?
getApi().get( Object.assign( { getApi().get( Object.assign( {
action: 'discussiontoolsfindcomment' action: 'discussiontoolsfindcomment'
}, findCommentQuery ) ) : }, findCommentQuery ) ) :
@ -621,14 +621,14 @@ function init( $container, state ) {
findCommentRequest, findCommentRequest,
mw.loader.using( 'mediawiki.notification' ) mw.loader.using( 'mediawiki.notification' )
).then( ( results ) => { ).then( ( results ) => {
var result = results[ 0 ]; const result = results[ 0 ];
var titles = []; let titles = [];
if ( result.discussiontoolsfindcomment ) { if ( result.discussiontoolsfindcomment ) {
titles = result.discussiontoolsfindcomment.map( ( threadItemData ) => { titles = result.discussiontoolsfindcomment.map( ( threadItemData ) => {
// Only show items that appear on the current revision of their page // Only show items that appear on the current revision of their page
// and are not transcluded from another page // and are not transcluded from another page
if ( threadItemData.couldredirect ) { if ( threadItemData.couldredirect ) {
var title = mw.Title.newFromText( const title = mw.Title.newFromText(
threadItemData.title + '#' + threadItemData.title + '#' +
mw.util.escapeIdForLink( threadItemData.id ) mw.util.escapeIdForLink( threadItemData.id )
); );
@ -638,8 +638,8 @@ function init( $container, state ) {
} ).filter( ( url ) => url ); } ).filter( ( url ) => url );
} }
if ( titles.length ) { if ( titles.length ) {
var $list = $( '<ul>' ); const $list = $( '<ul>' );
var $notification = $( '<div>' ).append( const $notification = $( '<div>' ).append(
$( '<p>' ).text( mw.message( $( '<p>' ).text( mw.message(
isHeading ? isHeading ?
'discussiontools-target-heading-found-moved' : 'discussiontools-target-heading-found-moved' :
@ -696,7 +696,7 @@ function updatePageContents( $container, data ) {
// eslint-disable-next-line no-jquery/no-global-selector // eslint-disable-next-line no-jquery/no-global-selector
if ( $( '#catlinks' ).length ) { if ( $( '#catlinks' ).length ) {
var $categories = $( $.parseHTML( data.parse.categorieshtml ) ); const $categories = $( $.parseHTML( data.parse.categorieshtml ) );
mw.hook( 'wikipage.categories' ).fire( $categories ); mw.hook( 'wikipage.categories' ).fire( $categories );
// eslint-disable-next-line no-jquery/no-global-selector // eslint-disable-next-line no-jquery/no-global-selector
$( '#catlinks' ).replaceWith( $categories ); $( '#catlinks' ).replaceWith( $categories );
@ -732,23 +732,23 @@ function updatePageContents( $container, data ) {
// TODO: Upstream this to core/skins, triggered by a hook (wikipage.content?) // TODO: Upstream this to core/skins, triggered by a hook (wikipage.content?)
// eslint-disable-next-line no-jquery/no-global-selector // eslint-disable-next-line no-jquery/no-global-selector
$( '#t-permalink' ).add( '#coll-download-as-rl' ).find( 'a' ).each( ( i, link ) => { $( '#t-permalink' ).add( '#coll-download-as-rl' ).find( 'a' ).each( ( i, link ) => {
var permalinkUrl = new URL( link.href ); const permalinkUrl = new URL( link.href );
permalinkUrl.searchParams.set( 'oldid', data.parse.revid ); permalinkUrl.searchParams.set( 'oldid', data.parse.revid );
$( link ).attr( 'href', permalinkUrl.toString() ); $( link ).attr( 'href', permalinkUrl.toString() );
} ); } );
var url = new URL( location.href ); const url = new URL( location.href );
url.searchParams.delete( 'oldid' ); url.searchParams.delete( 'oldid' );
// If there are any other query parameters left, re-use that URL object. // If there are any other query parameters left, re-use that URL object.
// Otherwise use the canonical style view url (T44553, T102363). // Otherwise use the canonical style view url (T44553, T102363).
var keys = []; const keys = [];
url.searchParams.forEach( ( val, key ) => { url.searchParams.forEach( ( val, key ) => {
keys.push( key ); keys.push( key );
} ); } );
if ( !keys.length || ( keys.length === 1 && keys[ 0 ] === 'title' ) ) { if ( !keys.length || ( keys.length === 1 && keys[ 0 ] === 'title' ) ) {
var viewUrl = new URL( mw.util.getUrl( mw.config.get( 'wgRelevantPageName' ) ), document.baseURI ); const viewUrl = new URL( mw.util.getUrl( mw.config.get( 'wgRelevantPageName' ) ), document.baseURI );
viewUrl.hash = location.hash; viewUrl.hash = location.hash;
history.pushState( null, '', viewUrl ); history.pushState( null, '', viewUrl );
} else { } else {
@ -809,7 +809,7 @@ function update( data, threadItem, pageName, replyWidget ) {
replyWidget.unbindBeforeUnloadHandler(); replyWidget.unbindBeforeUnloadHandler();
replyWidget.clearStorage(); replyWidget.clearStorage();
replyWidget.setPending( true ); replyWidget.setPending( true );
var params = { dtrepliedto: threadItem.id }; const params = { dtrepliedto: threadItem.id };
if ( data.tempusercreated ) { if ( data.tempusercreated ) {
params.dttempusercreated = '1'; params.dttempusercreated = '1';
} }
@ -826,7 +826,7 @@ function update( data, threadItem, pageName, replyWidget ) {
mw.dt.initState.tempUserCreated = data.tempusercreated; mw.dt.initState.tempUserCreated = data.tempusercreated;
// Update page state // Update page state
var pageUpdated = $.Deferred(); const pageUpdated = $.Deferred();
if ( pageName === mw.config.get( 'wgRelevantPageName' ) ) { if ( pageName === mw.config.get( 'wgRelevantPageName' ) ) {
// We can use the result from the VisualEditor API // We can use the result from the VisualEditor API
updatePageContents( $pageContainer, { updatePageContents( $pageContainer, {
@ -854,7 +854,7 @@ function update( data, threadItem, pageName, replyWidget ) {
} else { } else {
// We saved to another page, we must purge and then fetch the current page // We saved to another page, we must purge and then fetch the current page
var api = getApi(); const api = getApi();
api.post( { api.post( {
action: 'purge', action: 'purge',
titles: mw.config.get( 'wgRelevantPageName' ) titles: mw.config.get( 'wgRelevantPageName' )
@ -871,7 +871,7 @@ function update( data, threadItem, pageName, replyWidget ) {
// User logged in if module loaded. // User logged in if module loaded.
if ( mw.loader.getState( 'mediawiki.page.watch.ajax' ) === 'ready' ) { if ( mw.loader.getState( 'mediawiki.page.watch.ajax' ) === 'ready' ) {
var watch = require( 'mediawiki.page.watch.ajax' ); const watch = require( 'mediawiki.page.watch.ajax' );
watch.updateWatchLink( watch.updateWatchLink(
mw.Title.newFromText( pageName ), mw.Title.newFromText( pageName ),

View file

@ -6,24 +6,24 @@ var updaters = [];
var isRtl = $( 'html' ).attr( 'dir' ) === 'rtl'; var isRtl = $( 'html' ).attr( 'dir' ) === 'rtl';
function markTimestamp( parser, node, match ) { function markTimestamp( parser, node, match ) {
var dfParsers = parser.getLocalTimestampParsers(); const dfParsers = parser.getLocalTimestampParsers();
var newNode = node.splitText( match.matchData.index ); const newNode = node.splitText( match.matchData.index );
newNode.splitText( match.matchData[ 0 ].length ); newNode.splitText( match.matchData[ 0 ].length );
var wrapper = document.createElement( 'span' ); const wrapper = document.createElement( 'span' );
wrapper.className = 'ext-discussiontools-debughighlighter-timestamp'; wrapper.className = 'ext-discussiontools-debughighlighter-timestamp';
// We might need to actually port all the date formatting code from MediaWiki's PHP code // We might need to actually port all the date formatting code from MediaWiki's PHP code
// if we want to support displaying dates in all the formats available in user preferences // if we want to support displaying dates in all the formats available in user preferences
// (which include formats in several non-Gregorian calendars). // (which include formats in several non-Gregorian calendars).
var date = dfParsers[ match.parserIndex ]( match.matchData ).date; const date = dfParsers[ match.parserIndex ]( match.matchData ).date;
wrapper.title = date.format() + ' / ' + date.fromNow(); wrapper.title = date.format() + ' / ' + date.fromNow();
wrapper.appendChild( newNode ); wrapper.appendChild( newNode );
node.parentNode.insertBefore( wrapper, node.nextSibling ); node.parentNode.insertBefore( wrapper, node.nextSibling );
} }
function markSignature( sigNodes ) { function markSignature( sigNodes ) {
var const
where = sigNodes[ 0 ], where = sigNodes[ 0 ],
wrapper = document.createElement( 'span' ); wrapper = document.createElement( 'span' );
wrapper.className = 'ext-discussiontools-debughighlighter-signature'; wrapper.className = 'ext-discussiontools-debughighlighter-signature';
@ -37,7 +37,7 @@ function fixFakeFirstHeadingRect( rect, comment ) {
// If the page has comments before the first section heading, they are connected to a "fake" // If the page has comments before the first section heading, they are connected to a "fake"
// heading with an empty range. Visualize the page title as the heading for that section. // heading with an empty range. Visualize the page title as the heading for that section.
if ( rect.x === 0 && rect.y === 0 && comment.type === 'heading' ) { if ( rect.x === 0 && rect.y === 0 && comment.type === 'heading' ) {
var node = document.getElementsByClassName( 'firstHeading' )[ 0 ]; const node = document.getElementsByClassName( 'firstHeading' )[ 0 ];
return node.getBoundingClientRect(); return node.getBoundingClientRect();
} }
return rect; return rect;
@ -45,9 +45,9 @@ function fixFakeFirstHeadingRect( rect, comment ) {
function calculateSizes() { function calculateSizes() {
// eslint-disable-next-line no-jquery/no-global-selector // eslint-disable-next-line no-jquery/no-global-selector
var $content = $( '#mw-content-text' ); const $content = $( '#mw-content-text' );
var $test = $( '<dd>' ).appendTo( $( '<dl>' ).appendTo( $content ) ); const $test = $( '<dd>' ).appendTo( $( '<dl>' ).appendTo( $content ) );
var rect = $content[ 0 ].getBoundingClientRect(); const rect = $content[ 0 ].getBoundingClientRect();
initialOffset = isRtl ? document.body.scrollWidth - rect.left - rect.width : rect.left; initialOffset = isRtl ? document.body.scrollWidth - rect.left - rect.width : rect.left;
indentWidth = parseFloat( $test.css( isRtl ? 'margin-right' : 'margin-left' ) ) + indentWidth = parseFloat( $test.css( isRtl ? 'margin-right' : 'margin-left' ) ) +
@ -57,33 +57,33 @@ function calculateSizes() {
} }
function markComment( comment ) { function markComment( comment ) {
var marker = document.createElement( 'div' ); const marker = document.createElement( 'div' );
marker.className = 'ext-discussiontools-debughighlighter-comment'; marker.className = 'ext-discussiontools-debughighlighter-comment';
if ( !firstMarker ) { if ( !firstMarker ) {
firstMarker = marker; firstMarker = marker;
} }
var marker2 = null; let marker2 = null;
if ( comment.parent ) { if ( comment.parent ) {
marker2 = document.createElement( 'div' ); marker2 = document.createElement( 'div' );
marker2.className = 'ext-discussiontools-debughighlighter-comment-ruler'; marker2.className = 'ext-discussiontools-debughighlighter-comment-ruler';
} }
var markerWarnings = null; let markerWarnings = null;
if ( comment.warnings && comment.warnings.length ) { if ( comment.warnings && comment.warnings.length ) {
markerWarnings = document.createElement( 'div' ); markerWarnings = document.createElement( 'div' );
markerWarnings.className = 'ext-discussiontools-debughighlighter-comment-warnings'; markerWarnings.className = 'ext-discussiontools-debughighlighter-comment-warnings';
markerWarnings.innerText = comment.warnings.join( '\n' ); markerWarnings.innerText = comment.warnings.join( '\n' );
} }
var update = function () { const update = function () {
var rect = fixFakeFirstHeadingRect( const rect = fixFakeFirstHeadingRect(
comment.getRange().getBoundingClientRect(), comment.getRange().getBoundingClientRect(),
comment comment
); );
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop; const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft; const scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft;
marker.style.top = ( rect.top + scrollTop ) + 'px'; marker.style.top = ( rect.top + scrollTop ) + 'px';
marker.style.height = ( rect.height ) + 'px'; marker.style.height = ( rect.height ) + 'px';
@ -91,7 +91,7 @@ function markComment( comment ) {
marker.style.width = ( rect.width ) + 'px'; marker.style.width = ( rect.width ) + 'px';
if ( marker2 ) { if ( marker2 ) {
var parentRect = comment.parent.getRange().getBoundingClientRect(); let parentRect = comment.parent.getRange().getBoundingClientRect();
parentRect = fixFakeFirstHeadingRect( parentRect, comment.parent ); parentRect = fixFakeFirstHeadingRect( parentRect, comment.parent );
if ( comment.parent.level === 0 ) { if ( comment.parent.level === 0 ) {
// Twiddle so that it looks nice // Twiddle so that it looks nice

View file

@ -11,7 +11,7 @@ require( './CommentTarget.js' );
* @param {Object} [config] Configuration options * @param {Object} [config] Configuration options
*/ */
function CommentTargetWidget( replyWidget, config ) { function CommentTargetWidget( replyWidget, config ) {
var excludeCommands = [ let excludeCommands = [
'blockquoteWrap', // T258194 'blockquoteWrap', // T258194
// Disable to allow Tab/Shift+Tab to move focus out of the widget (T172694) // Disable to allow Tab/Shift+Tab to move focus out of the widget (T172694)
'indent', 'indent',
@ -75,7 +75,7 @@ CommentTargetWidget.prototype.createTarget = function () {
* @inheritdoc * @inheritdoc
*/ */
CommentTargetWidget.prototype.setDocument = function ( docOrHtml ) { CommentTargetWidget.prototype.setDocument = function ( docOrHtml ) {
var mode = this.target.getDefaultMode(), const mode = this.target.getDefaultMode(),
doc = ( mode === 'visual' && typeof docOrHtml === 'string' ) ? doc = ( mode === 'visual' && typeof docOrHtml === 'string' ) ?
this.target.parseDocument( docOrHtml ) : this.target.parseDocument( docOrHtml ) :
docOrHtml, docOrHtml,

View file

@ -41,7 +41,7 @@ CeMWPingNode.static.getDescription = function ( model ) {
* @inheritdoc * @inheritdoc
*/ */
CeMWPingNode.prototype.initialize = function () { CeMWPingNode.prototype.initialize = function () {
var model = this.getModel(), const model = this.getModel(),
prefix = mw.msg( 'discussiontools-replywidget-mention-prefix' ), prefix = mw.msg( 'discussiontools-replywidget-mention-prefix' ),
suffix = mw.msg( 'discussiontools-replywidget-mention-suffix' ), suffix = mw.msg( 'discussiontools-replywidget-mention-suffix' ),
user = model.getAttribute( 'user' ), user = model.getAttribute( 'user' ),
@ -51,7 +51,7 @@ CeMWPingNode.prototype.initialize = function () {
CeMWPingNode.super.prototype.initialize.call( this ); CeMWPingNode.super.prototype.initialize.call( this );
// DOM changes // DOM changes
var $link = $( '<a>' ) const $link = $( '<a>' )
.addClass( 'ext-discussiontools-ce-mwPingNode' ) .addClass( 'ext-discussiontools-ce-mwPingNode' )
.attr( { .attr( {
href: title.getUrl(), href: title.getUrl(),

View file

@ -40,7 +40,7 @@ DmMWPingNode.static.matchFunction = function () {
DmMWPingNode.static.disallowedAnnotationTypes = [ 'link' ]; DmMWPingNode.static.disallowedAnnotationTypes = [ 'link' ];
DmMWPingNode.static.toDomElements = function ( dataElement, doc, converter ) { DmMWPingNode.static.toDomElements = function ( dataElement, doc, converter ) {
var domElements, let domElements,
prefix = mw.msg( 'discussiontools-replywidget-mention-prefix' ), prefix = mw.msg( 'discussiontools-replywidget-mention-prefix' ),
suffix = mw.msg( 'discussiontools-replywidget-mention-suffix' ), suffix = mw.msg( 'discussiontools-replywidget-mention-suffix' ),
title = mw.Title.makeTitle( mw.config.get( 'wgNamespaceIds' ).user, dataElement.attributes.user ); title = mw.Title.makeTitle( mw.config.get( 'wgNamespaceIds' ).user, dataElement.attributes.user );

View file

@ -15,9 +15,9 @@ DtUiMWSignatureContextItem.static.label =
// Get the formatted, localized, platform-specific shortcut key for the given command // Get the formatted, localized, platform-specific shortcut key for the given command
DtUiMWSignatureContextItem.prototype.getShortcutKey = function ( commandName ) { DtUiMWSignatureContextItem.prototype.getShortcutKey = function ( commandName ) {
// Adapted from ve.ui.CommandHelpDialog.prototype.initialize // Adapted from ve.ui.CommandHelpDialog.prototype.initialize
var commandInfo = ve.ui.commandHelpRegistry.lookup( commandName ); const commandInfo = ve.ui.commandHelpRegistry.lookup( commandName );
var triggerList = ve.ui.triggerRegistry.lookup( commandInfo.trigger ); const triggerList = ve.ui.triggerRegistry.lookup( commandInfo.trigger );
var $shortcut = $( '<kbd>' ).addClass( 've-ui-commandHelpDialog-shortcut' ).append( const $shortcut = $( '<kbd>' ).addClass( 've-ui-commandHelpDialog-shortcut' ).append(
triggerList[ 0 ].getMessage( true ).map( ve.ui.CommandHelpDialog.static.buildKeyNode ) triggerList[ 0 ].getMessage( true ).map( ve.ui.CommandHelpDialog.static.buildKeyNode )
).find( 'kbd + kbd' ).before( '+' ).end(); ).find( 'kbd + kbd' ).before( '+' ).end();
return $shortcut; return $shortcut;

View file

@ -37,7 +37,7 @@ function MWUsernameCompletionAction() {
} }
} ); } );
// On user talk pages, always list the "owner" of the talk page // On user talk pages, always list the "owner" of the talk page
var relevantUserName = mw.config.get( 'wgRelevantUserName' ); const relevantUserName = mw.config.get( 'wgRelevantUserName' );
if ( if (
relevantUserName && relevantUserName &&
relevantUserName !== mw.user.getName() && relevantUserName !== mw.user.getName() &&
@ -67,7 +67,7 @@ MWUsernameCompletionAction.static.methods.push( 'insertAndOpen' );
/* Methods */ /* Methods */
MWUsernameCompletionAction.prototype.insertAndOpen = function () { MWUsernameCompletionAction.prototype.insertAndOpen = function () {
var inserted = false, let inserted = false,
surfaceModel = this.surface.getModel(), surfaceModel = this.surface.getModel(),
fragment = surfaceModel.getFragment(); fragment = surfaceModel.getFragment();
@ -106,12 +106,12 @@ MWUsernameCompletionAction.prototype.getSequenceLength = function () {
}; };
MWUsernameCompletionAction.prototype.getSuggestions = function ( input ) { MWUsernameCompletionAction.prototype.getSuggestions = function ( input ) {
var title = mw.Title.makeTitle( mw.config.get( 'wgNamespaceIds' ).user, input ), const title = mw.Title.makeTitle( mw.config.get( 'wgNamespaceIds' ).user, input ),
validatedInput = title ? input : '', validatedInput = title ? input : '',
action = this; action = this;
this.api.abort(); // Abort all unfinished API requests this.api.abort(); // Abort all unfinished API requests
var apiPromise; let apiPromise;
if ( input.length > 0 && !this.searchedPrefixes[ input ] ) { if ( input.length > 0 && !this.searchedPrefixes[ input ] ) {
apiPromise = this.api.get( { apiPromise = this.api.get( {
action: 'query', action: 'query',
@ -123,7 +123,7 @@ MWUsernameCompletionAction.prototype.getSuggestions = function ( input ) {
// blocked users and still probably have some suggestions left // blocked users and still probably have some suggestions left
aulimit: this.constructor.static.defaultLimit * 2 aulimit: this.constructor.static.defaultLimit * 2
} ).then( ( response ) => { } ).then( ( response ) => {
var suggestions = response.query.allusers.filter( const suggestions = response.query.allusers.filter(
// API doesn't return IPs // API doesn't return IPs
( user ) => !hasUser( action.localUsers, user.name ) && ( user ) => !hasUser( action.localUsers, user.name ) &&
!hasUser( action.remoteUsers, user.name ) && !hasUser( action.remoteUsers, user.name ) &&
@ -167,7 +167,7 @@ MWUsernameCompletionAction.prototype.getSuggestions = function ( input ) {
* @inheritdoc * @inheritdoc
*/ */
MWUsernameCompletionAction.prototype.compareSuggestionToInput = function ( suggestion, normalizedInput ) { MWUsernameCompletionAction.prototype.compareSuggestionToInput = function ( suggestion, normalizedInput ) {
var normalizedSuggestion = suggestion.username.toLowerCase(), const normalizedSuggestion = suggestion.username.toLowerCase(),
normalizedSearchIndex = normalizedSuggestion + ' ' + normalizedSearchIndex = normalizedSuggestion + ' ' +
suggestion.displayNames suggestion.displayNames
.map( ( displayName ) => displayName.toLowerCase() ).join( ' ' ); .map( ( displayName ) => displayName.toLowerCase() ).join( ' ' );
@ -197,13 +197,13 @@ MWUsernameCompletionAction.prototype.getMenuItemForSuggestion = function ( sugge
MWUsernameCompletionAction.prototype.getHeaderLabel = function ( input, suggestions ) { MWUsernameCompletionAction.prototype.getHeaderLabel = function ( input, suggestions ) {
if ( suggestions === undefined ) { if ( suggestions === undefined ) {
var $query = $( '<span>' ).text( input ); const $query = $( '<span>' ).text( input );
return mw.message( 'discussiontools-replywidget-mention-tool-header', $query ).parseDom(); return mw.message( 'discussiontools-replywidget-mention-tool-header', $query ).parseDom();
} }
}; };
MWUsernameCompletionAction.prototype.insertCompletion = function ( word, range ) { MWUsernameCompletionAction.prototype.insertCompletion = function ( word, range ) {
var prefix = mw.msg( 'discussiontools-replywidget-mention-prefix' ), const prefix = mw.msg( 'discussiontools-replywidget-mention-prefix' ),
suffix = mw.msg( 'discussiontools-replywidget-mention-suffix' ), suffix = mw.msg( 'discussiontools-replywidget-mention-suffix' ),
title = mw.Title.newFromText( word, mw.config.get( 'wgNamespaceIds' ).user ); title = mw.Title.newFromText( word, mw.config.get( 'wgNamespaceIds' ).user );
@ -213,7 +213,7 @@ MWUsernameCompletionAction.prototype.insertCompletion = function ( word, range )
return MWUsernameCompletionAction.super.prototype.insertCompletion.call( this, word, range ); return MWUsernameCompletionAction.super.prototype.insertCompletion.call( this, word, range );
} }
var fragment = this.surface.getModel().getLinearFragment( range, true ); const fragment = this.surface.getModel().getLinearFragment( range, true );
fragment.removeContent().insertContent( [ fragment.removeContent().insertContent( [
{ type: 'mwPing', attributes: { user: word } }, { type: 'mwPing', attributes: { user: word } },
{ type: '/mwPing' } { type: '/mwPing' }

View file

@ -1,6 +1,6 @@
// Adapted from ve.ui.MWWikitextDataTransferHandlerFactory // Adapted from ve.ui.MWWikitextDataTransferHandlerFactory
function importRegistry( parent, child ) { function importRegistry( parent, child ) {
var name; let name;
// Copy existing items // Copy existing items
for ( name in parent.registry ) { for ( name in parent.registry ) {
child.register( parent.registry[ name ] ); child.register( parent.registry[ name ] );

View file

@ -22,13 +22,13 @@ if ( debug & DEBUG_HIGHLIGHT ) {
comments.forEach( ( comment ) => { comments.forEach( ( comment ) => {
comment.signatureRanges.forEach( ( signatureRange ) => { comment.signatureRanges.forEach( ( signatureRange ) => {
var node = signatureRange.endContainer; const node = signatureRange.endContainer;
var match = parser.findTimestamp( node, timestampRegexps ); const match = parser.findTimestamp( node, timestampRegexps );
if ( !match ) { if ( !match ) {
return; return;
} }
var signature = parser.findSignature( node ).nodes; const signature = parser.findSignature( node ).nodes;
var emptySignature = signature.length === 1 && signature[ 0 ] === node; const emptySignature = signature.length === 1 && signature[ 0 ] === node;
// Note that additional content may follow the timestamp (e.g. in some voting formats), but we // Note that additional content may follow the timestamp (e.g. in some voting formats), but we
// don't care about it. The code below doesn't mark that due to now the text nodes are sliced, // don't care about it. The code below doesn't mark that due to now the text nodes are sliced,
// but we might need to take care to use the matched range of node in other cases. // but we might need to take care to use the matched range of node in other cases.
@ -43,7 +43,7 @@ if ( debug & DEBUG_HIGHLIGHT ) {
// eslint-disable-next-line no-bitwise // eslint-disable-next-line no-bitwise
if ( ( debug & DEBUG_VOTE ) || ( debug & DEBUG_VOTE_PERMISSIVE ) ) { if ( ( debug & DEBUG_VOTE ) || ( debug & DEBUG_VOTE_PERMISSIVE ) ) {
threads.forEach( ( thread ) => { threads.forEach( ( thread ) => {
var firstComment = thread.replies[ 0 ]; const firstComment = thread.replies[ 0 ];
if ( firstComment && firstComment.type === 'comment' ) { if ( firstComment && firstComment.type === 'comment' ) {
// eslint-disable-next-line no-bitwise // eslint-disable-next-line no-bitwise
@ -52,7 +52,7 @@ if ( ( debug & DEBUG_VOTE ) || ( debug & DEBUG_VOTE_PERMISSIVE ) ) {
return; return;
} }
var firstVote = firstComment.level === 1 ? const firstVote = firstComment.level === 1 ?
// In permissive mode, the first vote is the replies to the OP // In permissive mode, the first vote is the replies to the OP
firstComment.replies[ 0 ] : firstComment.replies[ 0 ] :
firstComment; firstComment;
@ -61,15 +61,15 @@ if ( ( debug & DEBUG_VOTE ) || ( debug & DEBUG_VOTE_PERMISSIVE ) ) {
return; return;
} }
var lastReply; let lastReply;
var level = firstVote.level; const level = firstVote.level;
firstVote.parent.replies.forEach( ( reply ) => { firstVote.parent.replies.forEach( ( reply ) => {
if ( reply.type === 'comment' && reply.level <= level ) { if ( reply.type === 'comment' && reply.level <= level ) {
lastReply = reply; lastReply = reply;
} }
} ); } );
var listItem = modifier.addSiblingListItem( const listItem = modifier.addSiblingListItem(
utils.closestElement( lastReply.range.endContainer, [ 'li', 'dd', 'p' ] ) utils.closestElement( lastReply.range.endContainer, [ 'li', 'dd', 'p' ] )
); );
if ( listItem && listItem.tagName.toLowerCase() === 'li' ) { if ( listItem && listItem.tagName.toLowerCase() === 'li' ) {

View file

@ -63,7 +63,7 @@ mw.dt.init = function ( $container ) {
// If it's a full page live preview, (re)initialize to support highlighting comments (T309423) // If it's a full page live preview, (re)initialize to support highlighting comments (T309423)
// FIXME This really should not depend on implementation details of 2 different live previews // FIXME This really should not depend on implementation details of 2 different live previews
// FIXME VisualEditor (2017WTE) preview can't be supported, because it messes with `id` attributes // FIXME VisualEditor (2017WTE) preview can't be supported, because it messes with `id` attributes
var livePreviewSelectors = '#wikiPreview, .ext-WikiEditor-realtimepreview-preview'; const livePreviewSelectors = '#wikiPreview, .ext-WikiEditor-realtimepreview-preview';
if ( $container.parent().is( livePreviewSelectors ) ) { if ( $container.parent().is( livePreviewSelectors ) ) {
reallyInit( $container ); reallyInit( $container );
return; return;
@ -94,7 +94,7 @@ if ( mw.config.get( 'wgAction' ) === 'history' ) {
// TODO: Remove this code after a few weeks. // TODO: Remove this code after a few weeks.
mw.requestIdleCallback( () => { mw.requestIdleCallback( () => {
try { try {
for ( var key in localStorage ) { for ( const key in localStorage ) {
if ( key.startsWith( 'reply/' ) ) { if ( key.startsWith( 'reply/' ) ) {
localStorage.removeItem( key ); localStorage.removeItem( key );
localStorage.removeItem( '_EXPIRY_' + key ); localStorage.removeItem( '_EXPIRY_' + key );

View file

@ -31,7 +31,7 @@ function ReplyWidget( commentController, commentDetails, config ) {
this.pending = false; this.pending = false;
this.isTornDown = false; this.isTornDown = false;
this.commentController = commentController; this.commentController = commentController;
var threadItem = commentController.getThreadItem(); const threadItem = commentController.getThreadItem();
this.commentDetails = commentDetails; this.commentDetails = commentDetails;
this.isNewTopic = !!threadItem.isNewTopic; this.isNewTopic = !!threadItem.isNewTopic;
this.pageName = commentDetails.pageName; this.pageName = commentDetails.pageName;
@ -39,7 +39,7 @@ function ReplyWidget( commentController, commentDetails, config ) {
// pageExists can only be false for the new comment tool, so we // pageExists can only be false for the new comment tool, so we
// don't need to worry about transcluded replies. // don't need to worry about transcluded replies.
this.pageExists = mw.config.get( 'wgRelevantArticleId', 0 ) !== 0; this.pageExists = mw.config.get( 'wgRelevantArticleId', 0 ) !== 0;
var contextNode = utils.closestElement( threadItem.range.endContainer, [ 'dl', 'ul', 'ol' ] ); const contextNode = utils.closestElement( threadItem.range.endContainer, [ 'dl', 'ul', 'ol' ] );
this.context = contextNode ? contextNode.tagName.toLowerCase() : 'dl'; this.context = contextNode ? contextNode.tagName.toLowerCase() : 'dl';
this.storage = commentController.storage; this.storage = commentController.storage;
// eslint-disable-next-line no-jquery/no-global-selector // eslint-disable-next-line no-jquery/no-global-selector
@ -51,7 +51,7 @@ function ReplyWidget( commentController, commentDetails, config ) {
this.$window = $( this.getElementWindow() ); this.$window = $( this.getElementWindow() );
this.onWindowScrollThrottled = OO.ui.throttle( this.onWindowScroll.bind( this ), 100 ); this.onWindowScrollThrottled = OO.ui.throttle( this.onWindowScroll.bind( this ), 100 );
var inputConfig = Object.assign( const inputConfig = Object.assign(
{ {
placeholder: this.isNewTopic ? placeholder: this.isNewTopic ?
mw.msg( 'discussiontools-replywidget-placeholder-newtopic' ) : mw.msg( 'discussiontools-replywidget-placeholder-newtopic' ) :
@ -172,7 +172,7 @@ function ReplyWidget( commentController, commentDetails, config ) {
mw.message( 'discussiontools-replywidget-transcluded', this.pageName ).parseDom() mw.message( 'discussiontools-replywidget-transcluded', this.pageName ).parseDom()
) ); ) );
} }
var $footerLinks = $( '<ul>' ).addClass( 'ext-discussiontools-ui-replyWidget-footer-links' ); const $footerLinks = $( '<ul>' ).addClass( 'ext-discussiontools-ui-replyWidget-footer-links' );
if ( mw.user.isNamed() ) { if ( mw.user.isNamed() ) {
$footerLinks.append( $footerLinks.append(
$( '<li>' ).append( $( '<li>' ).append(
@ -268,10 +268,10 @@ function ReplyWidget( commentController, commentDetails, config ) {
} }
if ( mw.user.isAnon() ) { if ( mw.user.isAnon() ) {
var msg = this.commentDetails.wouldAutoCreate ? const msg = this.commentDetails.wouldAutoCreate ?
'discussiontools-replywidget-autocreate-warning' : 'discussiontools-replywidget-autocreate-warning' :
'discussiontools-replywidget-anon-warning'; 'discussiontools-replywidget-anon-warning';
var returnTo = { const returnTo = {
returntoquery: window.location.search.slice( 1 ), returntoquery: window.location.search.slice( 1 ),
returnto: mw.config.get( 'wgPageName' ) returnto: mw.config.get( 'wgPageName' )
}; };
@ -310,7 +310,7 @@ function ReplyWidget( commentController, commentDetails, config ) {
this.editSummaryField.$body.append( this.$checkboxes ); this.editSummaryField.$body.append( this.$checkboxes );
// bind logging: // bind logging:
for ( var name in checkboxes.checkboxesByName ) { for ( const name in checkboxes.checkboxesByName ) {
checkboxes.checkboxesByName[ name ].$element.off( '.dtReply' ).on( 'click.dtReply', trackCheckbox.bind( this, name ) ); checkboxes.checkboxesByName[ name ].$element.off( '.dtReply' ).on( 'click.dtReply', trackCheckbox.bind( this, name ) );
} }
} }
@ -436,9 +436,9 @@ ReplyWidget.prototype.clearStorage = function () {
* Handle window scroll events * Handle window scroll events
*/ */
ReplyWidget.prototype.onWindowScroll = function () { ReplyWidget.prototype.onWindowScroll = function () {
var rect = this.$element[ 0 ].getBoundingClientRect(); const rect = this.$element[ 0 ].getBoundingClientRect();
var viewportHeight = window.visualViewport ? visualViewport.height : this.viewportScrollContainer.clientHeight; const viewportHeight = window.visualViewport ? visualViewport.height : this.viewportScrollContainer.clientHeight;
var floating = rect.bottom < 0 ? 'top' : const floating = rect.bottom < 0 ? 'top' :
( rect.top > viewportHeight ? 'bottom' : null ); ( rect.top > viewportHeight ? 'bottom' : null );
if ( floating !== this.floating ) { if ( floating !== this.floating ) {
@ -479,7 +479,7 @@ ReplyWidget.prototype.saveEditMode = function ( mode ) {
}; };
ReplyWidget.prototype.onAdvancedToggleClick = function () { ReplyWidget.prototype.onAdvancedToggleClick = function () {
var showAdvanced = !this.showAdvanced; const showAdvanced = !this.showAdvanced;
mw.track( 'visualEditorFeatureUse', { mw.track( 'visualEditorFeatureUse', {
feature: 'dtReply', feature: 'dtReply',
action: 'advanced-' + ( showAdvanced ? 'show' : 'hide' ) action: 'advanced-' + ( showAdvanced ? 'show' : 'hide' )
@ -490,20 +490,20 @@ ReplyWidget.prototype.onAdvancedToggleClick = function () {
this.toggleAdvanced( showAdvanced ); this.toggleAdvanced( showAdvanced );
if ( showAdvanced ) { if ( showAdvanced ) {
var summary = this.editSummaryInput.getValue(); const summary = this.editSummaryInput.getValue();
// If the current summary has not been edited yet, select the text following the autocomment to // If the current summary has not been edited yet, select the text following the autocomment to
// make it easier to change. Otherwise, move cursor to end. // make it easier to change. Otherwise, move cursor to end.
var selectFromIndex = summary.length; let selectFromIndex = summary.length;
if ( this.isNewTopic ) { if ( this.isNewTopic ) {
var titleText = this.commentController.sectionTitle.getValue(); const titleText = this.commentController.sectionTitle.getValue();
if ( summary === this.commentController.generateSummary( titleText ) ) { if ( summary === this.commentController.generateSummary( titleText ) ) {
selectFromIndex = titleText.length + '/* '.length + ' */ '.length; selectFromIndex = titleText.length + '/* '.length + ' */ '.length;
} }
} else { } else {
// Same as summary.endsWith( defaultReplyTrail ) // Same as summary.endsWith( defaultReplyTrail )
var defaultReplyTrail = '*/ ' + mw.msg( 'discussiontools-defaultsummary-reply' ); const defaultReplyTrail = '*/ ' + mw.msg( 'discussiontools-defaultsummary-reply' );
var endCommentIndex = summary.indexOf( defaultReplyTrail ); const endCommentIndex = summary.indexOf( defaultReplyTrail );
if ( endCommentIndex + defaultReplyTrail.length === summary.length ) { if ( endCommentIndex + defaultReplyTrail.length === summary.length ) {
selectFromIndex = endCommentIndex + 3; selectFromIndex = endCommentIndex + 3;
} }
@ -537,7 +537,7 @@ ReplyWidget.prototype.getEditSummary = function () {
}; };
ReplyWidget.prototype.onModeTabSelectChoose = function ( option ) { ReplyWidget.prototype.onModeTabSelectChoose = function ( option ) {
var mode = option.getData(); const mode = option.getData();
if ( mode === this.getMode() ) { if ( mode === this.getMode() ) {
return; return;
@ -560,7 +560,7 @@ ReplyWidget.prototype.switch = function ( mode ) {
return $.Deferred().reject().promise(); return $.Deferred().reject().promise();
} }
var promise; let promise;
this.setPending( true ); this.setPending( true );
switch ( mode ) { switch ( mode ) {
case 'source': case 'source':
@ -609,7 +609,7 @@ ReplyWidget.prototype.setup = function ( data ) {
} }
this.saveEditMode( this.getMode() ); this.saveEditMode( this.getMode() );
var summary = this.storage.get( 'summary' ) || data.editSummary; let summary = this.storage.get( 'summary' ) || data.editSummary;
if ( !summary ) { if ( !summary ) {
if ( this.isNewTopic ) { if ( this.isNewTopic ) {
@ -617,7 +617,7 @@ ReplyWidget.prototype.setup = function ( data ) {
// in NewTopicController#onSectionTitleChange // in NewTopicController#onSectionTitleChange
summary = ''; summary = '';
} else { } else {
var title = this.commentController.getThreadItem().getHeading().getLinkableTitle(); const title = this.commentController.getThreadItem().getHeading().getLinkableTitle();
summary = ( title ? '/* ' + title + ' */ ' : '' ) + summary = ( title ? '/* ' + title + ' */ ' : '' ) +
mw.msg( 'discussiontools-defaultsummary-reply' ); mw.msg( 'discussiontools-defaultsummary-reply' );
} }
@ -663,10 +663,10 @@ ReplyWidget.prototype.afterSetup = function () {
* @return {string} Form token * @return {string} Form token
*/ */
ReplyWidget.prototype.getFormToken = function () { ReplyWidget.prototype.getFormToken = function () {
var formToken = this.storage.get( 'formToken' ); let formToken = this.storage.get( 'formToken' );
if ( !formToken ) { if ( !formToken ) {
// See ApiBase::PARAM_MAX_CHARS in ApiDiscussionToolsEdit.php // See ApiBase::PARAM_MAX_CHARS in ApiDiscussionToolsEdit.php
var maxLength = 16; const maxLength = 16;
formToken = Math.random().toString( 36 ).slice( 2, maxLength + 2 ); formToken = Math.random().toString( 36 ).slice( 2, maxLength + 2 );
this.storage.set( 'formToken', formToken ); this.storage.set( 'formToken', formToken );
} }
@ -680,7 +680,7 @@ ReplyWidget.prototype.getFormToken = function () {
* @return {jQuery.Promise} Resolves if widget was torn down, rejects if it wasn't * @return {jQuery.Promise} Resolves if widget was torn down, rejects if it wasn't
*/ */
ReplyWidget.prototype.tryTeardown = function () { ReplyWidget.prototype.tryTeardown = function () {
var promise; let promise;
if ( !this.isEmpty() || ( this.isNewTopic && this.commentController.sectionTitle.getValue() ) ) { if ( !this.isEmpty() || ( this.isNewTopic && this.commentController.sectionTitle.getValue() ) ) {
promise = OO.ui.getWindowManager().openWindow( this.isNewTopic ? 'abandontopic' : 'abandoncomment' ) promise = OO.ui.getWindowManager().openWindow( this.isNewTopic ? 'abandontopic' : 'abandoncomment' )
@ -815,7 +815,7 @@ ReplyWidget.prototype.preparePreview = function ( wikitext ) {
wikitext = wikitext !== undefined ? wikitext : this.getValue(); wikitext = wikitext !== undefined ? wikitext : this.getValue();
wikitext = utils.htmlTrim( wikitext ); wikitext = utils.htmlTrim( wikitext );
var title = this.isNewTopic && this.commentController.sectionTitle.getValue(); const title = this.isNewTopic && this.commentController.sectionTitle.getValue();
if ( this.previewWikitext === wikitext && this.previewTitle === title ) { if ( this.previewWikitext === wikitext && this.previewTitle === title ) {
return $.Deferred().resolve().promise(); return $.Deferred().resolve().promise();
@ -828,7 +828,7 @@ ReplyWidget.prototype.preparePreview = function ( wikitext ) {
this.previewRequest = null; this.previewRequest = null;
} }
var parsePromise; let parsePromise;
if ( !wikitext ) { if ( !wikitext ) {
parsePromise = $.Deferred().resolve( null ).promise(); parsePromise = $.Deferred().resolve( null ).promise();
} else { } else {
@ -1029,7 +1029,7 @@ ReplyWidget.prototype.onNewCommentsCloseClick = function () {
* @return {OO.ui.MessageWidget} Message widget * @return {OO.ui.MessageWidget} Message widget
*/ */
ReplyWidget.prototype.createErrorMessage = function ( message ) { ReplyWidget.prototype.createErrorMessage = function ( message ) {
var errorMessage = new OO.ui.MessageWidget( { const errorMessage = new OO.ui.MessageWidget( {
type: 'error', type: 'error',
label: message, label: message,
classes: [ 'ext-discussiontools-ui-replyWidget-error' ] classes: [ 'ext-discussiontools-ui-replyWidget-error' ]
@ -1058,12 +1058,12 @@ ReplyWidget.prototype.onReplyClick = function () {
mw.track( 'editAttemptStep', { action: 'saveIntent' } ); mw.track( 'editAttemptStep', { action: 'saveIntent' } );
// TODO: When editing a transcluded page, VE API returning the page HTML is a waste, since we won't use it // TODO: When editing a transcluded page, VE API returning the page HTML is a waste, since we won't use it
var pageName = this.pageName; const pageName = this.pageName;
mw.track( 'editAttemptStep', { action: 'saveAttempt' } ); mw.track( 'editAttemptStep', { action: 'saveAttempt' } );
this.commentController.save( pageName ).fail( ( code, data ) => { this.commentController.save( pageName ).fail( ( code, data ) => {
// Compare to ve.init.mw.ArticleTargetEvents.js in VisualEditor. // Compare to ve.init.mw.ArticleTargetEvents.js in VisualEditor.
var typeMap = { const typeMap = {
badtoken: 'userBadToken', badtoken: 'userBadToken',
assertanonfailed: 'userNewUser', assertanonfailed: 'userNewUser',
assertuserfailed: 'userNewUser', assertuserfailed: 'userNewUser',

View file

@ -15,7 +15,7 @@ function ReplyWidgetPlain() {
ReplyWidgetPlain.super.apply( this, arguments ); ReplyWidgetPlain.super.apply( this, arguments );
if ( OO.ui.isMobile() ) { if ( OO.ui.isMobile() ) {
var toolFactory = new OO.ui.ToolFactory(), const toolFactory = new OO.ui.ToolFactory(),
toolGroupFactory = new OO.ui.ToolGroupFactory(); toolGroupFactory = new OO.ui.ToolGroupFactory();
toolFactory.register( mw.libs.ve.MWEditModeVisualTool ); toolFactory.register( mw.libs.ve.MWEditModeVisualTool );
@ -56,7 +56,7 @@ OO.inheritClass( ReplyWidgetPlain, require( './dt.ui.ReplyWidget.js' ) );
* @inheritdoc * @inheritdoc
*/ */
ReplyWidgetPlain.prototype.createReplyBodyWidget = function ( config ) { ReplyWidgetPlain.prototype.createReplyBodyWidget = function ( config ) {
var textInput = new OO.ui.MultilineTextInputWidget( Object.assign( { const textInput = new OO.ui.MultilineTextInputWidget( Object.assign( {
rows: 3, rows: 3,
// TODO: Fix upstream to support a value meaning no max limit (e.g. Infinity) // TODO: Fix upstream to support a value meaning no max limit (e.g. Infinity)
maxRows: 999, maxRows: 999,
@ -123,7 +123,7 @@ ReplyWidgetPlain.prototype.onInputChange = function () {
// Parent method // Parent method
ReplyWidgetPlain.super.prototype.onInputChange.apply( this, arguments ); ReplyWidgetPlain.super.prototype.onInputChange.apply( this, arguments );
var wikitext = this.getValue(); const wikitext = this.getValue();
this.storage.set( 'body', wikitext ); this.storage.set( 'body', wikitext );
}; };
@ -131,7 +131,7 @@ ReplyWidgetPlain.prototype.onInputChange = function () {
* @inheritdoc * @inheritdoc
*/ */
ReplyWidgetPlain.prototype.setup = function ( data ) { ReplyWidgetPlain.prototype.setup = function ( data ) {
var autosaveValue = this.storage.get( 'body' ); const autosaveValue = this.storage.get( 'body' );
data = data || {}; data = data || {};

View file

@ -73,7 +73,7 @@ ReplyWidgetVisual.prototype.clear = function ( preserveStorage ) {
* @inheritdoc * @inheritdoc
*/ */
ReplyWidgetVisual.prototype.isEmpty = function () { ReplyWidgetVisual.prototype.isEmpty = function () {
var surface = this.replyBodyWidget.target.getSurface(); const surface = this.replyBodyWidget.target.getSurface();
return !( surface && surface.getModel().getDocument().data.hasContent() ); return !( surface && surface.getModel().getDocument().data.hasContent() );
}; };
@ -90,11 +90,11 @@ ReplyWidgetVisual.prototype.getMode = function () {
* @inheritdoc * @inheritdoc
*/ */
ReplyWidgetVisual.prototype.setup = function ( data, suppressNotifications ) { ReplyWidgetVisual.prototype.setup = function ( data, suppressNotifications ) {
var target = this.replyBodyWidget.target; const target = this.replyBodyWidget.target;
data = data || {}; data = data || {};
var htmlOrDoc; let htmlOrDoc;
if ( this.storage.get( 'saveable' ) ) { if ( this.storage.get( 'saveable' ) ) {
htmlOrDoc = this.storage.get( 've-dochtml' ); htmlOrDoc = this.storage.get( 've-dochtml' );
target.recovered = true; target.recovered = true;
@ -152,7 +152,7 @@ ReplyWidgetVisual.prototype.teardown = function () {
* @inheritdoc * @inheritdoc
*/ */
ReplyWidgetVisual.prototype.focus = function () { ReplyWidgetVisual.prototype.focus = function () {
var targetWidget = this.replyBodyWidget; const targetWidget = this.replyBodyWidget;
setTimeout( () => { setTimeout( () => {
// Check surface still exists after timeout // Check surface still exists after timeout
if ( targetWidget.getSurface() ) { if ( targetWidget.getSurface() ) {

View file

@ -12,8 +12,8 @@ var
* @param {ThreadItem|ThreadItem[]} items Thread item(s) to highlight * @param {ThreadItem|ThreadItem[]} items Thread item(s) to highlight
*/ */
function Highlight( items ) { function Highlight( items ) {
var highlightNodes = []; const highlightNodes = [];
var ranges = []; const ranges = [];
this.topmostElement = null; this.topmostElement = null;
@ -22,12 +22,12 @@ function Highlight( items ) {
this.rootNode = items[ 0 ] ? items[ 0 ].rootNode : null; this.rootNode = items[ 0 ] ? items[ 0 ].rootNode : null;
items.forEach( ( item ) => { items.forEach( ( item ) => {
var $highlight = $( '<div>' ).addClass( 'ext-discussiontools-init-highlight' ); const $highlight = $( '<div>' ).addClass( 'ext-discussiontools-init-highlight' );
// We insert the highlight in the DOM near the thead item, so that it remains positioned correctly // We insert the highlight in the DOM near the thead item, so that it remains positioned correctly
// when it shifts (e.g. collapsing the table of contents), and disappears when it is hidden (e.g. // when it shifts (e.g. collapsing the table of contents), and disappears when it is hidden (e.g.
// opening visual editor). // opening visual editor).
var range = item.getRange(); const range = item.getRange();
// Support: Firefox // Support: Firefox
// The highlight node must be inserted after the start marker node (data-mw-comment-start), not // The highlight node must be inserted after the start marker node (data-mw-comment-start), not
// before, otherwise Node#getBoundingClientRect() returns wrong results. // before, otherwise Node#getBoundingClientRect() returns wrong results.
@ -35,7 +35,7 @@ function Highlight( items ) {
// If the item is a top-level comment wrapped in a frame, highlight outside that frame // If the item is a top-level comment wrapped in a frame, highlight outside that frame
if ( item.level === 1 ) { if ( item.level === 1 ) {
var coveredSiblings = utils.getFullyCoveredSiblings( item, item.rootNode ); const coveredSiblings = utils.getFullyCoveredSiblings( item, item.rootNode );
if ( coveredSiblings ) { if ( coveredSiblings ) {
range.setStartBefore( coveredSiblings[ 0 ] ); range.setStartBefore( coveredSiblings[ 0 ] );
range.setEndAfter( coveredSiblings[ coveredSiblings.length - 1 ] ); range.setEndAfter( coveredSiblings[ coveredSiblings.length - 1 ] );
@ -86,13 +86,13 @@ Highlight.prototype.update = function () {
width: '', width: '',
height: '' height: ''
} ); } );
var rootRect = this.rootNode.getBoundingClientRect(); const rootRect = this.rootNode.getBoundingClientRect();
this.topmostElement = null; this.topmostElement = null;
var topmostTop = Infinity; let topmostTop = Infinity;
this.ranges.forEach( ( range, i ) => { this.ranges.forEach( ( range, i ) => {
var $element = this.$element.eq( i ); const $element = this.$element.eq( i );
var baseRect = $element[ 0 ].getBoundingClientRect(); const baseRect = $element[ 0 ].getBoundingClientRect();
var rect = RangeFix.getBoundingClientRect( range ); const rect = RangeFix.getBoundingClientRect( range );
// rect may be null if the range is in a detached or hidden node // rect may be null if the range is in a detached or hidden node
if ( rect ) { if ( rect ) {
// Draw the highlight over the full width of the page, except for very short comments // Draw the highlight over the full width of the page, except for very short comments
@ -104,9 +104,9 @@ Highlight.prototype.update = function () {
// //
// It seems difficult to distinguish the floating boxes from comments that are just // It seems difficult to distinguish the floating boxes from comments that are just
// very short or very deeply indented, and this seems to work well enough in practice. // very short or very deeply indented, and this seems to work well enough in practice.
var useFullWidth = rect.width > rootRect.width / 3; const useFullWidth = rect.width > rootRect.width / 3;
var headingTopAdj = 0; let headingTopAdj = 0;
if ( if (
featuresEnabled.visualenhancements && featuresEnabled.visualenhancements &&
$element.closest( '.ext-discussiontools-init-section' ).length $element.closest( '.ext-discussiontools-init-section' ).length
@ -116,10 +116,10 @@ Highlight.prototype.update = function () {
headingTopAdj = 10; headingTopAdj = 10;
} }
var top = rect.top - baseRect.top; const top = rect.top - baseRect.top;
var width = rect.width; let width = rect.width;
var height = rect.height; const height = rect.height;
var left, right; let left, right;
if ( $element.css( 'direction' ) === 'ltr' ) { if ( $element.css( 'direction' ) === 'ltr' ) {
left = rect.left - baseRect.left; left = rect.left - baseRect.left;
if ( useFullWidth ) { if ( useFullWidth ) {
@ -131,7 +131,7 @@ Highlight.prototype.update = function () {
width = rootRect.width - ( ( rootRect.left + rootRect.width ) - ( rect.left + rect.width ) ); width = rootRect.width - ( ( rootRect.left + rootRect.width ) - ( rect.left + rect.width ) );
} }
} }
var padding = 5; const padding = 5;
$element.css( { $element.css( {
'margin-top': top - padding + headingTopAdj, 'margin-top': top - padding + headingTopAdj,
'margin-left': left !== undefined ? left - padding : '', 'margin-left': left !== undefined ? left - padding : '',
@ -179,11 +179,11 @@ function highlightTargetComment( threadItemSet, noScroll ) {
highlightedTarget = null; highlightedTarget = null;
} }
var targetElement = mw.util.getTargetFromFragment(); const targetElement = mw.util.getTargetFromFragment();
if ( targetElement && targetElement.hasAttribute( 'data-mw-comment-start' ) ) { if ( targetElement && targetElement.hasAttribute( 'data-mw-comment-start' ) ) {
var threadItemId = targetElement.getAttribute( 'id' ); const threadItemId = targetElement.getAttribute( 'id' );
var threadItem = threadItemSet.findCommentById( targetElement.getAttribute( 'id' ) ); const threadItem = threadItemSet.findCommentById( targetElement.getAttribute( 'id' ) );
if ( threadItem ) { if ( threadItem ) {
highlightedTarget = new Highlight( threadItem ); highlightedTarget = new Highlight( threadItem );
highlightedTarget.$element.addClass( 'ext-discussiontools-init-targetcomment' ); highlightedTarget.$element.addClass( 'ext-discussiontools-init-targetcomment' );
@ -203,7 +203,7 @@ function highlightTargetComment( threadItemSet, noScroll ) {
}; };
} }
var url = new URL( location.href ); const url = new URL( location.href );
return highlightNewComments( return highlightNewComments(
threadItemSet, threadItemSet,
noScroll, noScroll,
@ -225,11 +225,11 @@ function highlightTargetComment( threadItemSet, noScroll ) {
* @param {string} threadItemId Thread item ID (NEW_TOPIC_COMMENT_ID for the a new topic) * @param {string} threadItemId Thread item ID (NEW_TOPIC_COMMENT_ID for the a new topic)
*/ */
function highlightPublishedComment( threadItemSet, threadItemId ) { function highlightPublishedComment( threadItemSet, threadItemId ) {
var highlightComments = []; const highlightComments = [];
if ( threadItemId === utils.NEW_TOPIC_COMMENT_ID ) { if ( threadItemId === utils.NEW_TOPIC_COMMENT_ID ) {
// Highlight the last comment on the page // Highlight the last comment on the page
var lastComment = threadItemSet.threadItems[ threadItemSet.threadItems.length - 1 ]; const lastComment = threadItemSet.threadItems[ threadItemSet.threadItems.length - 1 ];
lastHighlightedPublishedComment = lastComment; lastHighlightedPublishedComment = lastComment;
highlightComments.push( lastComment ); highlightComments.push( lastComment );
@ -244,15 +244,15 @@ function highlightPublishedComment( threadItemSet, threadItemId ) {
lastHighlightedPublishedComment = lastComment.parent; lastHighlightedPublishedComment = lastComment.parent;
// Change URL to point to this section, like the old section=new wikitext editor does. // Change URL to point to this section, like the old section=new wikitext editor does.
// This also expands collapsed sections on mobile (T301840). // This also expands collapsed sections on mobile (T301840).
var sectionTitle = lastHighlightedPublishedComment.getLinkableTitle(); const sectionTitle = lastHighlightedPublishedComment.getLinkableTitle();
var urlFragment = mw.util.escapeIdForLink( sectionTitle ); const urlFragment = mw.util.escapeIdForLink( sectionTitle );
// Navigate to fragment without scrolling // Navigate to fragment without scrolling
location.hash = '#' + urlFragment + '-DoesNotExist-DiscussionToolsHack'; location.hash = '#' + urlFragment + '-DoesNotExist-DiscussionToolsHack';
history.replaceState( null, '', '#' + urlFragment ); history.replaceState( null, '', '#' + urlFragment );
} }
} else { } else {
// Find the comment we replied to, then highlight the last reply // Find the comment we replied to, then highlight the last reply
var repliedToComment = threadItemSet.threadItemsById[ threadItemId ]; const repliedToComment = threadItemSet.threadItemsById[ threadItemId ];
highlightComments.push( repliedToComment.replies[ repliedToComment.replies.length - 1 ] ); highlightComments.push( repliedToComment.replies[ repliedToComment.replies.length - 1 ] );
lastHighlightedPublishedComment = highlightComments[ 0 ]; lastHighlightedPublishedComment = highlightComments[ 0 ];
} }
@ -260,7 +260,7 @@ function highlightPublishedComment( threadItemSet, threadItemId ) {
// We may have changed the location hash on mobile, so wait for that to cause // We may have changed the location hash on mobile, so wait for that to cause
// the section to expand before drawing the highlight. // the section to expand before drawing the highlight.
setTimeout( () => { setTimeout( () => {
var highlight = new Highlight( highlightComments ); const highlight = new Highlight( highlightComments );
highlight.$element.addClass( 'ext-discussiontools-init-publishedcomment' ); highlight.$element.addClass( 'ext-discussiontools-init-publishedcomment' );
// Show a highlight with the same timing as the post-edit message (mediawiki.action.view.postEdit): // Show a highlight with the same timing as the post-edit message (mediawiki.action.view.postEdit):
@ -314,12 +314,12 @@ function highlightNewComments( threadItemSet, noScroll, newCommentIds, options )
options = options || {}; options = options || {};
if ( options.newCommentsSinceId ) { if ( options.newCommentsSinceId ) {
var newCommentsSince = threadItemSet.findCommentById( options.newCommentsSinceId ); const newCommentsSince = threadItemSet.findCommentById( options.newCommentsSinceId );
if ( newCommentsSince && newCommentsSince instanceof CommentItem ) { if ( newCommentsSince && newCommentsSince instanceof CommentItem ) {
var sinceTimestamp = newCommentsSince.timestamp; const sinceTimestamp = newCommentsSince.timestamp;
var threadItems; let threadItems;
if ( options.inThread ) { if ( options.inThread ) {
var heading = newCommentsSince.getSubscribableHeading() || newCommentsSince.getHeading(); const heading = newCommentsSince.getSubscribableHeading() || newCommentsSince.getHeading();
threadItems = heading.getThreadItemsBelow(); threadItems = heading.getThreadItemsBelow();
} else { } else {
threadItems = threadItemSet.getCommentItems(); threadItems = threadItemSet.getCommentItems();
@ -332,8 +332,8 @@ function highlightNewComments( threadItemSet, noScroll, newCommentIds, options )
if ( options.sinceThread ) { if ( options.sinceThread ) {
// Check that we are in a thread that is newer than `sinceTimestamp`. // Check that we are in a thread that is newer than `sinceTimestamp`.
// Thread age is determined by looking at getOldestReply. // Thread age is determined by looking at getOldestReply.
var itemHeading = threadItem.getSubscribableHeading() || threadItem.getHeading(); const itemHeading = threadItem.getSubscribableHeading() || threadItem.getHeading();
var oldestReply = itemHeading.getOldestReply(); const oldestReply = itemHeading.getOldestReply();
if ( !( oldestReply && oldestReply.timestamp >= sinceTimestamp ) ) { if ( !( oldestReply && oldestReply.timestamp >= sinceTimestamp ) ) {
return; return;
} }
@ -344,7 +344,7 @@ function highlightNewComments( threadItemSet, noScroll, newCommentIds, options )
} }
} }
var comments; let comments;
if ( newCommentIds.length ) { if ( newCommentIds.length ) {
comments = newCommentIds comments = newCommentIds
.map( ( id ) => threadItemSet.findCommentById( id ) ) .map( ( id ) => threadItemSet.findCommentById( id ) )
@ -372,9 +372,9 @@ function highlightNewComments( threadItemSet, noScroll, newCommentIds, options )
* @param {ThreadItemSet} threadItemSet * @param {ThreadItemSet} threadItemSet
*/ */
function clearHighlightTargetComment( threadItemSet ) { function clearHighlightTargetComment( threadItemSet ) {
var url = new URL( location.href ); const url = new URL( location.href );
var targetElement = mw.util.getTargetFromFragment(); const targetElement = mw.util.getTargetFromFragment();
if ( targetElement && targetElement.hasAttribute( 'data-mw-comment-start' ) ) { if ( targetElement && targetElement.hasAttribute( 'data-mw-comment-start' ) ) {
// Clear the hash from the URL, triggering the 'hashchange' event and updating the :target // Clear the hash from the URL, triggering the 'hashchange' event and updating the :target

View file

@ -8,7 +8,7 @@ var isIos = /ipad|iphone|ipod/i.test( navigator.userAgent );
$( document.body ).toggleClass( 'ext-discussiontools-init-ios', isIos ); $( document.body ).toggleClass( 'ext-discussiontools-init-ios', isIos );
function onViewportChange() { function onViewportChange() {
var isKeyboardOpen; let isKeyboardOpen;
if ( isIos ) { if ( isIos ) {
isKeyboardOpen = visualViewport.height < viewportScrollContainer.clientHeight; isKeyboardOpen = visualViewport.height < viewportScrollContainer.clientHeight;
@ -32,20 +32,20 @@ function init( $container ) {
if ( !viewportScrollContainer && window.visualViewport ) { if ( !viewportScrollContainer && window.visualViewport ) {
viewportScrollContainer = OO.ui.Element.static.getClosestScrollableContainer( document.body ); viewportScrollContainer = OO.ui.Element.static.getClosestScrollableContainer( document.body );
initialClientHeight = viewportScrollContainer.clientHeight; initialClientHeight = viewportScrollContainer.clientHeight;
var onViewportChangeThrottled = OO.ui.throttle( onViewportChange, 100 ); const onViewportChangeThrottled = OO.ui.throttle( onViewportChange, 100 );
$( visualViewport ).on( 'resize', onViewportChangeThrottled ); $( visualViewport ).on( 'resize', onViewportChangeThrottled );
} }
// Mobile overflow menu // Mobile overflow menu
var $ledeContent = $container.find( '.mf-section-0' ).children( ':not( .ext-discussiontools-emptystate )' ) const $ledeContent = $container.find( '.mf-section-0' ).children( ':not( .ext-discussiontools-emptystate )' )
// On non-existent pages MobileFrontend wrapping isn't there // On non-existent pages MobileFrontend wrapping isn't there
.add( $container.find( '.mw-talkpageheader' ) ); .add( $container.find( '.mw-talkpageheader' ) );
var $ledeButton = $container.find( '.ext-discussiontools-init-lede-button' ); const $ledeButton = $container.find( '.ext-discussiontools-init-lede-button' );
if ( $ledeButton.length ) { if ( $ledeButton.length ) {
var windowManager = OO.ui.getWindowManager(); const windowManager = OO.ui.getWindowManager();
if ( !ledeSectionDialog ) { if ( !ledeSectionDialog ) {
var LedeSectionDialog = require( './LedeSectionDialog.js' ); const LedeSectionDialog = require( './LedeSectionDialog.js' );
ledeSectionDialog = new LedeSectionDialog(); ledeSectionDialog = new LedeSectionDialog();
windowManager.addWindows( [ ledeSectionDialog ] ); windowManager.addWindows( [ ledeSectionDialog ] );
} }
@ -61,7 +61,7 @@ function init( $container ) {
} }
// eslint-disable-next-line no-jquery/no-global-selector // eslint-disable-next-line no-jquery/no-global-selector
var $newTopicWrapper = $( '.ext-discussiontools-init-new-topic' ); const $newTopicWrapper = $( '.ext-discussiontools-init-new-topic' );
if ( if (
!newTopicButton && !newTopicButton &&
@ -72,16 +72,16 @@ function init( $container ) {
newTopicButton = OO.ui.infuse( $( '.ext-discussiontools-init-new-topic-button' ) ); newTopicButton = OO.ui.infuse( $( '.ext-discussiontools-init-new-topic-button' ) );
// For compatibility with MobileWebUIActionsTracking logging (T295490) // For compatibility with MobileWebUIActionsTracking logging (T295490)
newTopicButton.$element.attr( 'data-event-name', 'talkpage.add-topic' ); newTopicButton.$element.attr( 'data-event-name', 'talkpage.add-topic' );
var $scrollContainer = $( OO.ui.Element.static.getClosestScrollableContainer( document.body ) ); const $scrollContainer = $( OO.ui.Element.static.getClosestScrollableContainer( document.body ) );
var $scrollListener = $scrollContainer.is( 'html, body' ) ? $( OO.ui.Element.static.getWindow( $scrollContainer[ 0 ] ) ) : $scrollContainer; const $scrollListener = $scrollContainer.is( 'html, body' ) ? $( OO.ui.Element.static.getWindow( $scrollContainer[ 0 ] ) ) : $scrollContainer;
var lastScrollTop = $scrollContainer.scrollTop(); let lastScrollTop = $scrollContainer.scrollTop();
var wasScrollDown = null; let wasScrollDown = null;
var $body = $( document.body ); const $body = $( document.body );
// This block of code is only run once, so we don't need to remove this listener ever // This block of code is only run once, so we don't need to remove this listener ever
$scrollListener[ 0 ].addEventListener( 'scroll', OO.ui.throttle( () => { $scrollListener[ 0 ].addEventListener( 'scroll', OO.ui.throttle( () => {
// Round negative values up to 0 to ignore iOS scroll bouncing (T323400) // Round negative values up to 0 to ignore iOS scroll bouncing (T323400)
var scrollTop = Math.max( $scrollContainer.scrollTop(), 0 ); const scrollTop = Math.max( $scrollContainer.scrollTop(), 0 );
var isScrollDown = scrollTop > lastScrollTop; const isScrollDown = scrollTop > lastScrollTop;
if ( isScrollDown !== wasScrollDown ) { if ( isScrollDown !== wasScrollDown ) {
if ( !isScrollDown ) { if ( !isScrollDown ) {
$newTopicWrapper.css( 'transition', 'none' ); $newTopicWrapper.css( 'transition', 'none' );
@ -97,7 +97,7 @@ function init( $container ) {
} ); } );
} }
var observer = new IntersectionObserver( const observer = new IntersectionObserver(
( ( entries ) => { ( ( entries ) => {
$newTopicWrapper.toggleClass( 'ext-discussiontools-init-new-topic-pinned', entries[ 0 ].intersectionRatio === 1 ); $newTopicWrapper.toggleClass( 'ext-discussiontools-init-new-topic-pinned', entries[ 0 ].intersectionRatio === 1 );
} ), } ),

View file

@ -24,7 +24,7 @@ function sanitizeWikitextLinebreaks( wikitext ) {
* @param {HTMLElement} linkNode Reply link * @param {HTMLElement} linkNode Reply link
*/ */
function addReplyLink( comment, linkNode ) { function addReplyLink( comment, linkNode ) {
var target = comment.range.endContainer; const target = comment.range.endContainer;
// Insert the link before trailing whitespace. // Insert the link before trailing whitespace.
// In the MediaWiki parser output, <ul>/<dl> nodes are preceded by a newline. Normally it isn't // In the MediaWiki parser output, <ul>/<dl> nodes are preceded by a newline. Normally it isn't
@ -52,7 +52,7 @@ function addReplyLink( comment, linkNode ) {
* @return {HTMLElement} * @return {HTMLElement}
*/ */
function addListItem( comment, replyIndentation ) { function addListItem( comment, replyIndentation ) {
var listTypeMap = { const listTypeMap = {
li: 'ul', li: 'ul',
dd: 'dl' dd: 'dl'
}; };
@ -62,13 +62,13 @@ function addListItem( comment, replyIndentation ) {
// (or in other words, all replies, and replies to replies, and so on) // (or in other words, all replies, and replies to replies, and so on)
// 3. Add comment with level of the given comment plus 1 // 3. Add comment with level of the given comment plus 1
var curComment = comment; let curComment = comment;
while ( curComment.replies.length ) { while ( curComment.replies.length ) {
curComment = curComment.replies[ curComment.replies.length - 1 ]; curComment = curComment.replies[ curComment.replies.length - 1 ];
} }
// Tag names for lists and items we're going to insert // Tag names for lists and items we're going to insert
var itemType; let itemType;
if ( replyIndentation === 'invisible' ) { if ( replyIndentation === 'invisible' ) {
itemType = 'dd'; itemType = 'dd';
} else if ( replyIndentation === 'bullet' ) { } else if ( replyIndentation === 'bullet' ) {
@ -76,16 +76,16 @@ function addListItem( comment, replyIndentation ) {
} else { } else {
throw new Error( "Invalid reply indentation syntax '" + replyIndentation + "'" ); throw new Error( "Invalid reply indentation syntax '" + replyIndentation + "'" );
} }
var listType = listTypeMap[ itemType ]; const listType = listTypeMap[ itemType ];
var desiredLevel = comment.level + 1; const desiredLevel = comment.level + 1;
var target = curComment.range.endContainer; let target = curComment.range.endContainer;
// target is a text node or an inline element at the end of a "paragraph" (not necessarily paragraph node). // target is a text node or an inline element at the end of a "paragraph" (not necessarily paragraph node).
// First, we need to find a block-level parent that we can mess with. // First, we need to find a block-level parent that we can mess with.
// If we can't find a surrounding list item or paragraph (e.g. maybe we're inside a table cell // If we can't find a surrounding list item or paragraph (e.g. maybe we're inside a table cell
// or something), take the parent node and hope for the best. // or something), take the parent node and hope for the best.
var parent = utils.closestElement( target, [ 'li', 'dd', 'p' ] ) || target.parentNode; let parent = utils.closestElement( target, [ 'li', 'dd', 'p' ] ) || target.parentNode;
while ( target.parentNode !== parent ) { while ( target.parentNode !== parent ) {
target = target.parentNode; target = target.parentNode;
} }
@ -95,9 +95,9 @@ function addListItem( comment, replyIndentation ) {
// If the comment is fully covered by some wrapper element, insert replies outside that wrapper. // If the comment is fully covered by some wrapper element, insert replies outside that wrapper.
// This will often just be a paragraph node (<p>), but it can be a <div> or <table> that serves // This will often just be a paragraph node (<p>), but it can be a <div> or <table> that serves
// as some kind of a fancy frame, which are often used for barnstars and announcements. // as some kind of a fancy frame, which are often used for barnstars and announcements.
var excludedWrapper = utils.closestElement( target, [ 'section' ] ) || const excludedWrapper = utils.closestElement( target, [ 'section' ] ) ||
curComment.rootNode; curComment.rootNode;
var covered = utils.getFullyCoveredSiblings( curComment, excludedWrapper ); const covered = utils.getFullyCoveredSiblings( curComment, excludedWrapper );
if ( curComment.level === 1 && covered ) { if ( curComment.level === 1 && covered ) {
target = covered[ covered.length - 1 ]; target = covered[ covered.length - 1 ];
parent = target.parentNode; parent = target.parentNode;
@ -105,7 +105,7 @@ function addListItem( comment, replyIndentation ) {
// If the comment is in a transclusion, insert replies after the transclusion. (T313100) // If the comment is in a transclusion, insert replies after the transclusion. (T313100)
// This method should never be called in cases where that would be a bad idea. // This method should never be called in cases where that would be a bad idea.
var transclusionNode = utils.getTranscludedFromElement( target ); let transclusionNode = utils.getTranscludedFromElement( target );
if ( transclusionNode ) { if ( transclusionNode ) {
while ( while (
transclusionNode.nextSibling && transclusionNode.nextSibling &&
@ -142,9 +142,9 @@ function addListItem( comment, replyIndentation ) {
// Instead of just using curComment.level, consider indentation of lists within the // Instead of just using curComment.level, consider indentation of lists within the
// comment (T252702) // comment (T252702)
var curLevel = utils.getIndentLevel( target, curComment.rootNode ) + 1; let curLevel = utils.getIndentLevel( target, curComment.rootNode ) + 1;
var item, list; let item, list;
if ( desiredLevel === 1 ) { if ( desiredLevel === 1 ) {
// Special handling for top-level comments // Special handling for top-level comments
item = target.ownerDocument.createElement( 'div' ); item = target.ownerDocument.createElement( 'div' );
@ -160,7 +160,7 @@ function addListItem( comment, replyIndentation ) {
// which appear at the end of the line in wikitext outside the paragraph, // which appear at the end of the line in wikitext outside the paragraph,
// but we usually shouldn't insert replies between the paragraph and such comments. (T257651) // but we usually shouldn't insert replies between the paragraph and such comments. (T257651)
// Skip over comments and whitespace, but only update target when skipping past comments. // Skip over comments and whitespace, but only update target when skipping past comments.
var pointer = target; let pointer = target;
while ( while (
pointer.nextSibling && ( pointer.nextSibling && (
utils.isRenderingTransparentNode( pointer.nextSibling ) || utils.isRenderingTransparentNode( pointer.nextSibling ) ||
@ -196,7 +196,7 @@ function addListItem( comment, replyIndentation ) {
} else { } else {
// Split the ancestor nodes after the target to decrease nesting. // Split the ancestor nodes after the target to decrease nesting.
var newNode; let newNode;
do { do {
if ( !target || !parent ) { if ( !target || !parent ) {
throw new Error( 'Can not decrease nesting any more' ); throw new Error( 'Can not decrease nesting any more' );
@ -310,7 +310,7 @@ function removeAddedListItem( node ) {
* @param {DocumentFragment|null} fragment Containing document fragment if list has no parent * @param {DocumentFragment|null} fragment Containing document fragment if list has no parent
*/ */
function unwrapList( list, fragment ) { function unwrapList( list, fragment ) {
var doc = list.ownerDocument, let doc = list.ownerDocument,
container = fragment || list.parentNode, container = fragment || list.parentNode,
referenceNode = list; referenceNode = list;
@ -330,11 +330,11 @@ function unwrapList( list, fragment ) {
return; return;
} }
var insertBefore; let insertBefore;
while ( list.firstChild ) { while ( list.firstChild ) {
if ( list.firstChild.nodeType === Node.ELEMENT_NODE ) { if ( list.firstChild.nodeType === Node.ELEMENT_NODE ) {
// Move <dd> contents to <p> // Move <dd> contents to <p>
var p = doc.createElement( 'p' ); let p = doc.createElement( 'p' );
while ( list.firstChild.firstChild ) { while ( list.firstChild.firstChild ) {
// If contents is a block element, place outside the paragraph // If contents is a block element, place outside the paragraph
// and start a new paragraph after // and start a new paragraph after
@ -375,7 +375,7 @@ function unwrapList( list, fragment ) {
* @return {HTMLElement} * @return {HTMLElement}
*/ */
function addSiblingListItem( previousItem ) { function addSiblingListItem( previousItem ) {
var listItem = previousItem.ownerDocument.createElement( previousItem.tagName ); const listItem = previousItem.ownerDocument.createElement( previousItem.tagName );
previousItem.parentNode.insertBefore( listItem, previousItem.nextSibling ); previousItem.parentNode.insertBefore( listItem, previousItem.nextSibling );
return listItem; return listItem;
} }

View file

@ -8,14 +8,14 @@ function init( $container, pageThreads ) {
mw.loader.using( [ 'oojs-ui-widgets', 'oojs-ui.styles.icons-editing-core' ] ).then( () => { mw.loader.using( [ 'oojs-ui-widgets', 'oojs-ui.styles.icons-editing-core' ] ).then( () => {
$container.find( '.ext-discussiontools-init-section-overflowMenuButton' ).each( ( i, button ) => { $container.find( '.ext-discussiontools-init-section-overflowMenuButton' ).each( ( i, button ) => {
// Comment ellipsis // Comment ellipsis
var $threadMarker = $( button ).closest( '[data-mw-thread-id]' ); let $threadMarker = $( button ).closest( '[data-mw-thread-id]' );
if ( !$threadMarker.length ) { if ( !$threadMarker.length ) {
// Heading ellipsis // Heading ellipsis
$threadMarker = $( button ).closest( '.ext-discussiontools-init-section' ).find( '[data-mw-thread-id]' ); $threadMarker = $( button ).closest( '.ext-discussiontools-init-section' ).find( '[data-mw-thread-id]' );
} }
var threadItem = pageThreads.findCommentById( $threadMarker.data( 'mw-thread-id' ) ); const threadItem = pageThreads.findCommentById( $threadMarker.data( 'mw-thread-id' ) );
var buttonMenu = OO.ui.infuse( button, { const buttonMenu = OO.ui.infuse( button, {
$overlay: true, $overlay: true,
menu: { menu: {
classes: [ 'ext-discussiontools-init-section-overflowMenu' ], classes: [ 'ext-discussiontools-init-section-overflowMenu' ],
@ -24,13 +24,13 @@ function init( $container, pageThreads ) {
} ); } );
mw.loader.using( buttonMenu.getData().resourceLoaderModules || [] ).then( () => { mw.loader.using( buttonMenu.getData().resourceLoaderModules || [] ).then( () => {
var itemConfigs = buttonMenu.getData().itemConfigs; const itemConfigs = buttonMenu.getData().itemConfigs;
if ( !itemConfigs ) { if ( !itemConfigs ) {
// We should never have missing itemConfigs, but if this happens, hide the empty menu // We should never have missing itemConfigs, but if this happens, hide the empty menu
buttonMenu.toggle( false ); buttonMenu.toggle( false );
return; return;
} }
var overflowMenuItemWidgets = itemConfigs.map( ( itemConfig ) => new OO.ui.MenuOptionWidget( itemConfig ) ); const overflowMenuItemWidgets = itemConfigs.map( ( itemConfig ) => new OO.ui.MenuOptionWidget( itemConfig ) );
buttonMenu.getMenu().addItems( overflowMenuItemWidgets ); buttonMenu.getMenu().addItems( overflowMenuItemWidgets );
buttonMenu.getMenu().items.forEach( ( menuItem ) => { buttonMenu.getMenu().items.forEach( ( menuItem ) => {
mw.hook( 'discussionToolsOverflowMenuOnAddItem' ).fire( menuItem.getData().id, menuItem, threadItem ); mw.hook( 'discussionToolsOverflowMenuOnAddItem' ).fire( menuItem.getData().id, menuItem, threadItem );

View file

@ -5,10 +5,10 @@ function init( $pageContainer ) {
} }
function copyLink( link ) { function copyLink( link ) {
var $win = $( window ); const $win = $( window );
var scrollTop = $win.scrollTop(); const scrollTop = $win.scrollTop();
var $tmpInput = $( '<input>' ) const $tmpInput = $( '<input>' )
.val( link ) .val( link )
.addClass( 'noime' ) .addClass( 'noime' )
.css( { .css( {
@ -18,7 +18,7 @@ function copyLink( link ) {
.appendTo( 'body' ) .appendTo( 'body' )
.trigger( 'focus' ); .trigger( 'focus' );
$tmpInput[ 0 ].setSelectionRange( 0, link.length ); $tmpInput[ 0 ].setSelectionRange( 0, link.length );
var copied; let copied;
try { try {
copied = document.execCommand( 'copy' ); copied = document.execCommand( 'copy' );
} catch ( err ) { } catch ( err ) {

View file

@ -74,7 +74,7 @@ function updateSubscribeButton( button, state ) {
* @return {jQuery.Promise} Promise which resolves after change of state * @return {jQuery.Promise} Promise which resolves after change of state
*/ */
function changeSubscription( title, commentName, subscribe, isNewTopics ) { function changeSubscription( title, commentName, subscribe, isNewTopics ) {
var promise = api.postWithToken( 'csrf', { const promise = api.postWithToken( 'csrf', {
action: 'discussiontoolssubscribe', action: 'discussiontoolssubscribe',
page: title, page: title,
commentname: commentName, commentname: commentName,
@ -148,19 +148,19 @@ function initTopicSubscriptions( $container, threadItemSet ) {
$container.find( '.ext-discussiontools-init-section-subscribeButton' ).each( ( i, element ) => { $container.find( '.ext-discussiontools-init-section-subscribeButton' ).each( ( i, element ) => {
// These attributes will be lost when infusing // These attributes will be lost when infusing
// TODO: Could also be fixed by subclassing ButtonWidget in PHP // TODO: Could also be fixed by subclassing ButtonWidget in PHP
var subscribedStateTemp = getSubscribedStateFromElement( element ); const subscribedStateTemp = getSubscribedStateFromElement( element );
var id = $( element ).closest( '.ext-discussiontools-init-section' ) const id = $( element ).closest( '.ext-discussiontools-init-section' )
.find( '[data-mw-comment-start]' ).attr( 'id' ); .find( '[data-mw-comment-start]' ).attr( 'id' );
var headingItem = threadItemSet.findCommentById( id ); const headingItem = threadItemSet.findCommentById( id );
if ( !( headingItem instanceof HeadingItem ) ) { if ( !( headingItem instanceof HeadingItem ) ) {
// This should never happen // This should never happen
return; return;
} }
var name = headingItem.name; const name = headingItem.name;
var button = OO.ui.infuse( element ); const button = OO.ui.infuse( element );
buttonsByName[ name ] = button; buttonsByName[ name ] = button;
// Restore data attribute // Restore data attribute
@ -168,11 +168,11 @@ function initTopicSubscriptions( $container, threadItemSet ) {
button.$element[ 0 ].setAttribute( 'data-mw-subscribed', String( subscribedStateTemp ) ); button.$element[ 0 ].setAttribute( 'data-mw-subscribed', String( subscribedStateTemp ) );
} }
var title = mw.config.get( 'wgRelevantPageName' ) + '#' + headingItem.getLinkableTitle(); const title = mw.config.get( 'wgRelevantPageName' ) + '#' + headingItem.getLinkableTitle();
button.on( 'click', () => { button.on( 'click', () => {
// Get latest subscribedState // Get latest subscribedState
var subscribedState = getSubscribedStateFromElement( button.$element[ 0 ] ); const subscribedState = getSubscribedStateFromElement( button.$element[ 0 ] );
button.setDisabled( true ); button.setDisabled( true );
changeSubscription( title, name, !subscribedState ) changeSubscription( title, name, !subscribedState )
@ -187,18 +187,18 @@ function initTopicSubscriptions( $container, threadItemSet ) {
// Subscription links (no visual enhancements) // Subscription links (no visual enhancements)
$container.find( '.ext-discussiontools-init-section-subscribe-link' ).each( ( i, link ) => { $container.find( '.ext-discussiontools-init-section-subscribe-link' ).each( ( i, link ) => {
var $link = $( link ); const $link = $( link );
var id = $link.closest( '.ext-discussiontools-init-section' ) const id = $link.closest( '.ext-discussiontools-init-section' )
.find( '[data-mw-comment-start]' ).attr( 'id' ); .find( '[data-mw-comment-start]' ).attr( 'id' );
var headingItem = threadItemSet.findCommentById( id ); const headingItem = threadItemSet.findCommentById( id );
if ( !( headingItem instanceof HeadingItem ) ) { if ( !( headingItem instanceof HeadingItem ) ) {
// This should never happen // This should never happen
return; return;
} }
var itemName = headingItem.name; const itemName = headingItem.name;
var title = mw.config.get( 'wgRelevantPageName' ) + '#' + headingItem.getLinkableTitle(); const title = mw.config.get( 'wgRelevantPageName' ) + '#' + headingItem.getLinkableTitle();
linksByName[ itemName ] = link; linksByName[ itemName ] = link;
@ -215,7 +215,7 @@ function initTopicSubscriptions( $container, threadItemSet ) {
e.preventDefault(); e.preventDefault();
// Get latest subscribedState // Get latest subscribedState
var subscribedState = getSubscribedStateFromElement( $link[ 0 ] ); const subscribedState = getSubscribedStateFromElement( $link[ 0 ] );
$link.addClass( 'ext-discussiontools-init-section-subscribe-link-pending' ); $link.addClass( 'ext-discussiontools-init-section-subscribe-link-pending' );
changeSubscription( title, itemName, !subscribedState ) changeSubscription( title, itemName, !subscribedState )
@ -241,7 +241,7 @@ function initTopicSubscriptions( $container, threadItemSet ) {
* page actions like live-preview can still reach this point. * page actions like live-preview can still reach this point.
*/ */
function initNewTopicsSubscription() { function initNewTopicsSubscription() {
var $button, $label, $icon; let $button, $label, $icon;
initApi(); initApi();
@ -252,7 +252,7 @@ function initNewTopicsSubscription() {
$icon = $button.find( '.minerva-icon' ); $icon = $button.find( '.minerva-icon' );
// HACK: We can't set data-mw-subscribed intially in Minerva, so work it out from the icon // HACK: We can't set data-mw-subscribed intially in Minerva, so work it out from the icon
// eslint-disable-next-line no-jquery/no-class-state // eslint-disable-next-line no-jquery/no-class-state
var initialState = $icon.hasClass( 'minerva-icon--bell' ) ? STATE_SUBSCRIBED : STATE_UNSUBSCRIBED; const initialState = $icon.hasClass( 'minerva-icon--bell' ) ? STATE_SUBSCRIBED : STATE_UNSUBSCRIBED;
$button.attr( 'data-mw-subscribed', String( initialState ) ); $button.attr( 'data-mw-subscribed', String( initialState ) );
} else { } else {
// eslint-disable-next-line no-jquery/no-global-selector // eslint-disable-next-line no-jquery/no-global-selector
@ -261,13 +261,13 @@ function initNewTopicsSubscription() {
$icon = $( [] ); $icon = $( [] );
} }
var titleObj = mw.Title.newFromText( mw.config.get( 'wgRelevantPageName' ) ); const titleObj = mw.Title.newFromText( mw.config.get( 'wgRelevantPageName' ) );
var name = utils.getNewTopicsSubscriptionId( titleObj ); const name = utils.getNewTopicsSubscriptionId( titleObj );
$button.off( '.mw-dt-topicsubscriptions' ).on( 'click.mw-dt-topicsubscriptions', ( e ) => { $button.off( '.mw-dt-topicsubscriptions' ).on( 'click.mw-dt-topicsubscriptions', ( e ) => {
e.preventDefault(); e.preventDefault();
// Get latest subscribedState // Get latest subscribedState
var subscribedState = getSubscribedStateFromElement( $button[ 0 ] ); const subscribedState = getSubscribedStateFromElement( $button[ 0 ] );
changeSubscription( titleObj.getPrefixedText(), name, !subscribedState, true ) changeSubscription( titleObj.getPrefixedText(), name, !subscribedState, true )
.then( ( result ) => { .then( ( result ) => {
@ -284,9 +284,9 @@ function initSpecialTopicSubscriptions() {
// Unsubscribe links on special page // Unsubscribe links on special page
// eslint-disable-next-line no-jquery/no-global-selector // eslint-disable-next-line no-jquery/no-global-selector
$( '.ext-discussiontools-special-unsubscribe-button' ).each( ( i, element ) => { $( '.ext-discussiontools-special-unsubscribe-button' ).each( ( i, element ) => {
var button = OO.ui.infuse( element ); const button = OO.ui.infuse( element );
var data = button.getData(); const data = button.getData();
var subscribedState = STATE_SUBSCRIBED; let subscribedState = STATE_SUBSCRIBED;
button.on( 'click', () => { button.on( 'click', () => {
button.setDisabled( true ); button.setDisabled( true );
@ -311,7 +311,7 @@ function initSpecialTopicSubscriptions() {
* Show the first time popup for auto topic subscriptions, if required * Show the first time popup for auto topic subscriptions, if required
*/ */
function maybeShowFirstTimeAutoTopicSubPopup() { function maybeShowFirstTimeAutoTopicSubPopup() {
var lastHighlightComment = require( './highlighter.js' ).getLastHighlightedPublishedComment(); const lastHighlightComment = require( './highlighter.js' ).getLastHighlightedPublishedComment();
if ( !lastHighlightComment || seenAutoTopicSubPopup ) { if ( !lastHighlightComment || seenAutoTopicSubPopup ) {
return; return;
@ -321,7 +321,7 @@ function maybeShowFirstTimeAutoTopicSubPopup() {
mw.user.options.set( 'discussiontools-seenautotopicsubpopup', '1' ); mw.user.options.set( 'discussiontools-seenautotopicsubpopup', '1' );
api.saveOption( 'discussiontools-seenautotopicsubpopup', '1' ); api.saveOption( 'discussiontools-seenautotopicsubpopup', '1' );
var $popupContent, popup; let $popupContent, popup;
function close() { function close() {
popup.$element.removeClass( 'ext-discussiontools-autotopicsubpopup-fadein' ); popup.$element.removeClass( 'ext-discussiontools-autotopicsubpopup-fadein' );
@ -420,13 +420,13 @@ function updateSubscriptionStates( $container, headingsToUpdate ) {
// If the topic is already marked as auto-subscribed, there's nothing to do. // If the topic is already marked as auto-subscribed, there's nothing to do.
// (Except maybe show the first-time popup.) // (Except maybe show the first-time popup.)
// If the topic is marked as having never been subscribed, check if they are auto-subscribed now. // If the topic is marked as having never been subscribed, check if they are auto-subscribed now.
var topicsToCheck = []; const topicsToCheck = [];
var pendingLinks = []; const pendingLinks = [];
var pendingButtons = []; const pendingButtons = [];
for ( var headingName in headingsToUpdate ) { for ( const headingName in headingsToUpdate ) {
var link = linksByName[ headingName ]; const link = linksByName[ headingName ];
var button = buttonsByName[ headingName ]; const button = buttonsByName[ headingName ];
var subscribedState = getSubscribedStateFromElement( link || button.$element[ 0 ] ); const subscribedState = getSubscribedStateFromElement( link || button.$element[ 0 ] );
if ( subscribedState === STATE_AUTOSUBSCRIBED ) { if ( subscribedState === STATE_AUTOSUBSCRIBED ) {
maybeShowFirstTimeAutoTopicSubPopup(); maybeShowFirstTimeAutoTopicSubPopup();
@ -458,7 +458,7 @@ function updateSubscriptionStates( $container, headingsToUpdate ) {
// updateSubscriptionStates() method is only called if we're really expecting one to be there. // updateSubscriptionStates() method is only called if we're really expecting one to be there.
// (There are certainly neater ways to implement this, involving push notifications or at // (There are certainly neater ways to implement this, involving push notifications or at
// least long-polling or something. But this is the simplest one!) // least long-polling or something. But this is the simplest one!)
var wait = $.Deferred(); const wait = $.Deferred();
setTimeout( wait.resolve, 5000 ); setTimeout( wait.resolve, 5000 );
return wait.then( () => api.get( { return wait.then( () => api.get( {
action: 'discussiontoolsgetsubscriptions', action: 'discussiontoolsgetsubscriptions',
@ -468,8 +468,8 @@ function updateSubscriptionStates( $container, headingsToUpdate ) {
return response; return response;
} ).then( ( response ) => { } ).then( ( response ) => {
// Update state of each topic for which there is a subscription // Update state of each topic for which there is a subscription
for ( var subItemName in response.subscriptions ) { for ( const subItemName in response.subscriptions ) {
var state = response.subscriptions[ subItemName ]; const state = response.subscriptions[ subItemName ];
if ( linksByName[ subItemName ] ) { if ( linksByName[ subItemName ] ) {
updateSubscribeLink( linksByName[ subItemName ], state ); updateSubscribeLink( linksByName[ subItemName ], state );
} }
@ -496,8 +496,8 @@ function updateSubscriptionStates( $container, headingsToUpdate ) {
* @param {string} [threadItemId] Just-posted comment ID (or NEW_TOPIC_COMMENT_ID) * @param {string} [threadItemId] Just-posted comment ID (or NEW_TOPIC_COMMENT_ID)
*/ */
function updateAutoSubscriptionStates( $container, threadItemSet, threadItemId ) { function updateAutoSubscriptionStates( $container, threadItemSet, threadItemId ) {
var recentComments = []; const recentComments = [];
var headingsToUpdate = {}; const headingsToUpdate = {};
if ( threadItemId ) { if ( threadItemId ) {
// Edited by using the reply tool or new topic tool. Only check the edited topic. // Edited by using the reply tool or new topic tool. Only check the edited topic.
if ( threadItemId === utils.NEW_TOPIC_COMMENT_ID ) { if ( threadItemId === utils.NEW_TOPIC_COMMENT_ID ) {
@ -507,7 +507,7 @@ function updateAutoSubscriptionStates( $container, threadItemSet, threadItemId )
} }
} else if ( mw.config.get( 'wgPostEdit' ) ) { } else if ( mw.config.get( 'wgPostEdit' ) ) {
// Edited by using wikitext editor. Check topics with their own comments within last minute. // Edited by using wikitext editor. Check topics with their own comments within last minute.
for ( var i = 0; i < threadItemSet.threadItems.length; i++ ) { for ( let i = 0; i < threadItemSet.threadItems.length; i++ ) {
if ( if (
threadItemSet.threadItems[ i ] instanceof CommentItem && threadItemSet.threadItems[ i ] instanceof CommentItem &&
threadItemSet.threadItems[ i ].author === mw.user.getName() && threadItemSet.threadItems[ i ].author === mw.user.getName() &&
@ -518,7 +518,7 @@ function updateAutoSubscriptionStates( $container, threadItemSet, threadItemId )
} }
} }
recentComments.forEach( ( recentComment ) => { recentComments.forEach( ( recentComment ) => {
var headingItem = recentComment.getSubscribableHeading(); const headingItem = recentComment.getSubscribableHeading();
if ( headingItem ) { if ( headingItem ) {
// Use names as object keys to deduplicate if there are multiple comments in a topic. // Use names as object keys to deduplicate if there are multiple comments in a topic.
headingsToUpdate[ headingItem.name ] = headingItem; headingsToUpdate[ headingItem.name ] = headingItem;

View file

@ -21,7 +21,7 @@ var solTransparentLinkRegexp = /(?:^|\s)mw:PageProp\/(?:Category|redirect|Langua
* @return {boolean} Node is considered a rendering-transparent node in Parsoid * @return {boolean} Node is considered a rendering-transparent node in Parsoid
*/ */
function isRenderingTransparentNode( node ) { function isRenderingTransparentNode( node ) {
var nextSibling = node.nextSibling; const nextSibling = node.nextSibling;
return ( return (
node.nodeType === Node.COMMENT_NODE || node.nodeType === Node.COMMENT_NODE ||
node.nodeType === Node.ELEMENT_NODE && ( node.nodeType === Node.ELEMENT_NODE && (
@ -117,7 +117,7 @@ function isCommentSeparator( node ) {
return false; return false;
} }
var tagName = node.tagName.toLowerCase(); const tagName = node.tagName.toLowerCase();
if ( tagName === 'br' || tagName === 'hr' ) { if ( tagName === 'br' || tagName === 'hr' ) {
return true; return true;
} }
@ -130,7 +130,7 @@ function isCommentSeparator( node ) {
return true; return true;
} }
var classList = node.classList; const classList = node.classList;
if ( if (
// Anything marked as not containing comments // Anything marked as not containing comments
classList.contains( 'mw-notalk' ) || classList.contains( 'mw-notalk' ) ||
@ -174,7 +174,7 @@ function isCommentContent( node ) {
* @return {number} Index in parentNode's childNode list * @return {number} Index in parentNode's childNode list
*/ */
function childIndexOf( child ) { function childIndexOf( child ) {
var i = 0; let i = 0;
while ( ( child = child.previousSibling ) ) { while ( ( child = child.previousSibling ) ) {
i++; i++;
} }
@ -219,7 +219,7 @@ function getTranscludedFromElement( node ) {
node.getAttribute( 'about' ) && node.getAttribute( 'about' ) &&
/^#mwt\d+$/.test( node.getAttribute( 'about' ) ) /^#mwt\d+$/.test( node.getAttribute( 'about' ) )
) { ) {
var about = node.getAttribute( 'about' ); const about = node.getAttribute( 'about' );
// 2. // 2.
while ( while (
@ -252,7 +252,7 @@ function getTranscludedFromElement( node ) {
*/ */
function getHeadlineNode( heading ) { function getHeadlineNode( heading ) {
// This code assumes that $wgFragmentMode is [ 'html5', 'legacy' ] or [ 'html5' ] // This code assumes that $wgFragmentMode is [ 'html5', 'legacy' ] or [ 'html5' ]
var headline = heading; let headline = heading;
if ( headline.hasAttribute( 'data-mw-comment-start' ) ) { if ( headline.hasAttribute( 'data-mw-comment-start' ) ) {
// JS only: Support output from the PHP CommentFormatter // JS only: Support output from the PHP CommentFormatter
@ -292,12 +292,12 @@ function htmlTrim( str ) {
* @return {number} * @return {number}
*/ */
function getIndentLevel( node, rootNode ) { function getIndentLevel( node, rootNode ) {
var indent = 0; let indent = 0;
while ( node ) { while ( node ) {
if ( node === rootNode ) { if ( node === rootNode ) {
break; break;
} }
var tagName = node instanceof HTMLElement ? node.tagName.toLowerCase() : null; const tagName = node instanceof HTMLElement ? node.tagName.toLowerCase() : null;
if ( tagName === 'li' || tagName === 'dd' ) { if ( tagName === 'li' || tagName === 'dd' ) {
indent++; indent++;
} }
@ -313,11 +313,11 @@ function getIndentLevel( node, rootNode ) {
* @return {Node[]} * @return {Node[]}
*/ */
function getCoveredSiblings( range ) { function getCoveredSiblings( range ) {
var ancestor = range.commonAncestorContainer; const ancestor = range.commonAncestorContainer;
var siblings = ancestor.childNodes; const siblings = ancestor.childNodes;
var start = 0; let start = 0;
var end = siblings.length - 1; let end = siblings.length - 1;
// Find first of the siblings that contains the item // Find first of the siblings that contains the item
if ( ancestor === range.startContainer ) { if ( ancestor === range.startContainer ) {
@ -350,20 +350,20 @@ function getCoveredSiblings( range ) {
* @return {Node[]|null} * @return {Node[]|null}
*/ */
function getFullyCoveredSiblings( item, excludedAncestorNode ) { function getFullyCoveredSiblings( item, excludedAncestorNode ) {
var siblings = getCoveredSiblings( item.getRange() ); let siblings = getCoveredSiblings( item.getRange() );
function makeRange( sibs ) { function makeRange( sibs ) {
var range = sibs[ 0 ].ownerDocument.createRange(); const range = sibs[ 0 ].ownerDocument.createRange();
range.setStartBefore( sibs[ 0 ] ); range.setStartBefore( sibs[ 0 ] );
range.setEndAfter( sibs[ sibs.length - 1 ] ); range.setEndAfter( sibs[ sibs.length - 1 ] );
return range; return range;
} }
var matches = compareRanges( makeRange( siblings ), item.getRange() ) === 'equal'; const matches = compareRanges( makeRange( siblings ), item.getRange() ) === 'equal';
if ( matches ) { if ( matches ) {
// If these are all of the children (or the only child), go up one more level // If these are all of the children (or the only child), go up one more level
var parent; let parent;
while ( while (
( parent = siblings[ 0 ].parentNode ) && ( parent = siblings[ 0 ].parentNode ) &&
parent !== excludedAncestorNode && parent !== excludedAncestorNode &&
@ -387,18 +387,18 @@ function getTitleFromUrl( url ) {
if ( !url ) { if ( !url ) {
return null; return null;
} }
var parsedUrl = new URL( url ); const parsedUrl = new URL( url );
if ( parsedUrl.searchParams.get( 'title' ) ) { if ( parsedUrl.searchParams.get( 'title' ) ) {
return parsedUrl.searchParams.get( 'title' ); return parsedUrl.searchParams.get( 'title' );
} }
// wgArticlePath is site config so is trusted // wgArticlePath is site config so is trusted
// eslint-disable-next-line security/detect-non-literal-regexp // eslint-disable-next-line security/detect-non-literal-regexp
var articlePathRegexp = new RegExp( const articlePathRegexp = new RegExp(
mw.util.escapeRegExp( mw.config.get( 'wgArticlePath' ) ) mw.util.escapeRegExp( mw.config.get( 'wgArticlePath' ) )
.replace( '\\$1', '(.*)' ) .replace( '\\$1', '(.*)' )
); );
var match; let match;
if ( ( match = parsedUrl.pathname.match( articlePathRegexp ) ) ) { if ( ( match = parsedUrl.pathname.match( articlePathRegexp ) ) ) {
return decodeURIComponent( match[ 1 ] ); return decodeURIComponent( match[ 1 ] );
} }
@ -420,7 +420,7 @@ function getTitleFromUrl( url ) {
* @return {any} Final return value of the callback * @return {any} Final return value of the callback
*/ */
function linearWalk( node, callback ) { function linearWalk( node, callback ) {
var let
result = null, result = null,
withinNode = node.parentNode, withinNode = node.parentNode,
beforeNode = node; beforeNode = node;
@ -449,7 +449,7 @@ function linearWalk( node, callback ) {
* @inheritdoc #linearWalk * @inheritdoc #linearWalk
*/ */
function linearWalkBackwards( node, callback ) { function linearWalkBackwards( node, callback ) {
var let
result = null, result = null,
withinNode = node.parentNode, withinNode = node.parentNode,
beforeNode = node; beforeNode = node;
@ -521,10 +521,10 @@ function getRangeLastNode( range ) {
function compareRanges( a, b ) { function compareRanges( a, b ) {
// Compare the positions of: start of A to start of B, start of A to end of B, and so on. // Compare the positions of: start of A to start of B, start of A to end of B, and so on.
// Watch out, the constant names are the opposite of what they should be. // Watch out, the constant names are the opposite of what they should be.
var startToStart = a.compareBoundaryPoints( Range.START_TO_START, b ); let startToStart = a.compareBoundaryPoints( Range.START_TO_START, b );
var startToEnd = a.compareBoundaryPoints( Range.END_TO_START, b ); const startToEnd = a.compareBoundaryPoints( Range.END_TO_START, b );
var endToStart = a.compareBoundaryPoints( Range.START_TO_END, b ); const endToStart = a.compareBoundaryPoints( Range.START_TO_END, b );
var endToEnd = a.compareBoundaryPoints( Range.END_TO_END, b ); let endToEnd = a.compareBoundaryPoints( Range.END_TO_END, b );
// Check for almost equal ranges (boundary points only differing by uninteresting nodes) // Check for almost equal ranges (boundary points only differing by uninteresting nodes)
if ( if (
@ -580,15 +580,15 @@ function compareRangesAlmostEqualBoundaries( a, b, boundary ) {
// This code is awful, but several attempts to rewrite it made it even worse. // This code is awful, but several attempts to rewrite it made it even worse.
// You're welcome to give it a try. // You're welcome to give it a try.
var from = boundary === 'end' ? getRangeLastNode( a ) : getRangeFirstNode( a ); const from = boundary === 'end' ? getRangeLastNode( a ) : getRangeFirstNode( a );
var to = boundary === 'end' ? getRangeLastNode( b ) : getRangeFirstNode( b ); const to = boundary === 'end' ? getRangeLastNode( b ) : getRangeFirstNode( b );
var skipNode = null; let skipNode = null;
if ( boundary === 'end' ) { if ( boundary === 'end' ) {
skipNode = from; skipNode = from;
} }
var foundContent = false; let foundContent = false;
linearWalk( linearWalk(
from, from,
( event, n ) => { ( event, n ) => {

View file

@ -5,10 +5,10 @@ var
QUnit.module( 'mw.dt.ThreadItem', QUnit.newMwEnvironment() ); QUnit.module( 'mw.dt.ThreadItem', QUnit.newMwEnvironment() );
QUnit.test( '#getAuthorsBelow/#getThreadItemsBelow', ( assert ) => { QUnit.test( '#getAuthorsBelow/#getThreadItemsBelow', ( assert ) => {
var cases = require( '../cases/authors.json' ); const cases = require( '../cases/authors.json' );
function newFromJSON( json ) { function newFromJSON( json ) {
var item; let item;
if ( json.type === 'heading' ) { if ( json.type === 'heading' ) {
item = new HeadingItem(); item = new HeadingItem();
} else { } else {
@ -22,7 +22,7 @@ QUnit.test( '#getAuthorsBelow/#getThreadItemsBelow', ( assert ) => {
} }
cases.forEach( ( caseItem ) => { cases.forEach( ( caseItem ) => {
var threadItem = newFromJSON( caseItem.thread ), const threadItem = newFromJSON( caseItem.thread ),
authors = threadItem.getAuthorsBelow(); authors = threadItem.getAuthorsBelow();
assert.deepEqual( assert.deepEqual(

View file

@ -6,36 +6,36 @@ var
QUnit.module( 'mw.dt.modifier', QUnit.newMwEnvironment() ); QUnit.module( 'mw.dt.modifier', QUnit.newMwEnvironment() );
require( '../cases/modified.json' ).forEach( ( caseItem ) => { require( '../cases/modified.json' ).forEach( ( caseItem ) => {
var testName = '#addListItem/#removeAddedListItem (' + caseItem.name + ')'; const testName = '#addListItem/#removeAddedListItem (' + caseItem.name + ')';
// This should be one test with many cases, rather than multiple tests, but the cases are large // This should be one test with many cases, rather than multiple tests, but the cases are large
// enough that processing all of them at once causes timeouts in Karma test runner. // enough that processing all of them at once causes timeouts in Karma test runner.
var skipTests = require( '../skip.json' )[ 'cases/modified.json' ]; const skipTests = require( '../skip.json' )[ 'cases/modified.json' ];
if ( skipTests.indexOf( caseItem.name ) !== -1 ) { if ( skipTests.indexOf( caseItem.name ) !== -1 ) {
QUnit.skip( testName ); QUnit.skip( testName );
return; return;
} }
QUnit.test( testName, ( assert ) => { QUnit.test( testName, ( assert ) => {
var dom = ve.createDocumentFromHtml( require( '../' + caseItem.dom ) ), const dom = ve.createDocumentFromHtml( require( '../' + caseItem.dom ) ),
expected = ve.createDocumentFromHtml( require( '../' + caseItem.expected ) ), expected = ve.createDocumentFromHtml( require( '../' + caseItem.expected ) ),
config = require( caseItem.config ), config = require( caseItem.config ),
data = require( caseItem.data ); data = require( caseItem.data );
testUtils.overrideMwConfig( config ); testUtils.overrideMwConfig( config );
var expectedHtml = testUtils.getThreadContainer( expected ).innerHTML; const expectedHtml = testUtils.getThreadContainer( expected ).innerHTML;
var reverseExpectedHtml = testUtils.getThreadContainer( dom ).innerHTML; const reverseExpectedHtml = testUtils.getThreadContainer( dom ).innerHTML;
var container = testUtils.getThreadContainer( dom ); const container = testUtils.getThreadContainer( dom );
var title = mw.Title.newFromText( caseItem.title ); const title = mw.Title.newFromText( caseItem.title );
var threadItemSet = new Parser( data ).parse( container, title ); const threadItemSet = new Parser( data ).parse( container, title );
var comments = threadItemSet.getCommentItems(); const comments = threadItemSet.getCommentItems();
// Add a reply to every comment. Note that this inserts *all* of the replies, unlike the real // Add a reply to every comment. Note that this inserts *all* of the replies, unlike the real
// thing, which only deals with one at a time. This isn't ideal but resetting everything after // thing, which only deals with one at a time. This isn't ideal but resetting everything after
// every reply would be super slow. // every reply would be super slow.
var nodes = []; const nodes = [];
comments.forEach( ( comment ) => { comments.forEach( ( comment ) => {
var node = modifier.addListItem( comment, caseItem.replyIndentation || 'invisible' ); const node = modifier.addListItem( comment, caseItem.replyIndentation || 'invisible' );
node.textContent = 'Reply to ' + comment.id; node.textContent = 'Reply to ' + comment.id;
nodes.push( node ); nodes.push( node );
} ); } );
@ -43,7 +43,7 @@ require( '../cases/modified.json' ).forEach( ( caseItem ) => {
// Uncomment this to get updated content for the "modified HTML" files, for copy/paste: // Uncomment this to get updated content for the "modified HTML" files, for copy/paste:
// console.log( container.innerHTML ); // console.log( container.innerHTML );
var actualHtml = container.innerHTML; const actualHtml = container.innerHTML;
assert.strictEqual( assert.strictEqual(
actualHtml, actualHtml,
@ -56,7 +56,7 @@ require( '../cases/modified.json' ).forEach( ( caseItem ) => {
modifier.removeAddedListItem( node ); modifier.removeAddedListItem( node );
} ); } );
var reverseActualHtml = container.innerHTML; const reverseActualHtml = container.innerHTML;
assert.strictEqual( assert.strictEqual(
reverseActualHtml, reverseActualHtml,
reverseExpectedHtml, reverseExpectedHtml,
@ -66,26 +66,26 @@ require( '../cases/modified.json' ).forEach( ( caseItem ) => {
} ); } );
QUnit.test( '#addReplyLink', ( assert ) => { QUnit.test( '#addReplyLink', ( assert ) => {
var cases = require( '../cases/reply.json' ); const cases = require( '../cases/reply.json' );
cases.forEach( ( caseItem ) => { cases.forEach( ( caseItem ) => {
var dom = ve.createDocumentFromHtml( require( '../' + caseItem.dom ) ), const dom = ve.createDocumentFromHtml( require( '../' + caseItem.dom ) ),
expected = ve.createDocumentFromHtml( require( '../' + caseItem.expected ) ), expected = ve.createDocumentFromHtml( require( '../' + caseItem.expected ) ),
config = require( caseItem.config ), config = require( caseItem.config ),
data = require( caseItem.data ); data = require( caseItem.data );
testUtils.overrideMwConfig( config ); testUtils.overrideMwConfig( config );
var expectedHtml = testUtils.getThreadContainer( expected ).innerHTML; const expectedHtml = testUtils.getThreadContainer( expected ).innerHTML;
var container = testUtils.getThreadContainer( dom ); const container = testUtils.getThreadContainer( dom );
var title = mw.Title.newFromText( caseItem.title ); const title = mw.Title.newFromText( caseItem.title );
var threadItemSet = new Parser( data ).parse( container, title ); const threadItemSet = new Parser( data ).parse( container, title );
var comments = threadItemSet.getCommentItems(); const comments = threadItemSet.getCommentItems();
// Add a reply link to every comment. // Add a reply link to every comment.
comments.forEach( ( comment ) => { comments.forEach( ( comment ) => {
var linkNode = document.createElement( 'a' ); const linkNode = document.createElement( 'a' );
linkNode.textContent = 'Reply'; linkNode.textContent = 'Reply';
linkNode.href = '#'; linkNode.href = '#';
modifier.addReplyLink( comment, linkNode ); modifier.addReplyLink( comment, linkNode );
@ -94,7 +94,7 @@ QUnit.test( '#addReplyLink', ( assert ) => {
// Uncomment this to get updated content for the "reply HTML" files, for copy/paste: // Uncomment this to get updated content for the "reply HTML" files, for copy/paste:
// console.log( container.innerHTML ); // console.log( container.innerHTML );
var actualHtml = container.innerHTML; const actualHtml = container.innerHTML;
assert.strictEqual( assert.strictEqual(
actualHtml, actualHtml,
@ -105,10 +105,10 @@ QUnit.test( '#addReplyLink', ( assert ) => {
} ); } );
QUnit.test( '#unwrapList', ( assert ) => { QUnit.test( '#unwrapList', ( assert ) => {
var cases = require( '../cases/unwrap.json' ); const cases = require( '../cases/unwrap.json' );
cases.forEach( ( caseItem ) => { cases.forEach( ( caseItem ) => {
var container = document.createElement( 'div' ); const container = document.createElement( 'div' );
container.innerHTML = caseItem.html; container.innerHTML = caseItem.html;
modifier.unwrapList( container.childNodes[ caseItem.index || 0 ] ); modifier.unwrapList( container.childNodes[ caseItem.index || 0 ] );
@ -122,7 +122,7 @@ QUnit.test( '#unwrapList', ( assert ) => {
} ); } );
QUnit.test( 'sanitizeWikitextLinebreaks', ( assert ) => { QUnit.test( 'sanitizeWikitextLinebreaks', ( assert ) => {
var cases = require( '../cases/sanitize-wikitext-linebreaks.json' ); const cases = require( '../cases/sanitize-wikitext-linebreaks.json' );
cases.forEach( ( caseItem ) => { cases.forEach( ( caseItem ) => {
assert.strictEqual( assert.strictEqual(

View file

@ -6,7 +6,7 @@ var
QUnit.module( 'mw.dt.Parser', QUnit.newMwEnvironment() ); QUnit.module( 'mw.dt.Parser', QUnit.newMwEnvironment() );
QUnit.test( '#getTimestampRegexp', ( assert ) => { QUnit.test( '#getTimestampRegexp', ( assert ) => {
var cases = require( '../cases/timestamp-regex.json' ), const cases = require( '../cases/timestamp-regex.json' ),
parser = new Parser( require( '../data-en.json' ) ); parser = new Parser( require( '../data-en.json' ) );
cases.forEach( ( caseItem ) => { cases.forEach( ( caseItem ) => {
@ -19,11 +19,11 @@ QUnit.test( '#getTimestampRegexp', ( assert ) => {
} ); } );
QUnit.test( '#getTimestampParser', ( assert ) => { QUnit.test( '#getTimestampParser', ( assert ) => {
var cases = require( '../cases/timestamp-parser.json' ), const cases = require( '../cases/timestamp-parser.json' ),
parser = new Parser( require( '../data-en.json' ) ); parser = new Parser( require( '../data-en.json' ) );
cases.forEach( ( caseItem ) => { cases.forEach( ( caseItem ) => {
var tsParser = parser.getTimestampParser( 'en', caseItem.format, caseItem.digits, 'UTC', { UTC: 'UTC' } ), const tsParser = parser.getTimestampParser( 'en', caseItem.format, caseItem.digits, 'UTC', { UTC: 'UTC' } ),
expectedDate = moment( caseItem.expected ); expectedDate = moment( caseItem.expected );
assert.true( assert.true(
@ -34,11 +34,11 @@ QUnit.test( '#getTimestampParser', ( assert ) => {
} ); } );
QUnit.test( '#getTimestampParser (at DST change)', ( assert ) => { QUnit.test( '#getTimestampParser (at DST change)', ( assert ) => {
var cases = require( '../cases/timestamp-parser-dst.json' ), const cases = require( '../cases/timestamp-parser-dst.json' ),
parser = new Parser( require( '../data-en.json' ) ); parser = new Parser( require( '../data-en.json' ) );
cases.forEach( ( caseItem ) => { cases.forEach( ( caseItem ) => {
var regexp = parser.getTimestampRegexp( 'en', caseItem.format, '\\d', caseItem.timezoneAbbrs ), const regexp = parser.getTimestampRegexp( 'en', caseItem.format, '\\d', caseItem.timezoneAbbrs ),
tsParser = parser.getTimestampParser( 'en', caseItem.format, null, caseItem.timezone, caseItem.timezoneAbbrs ), tsParser = parser.getTimestampParser( 'en', caseItem.format, null, caseItem.timezone, caseItem.timezoneAbbrs ),
date = tsParser( caseItem.sample.match( regexp ) ).date; date = tsParser( caseItem.sample.match( regexp ) ).date;
@ -55,19 +55,19 @@ QUnit.test( '#getTimestampParser (at DST change)', ( assert ) => {
require( '../cases/comments.json' ).forEach( ( caseItem ) => { require( '../cases/comments.json' ).forEach( ( caseItem ) => {
var testName = '#getThreads (' + caseItem.name + ')'; const testName = '#getThreads (' + caseItem.name + ')';
QUnit.test( testName, ( assert ) => { QUnit.test( testName, ( assert ) => {
var dom = ve.createDocumentFromHtml( require( '../' + caseItem.dom ) ), const dom = ve.createDocumentFromHtml( require( '../' + caseItem.dom ) ),
expected = require( caseItem.expected ), expected = require( caseItem.expected ),
config = require( caseItem.config ), config = require( caseItem.config ),
data = require( caseItem.data ); data = require( caseItem.data );
testUtils.overrideMwConfig( config ); testUtils.overrideMwConfig( config );
var container = testUtils.getThreadContainer( dom ); const container = testUtils.getThreadContainer( dom );
var title = mw.Title.newFromText( caseItem.title ); const title = mw.Title.newFromText( caseItem.title );
var threadItemSet = new Parser( data ).parse( container, title ); const threadItemSet = new Parser( data ).parse( container, title );
var threads = threadItemSet.getThreads(); const threads = threadItemSet.getThreads();
threads.forEach( ( thread, i ) => { threads.forEach( ( thread, i ) => {
testUtils.serializeComments( thread, container ); testUtils.serializeComments( thread, container );

View file

@ -24,8 +24,8 @@ module.exports.overrideMwConfig = function ( config ) {
module.exports.getThreadContainer = function ( doc ) { module.exports.getThreadContainer = function ( doc ) {
// In tests created from Parsoid output, comments are contained directly in <body>. // In tests created from Parsoid output, comments are contained directly in <body>.
// In tests created from old parser output, comments are contained in <div class="mw-parser-output">. // In tests created from old parser output, comments are contained in <div class="mw-parser-output">.
var body = doc.body; const body = doc.body;
var wrapper = body.querySelector( 'div.mw-parser-output' ); const wrapper = body.querySelector( 'div.mw-parser-output' );
return wrapper || body; return wrapper || body;
}; };
@ -38,7 +38,7 @@ module.exports.getThreadContainer = function ( doc ) {
* @return {string} The offset path * @return {string} The offset path
*/ */
function getOffsetPath( ancestor, node, nodeOffset ) { function getOffsetPath( ancestor, node, nodeOffset ) {
var path = [ nodeOffset ]; const path = [ nodeOffset ];
while ( node !== ancestor ) { while ( node !== ancestor ) {
if ( node.parentNode === null ) { if ( node.parentNode === null ) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console

View file

@ -3,26 +3,26 @@ var utils = require( 'ext.discussionTools.init' ).utils;
QUnit.module( 'mw.dt.utils', QUnit.newMwEnvironment() ); QUnit.module( 'mw.dt.utils', QUnit.newMwEnvironment() );
QUnit.test( '#linearWalk', ( assert ) => { QUnit.test( '#linearWalk', ( assert ) => {
var cases = require( '../cases/linearWalk.json' ); const cases = require( '../cases/linearWalk.json' );
cases.forEach( ( caseItem ) => { cases.forEach( ( caseItem ) => {
var const
doc = ve.createDocumentFromHtml( require( '../' + caseItem.dom ) ), doc = ve.createDocumentFromHtml( require( '../' + caseItem.dom ) ),
expected = require( caseItem.expected ); expected = require( caseItem.expected );
var actual = []; const actual = [];
utils.linearWalk( doc, ( event, node ) => { utils.linearWalk( doc, ( event, node ) => {
actual.push( event + ' ' + node.nodeName.toLowerCase() + '(' + node.nodeType + ')' ); actual.push( event + ' ' + node.nodeName.toLowerCase() + '(' + node.nodeType + ')' );
} ); } );
var actualBackwards = []; const actualBackwards = [];
utils.linearWalkBackwards( doc, ( event, node ) => { utils.linearWalkBackwards( doc, ( event, node ) => {
actualBackwards.push( event + ' ' + node.nodeName.toLowerCase() + '(' + node.nodeType + ')' ); actualBackwards.push( event + ' ' + node.nodeName.toLowerCase() + '(' + node.nodeType + ')' );
} ); } );
assert.deepEqual( actual, expected, caseItem.name ); assert.deepEqual( actual, expected, caseItem.name );
var expectedBackwards = expected.slice().reverse().map( ( a ) => ( a.slice( 0, 5 ) === 'enter' ? 'leave' : 'enter' ) + a.slice( 5 ) ); const expectedBackwards = expected.slice().reverse().map( ( a ) => ( a.slice( 0, 5 ) === 'enter' ? 'leave' : 'enter' ) + a.slice( 5 ) );
assert.deepEqual( actualBackwards, expectedBackwards, caseItem.name + ' (backwards)' ); assert.deepEqual( actualBackwards, expectedBackwards, caseItem.name + ' (backwards)' );
// Uncomment this to get updated content for the JSON files, for copy/paste: // Uncomment this to get updated content for the JSON files, for copy/paste: