' ).append(
$( '
' ).text( mw.message(
isHeading ?
'discussiontools-target-heading-found-moved' :
'discussiontools-target-comment-found-moved',
titles.length
).text() ),
$list
);
titles.forEach( ( title ) => {
$list.append(
$( '
' ).append(
$( '' ).attr( 'href', title.getUrl() ).text( title.getPrefixedText() )
)
);
} );
mw.notification.notify(
$notification,
{ type: 'warn', autoHide: false }
);
// This notification should not be accidentally dismissable
return $.Deferred().reject().promise();
} else {
return mw.notification.notify(
mw.message( isHeading ?
'discussiontools-target-heading-missing' :
'discussiontools-target-comment-missing'
).text(),
{ type: 'warn', autoHide: false }
);
}
} );
}
if ( highlightResult.highlighted.length === 0 && ( highlightResult.requested.length > 1 || highlightResult.requestedSince ) ) {
dismissableNotificationPromise = mw.loader.using( 'mediawiki.notification' ).then( () => mw.notification.notify(
mw.message( 'discussiontools-target-comments-missing' ).text(),
{ type: 'warn', autoHide: false }
) );
}
} );
}
}
/**
* Update the contents of the page with the data from an action=parse API response.
*
* @param {jQuery} $container Page container
* @param {Object} data Data from action=parse API
*/
function updatePageContents( $container, data ) {
$container.find( '.mw-parser-output' ).first().replaceWith( data.parse.text );
mw.util.clearSubtitle();
mw.util.addSubtitle( data.parse.subtitle );
// eslint-disable-next-line no-jquery/no-global-selector
if ( $( '#catlinks' ).length ) {
const $categories = $( $.parseHTML( data.parse.categorieshtml ) );
mw.hook( 'wikipage.categories' ).fire( $categories );
// eslint-disable-next-line no-jquery/no-global-selector
$( '#catlinks' ).replaceWith( $categories );
}
mw.config.set( data.parse.jsconfigvars );
mw.loader.load( data.parse.modulestyles );
mw.loader.load( data.parse.modules );
mw.config.set( {
wgCurRevisionId: data.parse.revid,
wgRevisionId: data.parse.revid
} );
// TODO: update displaytitle, lastmodified
// We may not be able to use prop=displaytitle without making changes in the action=parse API,
// VE API has some confusing code that changes the HTML escaping on it before returning???
// We need our init code to run after everyone else's handlers for this hook,
// so that all changes to the page layout have been completed (e.g. collapsible elements),
// and we can measure things and display the highlight in the right place.
mw.hook( 'wikipage.content' ).remove( mw.dt.init );
mw.hook( 'wikipage.content' ).fire( $container );
// The hooks have "memory" so calling add() after fire() actually fires the handler,
// and calling add() before fire() would actually fire it twice.
mw.hook( 'wikipage.content' ).add( mw.dt.init );
mw.hook( 'wikipage.tableOfContents' ).fire(
data.parse.showtoc ? data.parse.sections : []
);
// Copied from ve.init.mw.DesktopArticleTarget.prototype.saveComplete
// TODO: Upstream this to core/skins, triggered by a hook (wikipage.content?)
// eslint-disable-next-line no-jquery/no-global-selector
$( '#t-permalink' ).add( '#coll-download-as-rl' ).find( 'a' ).each( ( i, link ) => {
const permalinkUrl = new URL( link.href );
permalinkUrl.searchParams.set( 'oldid', data.parse.revid );
$( link ).attr( 'href', permalinkUrl.toString() );
} );
const url = new URL( location.href );
url.searchParams.delete( 'oldid' );
// If there are any other query parameters left, re-use that URL object.
// Otherwise use the canonical style view url (T44553, T102363).
const keys = [];
url.searchParams.forEach( ( val, key ) => {
keys.push( key );
} );
if ( !keys.length || ( keys.length === 1 && keys[ 0 ] === 'title' ) ) {
const viewUrl = new URL( mw.util.getUrl( mw.config.get( 'wgRelevantPageName' ) ), document.baseURI );
viewUrl.hash = location.hash;
history.pushState( null, '', viewUrl );
} else {
history.pushState( null, '', url );
}
}
/**
* Load the latest revision of the page and display its contents.
*
* @param {number} [oldId] Revision ID to fetch, latest if not specified
* @return {jQuery.Promise} Promise which resolves when the refresh is complete
*/
function refreshPageContents( oldId ) {
// This should approximately match the API call in ApiVisualEditorEditor#parseWikitext
return getApi().get( {
action: 'parse',
// HACK: 'useskin' triggers a different code path that runs our OutputPageBeforeHTML hook,
// adding our reply links in the HTML (T266195)
useskin: mw.config.get( 'skin' ),
mobileformat: OO.ui.isMobile(),
uselang: mw.config.get( 'wgUserLanguage' ),
// Pass through dtenable query string param from original request
dtenable: new URLSearchParams( location.search ).get( 'dtenable' ) ? '1' : undefined,
prop: [ 'text', 'revid', 'categorieshtml', 'sections', 'displaytitle', 'subtitle', 'modules', 'jsconfigvars' ],
page: !oldId ? mw.config.get( 'wgRelevantPageName' ) : undefined,
oldid: oldId || undefined
} ).then( ( parseResp ) => {
updatePageContents( $pageContainer, parseResp );
} );
}
/**
* Update the page after a comment is published/saved
*
* @param {Object} data Edit API response data
* @param {ThreadItem} threadItem Parent thread item
* @param {string} pageName Page title
* @param {mw.dt.ReplyWidget} replyWidget ReplyWidget
*/
function update( data, threadItem, pageName, replyWidget ) {
function logSaveSuccess() {
mw.track( 'editAttemptStep', {
action: 'saveSuccess',
timing: mw.now() - replyWidget.commentController.saveInitiated,
// eslint-disable-next-line camelcase
revision_id: data.newrevid
} );
}
if (
( pageName === mw.config.get( 'wgRelevantPageName' ) && data.nocontent ) ||
data.tempusercreated
) {
// Reload if the page didn't exist before this update, or we just became logged in
// as a temporary user. We'd handle setting up the content just fine (assuming there's
// a mw-parser-output), but fixing up the UI tabs/behavior is outside our scope.
replyWidget.unbindBeforeUnloadHandler();
replyWidget.clearStorage();
replyWidget.setPending( true );
const params = { dtrepliedto: threadItem.id };
if ( data.tempusercreated ) {
params.dttempusercreated = '1';
}
window.location = data.tempusercreatedredirect || mw.util.getUrl( pageName, params );
logSaveSuccess();
return;
}
replyWidget.teardown();
// TODO: Tell controller to teardown all other open widgets
// Highlight the new reply after re-initializing
mw.dt.initState.repliedTo = threadItem.id;
mw.dt.initState.tempUserCreated = data.tempusercreated;
// Update page state
const pageUpdated = $.Deferred();
if ( pageName === mw.config.get( 'wgRelevantPageName' ) ) {
// We can use the result from the VisualEditor API
updatePageContents( $pageContainer, {
parse: {
text: data.content,
subtitle: data.contentSub,
categorieshtml: data.categorieshtml,
jsconfigvars: data.jsconfigvars,
revid: data.newrevid,
// Note: VE API merges 'modules' and 'modulestyles'
modules: data.modules,
modulestyles: [],
// Note: VE API drops 'showtoc' and changes 'sections' depending on it
showtoc: true,
sections: data.sections
}
} );
mw.config.set( {
wgCurRevisionId: data.newrevid,
wgRevisionId: data.newrevid
} );
pageUpdated.resolve();
} else {
// We saved to another page, we must purge and then fetch the current page
const api = getApi();
api.post( {
action: 'purge',
titles: mw.config.get( 'wgRelevantPageName' )
} ).then( () => refreshPageContents() ).then( () => {
pageUpdated.resolve();
} ).catch( () => {
// We saved the reply, but couldn't purge or fetch the updated page. Seems difficult to
// explain this problem. Redirect to the page where the user can at least see their reply…
window.location = mw.util.getUrl( pageName, { dtrepliedto: threadItem.id } );
// We're confident the saving portion succeeded, so still log this:
logSaveSuccess();
} );
}
// User logged in if module loaded.
if ( mw.loader.getState( 'mediawiki.page.watch.ajax' ) === 'ready' ) {
const watch = require( 'mediawiki.page.watch.ajax' );
watch.updateWatchLink(
mw.Title.newFromText( pageName ),
data.watched ? 'unwatch' : 'watch',
'idle',
data.watchlistexpiry
);
}
pageUpdated.then( logSaveSuccess );
}
module.exports = {
init: init,
update: update,
updatePageContents: updatePageContents,
refreshPageContents: refreshPageContents,
checkThreadItemOnPage: checkThreadItemOnPage,
getCheckboxesPromise: getCheckboxesPromise,
getApi: getApi,
getReplyWidgetModules: getReplyWidgetModules,
defaultVisual: defaultVisual,
enable2017Wikitext: enable2017Wikitext
};