Replace mediawiki.Uri with native URL (ArticleTarget)

Persistent global-ish properties in ArticleTarget and friends. A lot
of our own code re-uses them, and code elsewhere could refer to them
as well (although I didn't find any uses).

In one case we need to keep using mediawiki.Uri, to handle building
an array from query parameters exactly like PHP would handle it.

Bug: T325249
Change-Id: I57699ff9dd39179ca29a87b6e2d9b12c2b86eb7d
This commit is contained in:
Bartosz Dziewoński 2022-12-17 16:44:56 +01:00
parent 1cab012fec
commit 2770809d1a
4 changed files with 180 additions and 183 deletions

View file

@ -48,8 +48,7 @@ ve.init.mw.ArticleTarget = function VeInitMwArticleTarget( config ) {
var enableVisualSectionEditing = mw.config.get( 'wgVisualEditorConfig' ).enableVisualSectionEditing;
this.enableVisualSectionEditing = enableVisualSectionEditing === true || enableVisualSectionEditing === this.constructor.static.trackingName;
this.toolbarScrollOffset = mw.config.get( 'wgVisualEditorToolbarScrollOffset', 0 );
// A workaround, as default URI does not get updated after pushState (T74334)
this.currentUri = new mw.Uri( location.href );
this.currentUrl = new URL( location.href );
this.section = null;
this.visibleSection = null;
this.visibleSectionOffset = null;
@ -58,10 +57,10 @@ ve.init.mw.ArticleTarget = function VeInitMwArticleTarget( config ) {
this.initialEditSummary = null;
this.initialCheckboxes = {};
this.viewUri = new mw.Uri( mw.util.getUrl( this.getPageName() ) );
this.viewUrl = new URL( mw.util.getUrl( this.getPageName() ), location.href );
this.isViewPage = (
mw.config.get( 'wgAction' ) === 'view' &&
this.currentUri.query.diff === undefined
!this.currentUrl.searchParams.has( 'diff' )
);
this.copyrightWarning = null;
@ -302,7 +301,7 @@ ve.init.mw.ArticleTarget.prototype.loadSuccess = function ( response ) {
// to make the VE API non-blocking in the future we will need to handle
// special-cases like this where the content doesn't come from RESTBase.
this.fromEditedState = !!data.fromEditedState || !!data.preloaded;
this.switched = data.switched || 'wteswitched' in new mw.Uri( location.href ).query;
this.switched = data.switched || new URL( location.href ).searchParams.has( 'wteswitched' );
var mode = this.getDefaultMode();
var section = ( mode === 'source' || this.enableVisualSectionEditing ) ? this.section : null;
this.doc = this.constructor.static.parseDocument( this.originalHtml, mode, section );
@ -637,20 +636,20 @@ ve.init.mw.ArticleTarget.prototype.saveComplete = function ( data ) {
if ( !this.pageExists || this.restoring || !this.isViewPage ) {
// Teardown the target, ensuring auto-save data is cleared
this.teardown().then( function () {
var newUrlParams = {};
var newUrl = new URL( target.viewUrl );
if ( data.newrevid !== undefined ) {
if ( target.restoring ) {
newUrlParams.venotify = 'restored';
newUrl.searchParams.set( 'venotify', 'restored' );
} else if ( !target.pageExists ) {
newUrlParams.venotify = 'created';
newUrl.searchParams.set( 'venotify', 'created' );
} else {
newUrlParams.venotify = 'saved';
newUrl.searchParams.set( 'venotify', 'saved' );
}
}
if ( data.isRedirect ) {
newUrlParams.redirect = 'no';
newUrl.searchParams.set( 'redirect', 'no' );
}
location.href = target.viewUri.extend( newUrlParams );
location.href = newUrl;
} );
} else {
// Update watch link to match 'watch checkbox' in save dialog.
@ -2058,7 +2057,7 @@ ve.init.mw.ArticleTarget.prototype.restoreEditSection = function () {
} );
if ( headingModel ) {
var headingView = surface.getView().getDocument().getDocumentNode().getNodeFromOffset( headingModel.getRange().start );
if ( setEditSummary && new mw.Uri().query.summary === undefined ) {
if ( setEditSummary && !new URL( location.href ).searchParams.has( 'summary' ) ) {
headingText = headingView.$element.text();
}
if ( setExactScrollOffset ) {
@ -2148,13 +2147,13 @@ ve.init.mw.ArticleTarget.prototype.scrollToHeading = function ( headingNode, hea
};
/**
* Get the hash fragment for the current section's ID using the page's HTML.
* Get the URL hash for the current section's ID using the page's HTML.
*
* TODO: Do this in a less skin-dependent way
*
* @return {string} Hash fragment, or empty string if not found
* @return {string} URL hash with leading '#', or empty string if not found
*/
ve.init.mw.ArticleTarget.prototype.getSectionFragmentFromPage = function () {
ve.init.mw.ArticleTarget.prototype.getSectionHashFromPage = function () {
// Assume there are section edit links, as the user just did a section edit. This also means
// that the section numbers line up correctly, as not every H_ tag is a numbered section.
var $sections = this.$editableContent.find( '.mw-editsection' );
@ -2170,7 +2169,7 @@ ve.init.mw.ArticleTarget.prototype.getSectionFragmentFromPage = function () {
var $section = $sections.eq( section - 1 ).parent().find( '.mw-headline' );
if ( $section.length && $section.attr( 'id' ) ) {
return $section.attr( 'id' ) || '';
return '#' + $section.attr( 'id' );
}
}
return '';

View file

@ -53,7 +53,7 @@ ve.init.mw.DesktopArticleTarget = function VeInitMwDesktopArticleTarget( config
if ( $( '#wpSummary' ).length ) {
this.initialEditSummary = $( '#wpSummary' ).val();
} else {
this.initialEditSummary = this.currentUri.query.summary;
this.initialEditSummary = this.currentUrl.searchParams.get( 'summary' );
}
this.initialCheckboxes = $( '.editCheckboxes input' ).toArray()
.reduce( function ( initialCheckboxes, node ) {
@ -75,7 +75,7 @@ ve.init.mw.DesktopArticleTarget = function VeInitMwDesktopArticleTarget( config
// use the Back button to exit the editor we can restore Read mode. This is because we want
// to ignore foreign states in onWindowPopState. Without this, the Read state is foreign.
// FIXME: There should be a much better solution than this.
history.replaceState( this.popState, '', this.currentUri );
history.replaceState( this.popState, '', this.currentUrl );
this.setupSkinTabs();
@ -304,7 +304,7 @@ ve.init.mw.DesktopArticleTarget.prototype.setupToolbarSaveButton = function () {
*/
ve.init.mw.DesktopArticleTarget.prototype.setupLocalNoticeMessages = function () {
if ( !(
'vesupported' in this.currentUri.query ||
this.currentUrl.searchParams.has( 'vesupported' ) ||
$.client.test( this.constructor.static.compatibility.supportedList, null, true )
) ) {
// Show warning in unknown browsers that pass the support test
@ -512,8 +512,8 @@ ve.init.mw.DesktopArticleTarget.prototype.setupNewSection = function ( surface )
surface.setPlaceholder( ve.msg( 'visualeditor-section-body-placeholder' ) );
this.$editableContent.before( this.sectionTitle.$element );
if ( this.currentUri.query.preloadtitle ) {
this.sectionTitle.setValue( this.currentUri.query.preloadtitle );
if ( this.currentUrl.searchParams.has( 'preloadtitle' ) ) {
this.sectionTitle.setValue( this.currentUrl.searchParams.get( 'preloadtitle' ) );
}
surface.once( 'destroy', this.teardownNewSection.bind( this, surface ) );
} else {
@ -631,7 +631,7 @@ ve.init.mw.DesktopArticleTarget.prototype.teardown = function ( trackMechanism )
}
target.clearState();
target.initialEditSummary = new mw.Uri().query.summary;
target.initialEditSummary = new URL( location.href ).searchParams.get( 'summary' );
target.editSummaryValue = null;
// Move original content back out of the target
@ -654,9 +654,11 @@ ve.init.mw.DesktopArticleTarget.prototype.teardown = function ( trackMechanism )
}
if ( !target.isViewPage ) {
location.href = target.viewUri.clone().extend( {
redirect: mw.config.get( 'wgIsRedirect' ) ? 'no' : undefined
} );
var newUrl = new URL( target.viewUrl );
if ( mw.config.get( 'wgIsRedirect' ) ) {
newUrl.searchParams.set( 'redirect', 'no' );
}
location.href = newUrl;
}
} );
};
@ -673,7 +675,10 @@ ve.init.mw.DesktopArticleTarget.prototype.loadFail = function ( code, errorDetai
if ( this.wikitextFallbackLoading ) {
// Failed twice now
mw.log.warn( 'Failed to fall back to wikitext', code, errorDetails );
location.href = this.viewUri.clone().extend( { action: 'edit', veswitched: 1 } );
var newUrl = new URL( target.viewUrl );
newUrl.searchParams.set( 'action', 'edit' );
newUrl.searchParams.set( 'veswitched', '1' );
location.href = newUrl;
return;
}
@ -859,9 +864,9 @@ ve.init.mw.DesktopArticleTarget.prototype.saveComplete = function ( data ) {
// Fix permalinks
if ( data.newrevid !== undefined ) {
$( '#t-permalink' ).add( '#coll-download-as-rl' ).find( 'a' ).each( function () {
var uri = new mw.Uri( $( this ).attr( 'href' ) );
uri.query.oldid = data.newrevid;
$( this ).attr( 'href', uri.toString() );
var permalinkUrl = new URL( this.href );
permalinkUrl.searchParams.set( 'oldid', data.newrevid );
$( this ).attr( 'href', permalinkUrl.toString() );
} );
}
@ -1078,36 +1083,36 @@ ve.init.mw.DesktopArticleTarget.prototype.transformCategoryLinks = function ( $c
*/
ve.init.mw.DesktopArticleTarget.prototype.updateHistoryState = function () {
var veaction = this.getDefaultMode() === 'visual' ? 'edit' : 'editsource',
section = this.section !== null ? this.section : undefined;
section = this.section;
// Push veaction=edit(source) url in history (if not already. If we got here by a veaction=edit(source)
// permalink then it will be there already and the constructor called #activate)
if (
!this.actFromPopState &&
(
this.currentUri.query.veaction !== veaction ||
this.currentUri.query.section !== section
this.currentUrl.searchParams.get( 'veaction' ) !== veaction ||
this.currentUrl.searchParams.get( 'section' ) !== section
) &&
this.currentUri.query.action !== 'edit'
this.currentUrl.searchParams.get( 'action' ) !== 'edit'
) {
// Set the current URL
var uri = this.currentUri;
var url = this.currentUrl;
if ( mw.libs.ve.isSingleEditTab ) {
uri.query.action = 'edit';
url.searchParams.set( 'action', 'edit' );
mw.config.set( 'wgAction', 'edit' );
} else {
uri.query.veaction = veaction;
delete uri.query.action;
url.searchParams.set( 'veaction', veaction );
url.searchParams.delete( 'action' );
mw.config.set( 'wgAction', 'view' );
}
if ( this.section !== null ) {
uri.query.section = this.section;
url.searchParams.set( 'section', this.section );
} else {
delete uri.query.section;
url.searchParams.delete( 'section' );
}
history.pushState( this.popState, '', uri );
history.pushState( this.popState, '', url );
}
this.actFromPopState = false;
};
@ -1131,19 +1136,19 @@ ve.init.mw.DesktopArticleTarget.prototype.restorePage = function () {
// Push article url into history
if ( !this.actFromPopState ) {
// Remove the VisualEditor query parameters
var uri = this.currentUri;
if ( 'veaction' in uri.query ) {
delete uri.query.veaction;
var url = this.currentUrl;
if ( url.searchParams.has( 'veaction' ) ) {
url.searchParams.delete( 'veaction' );
}
if ( this.section !== null ) {
// Translate into a fragment for the new URI:
// Translate into a hash for the new URL:
// This should be after replacePageContent if this is post-save, so we can just look
// at the headers on the page.
var fragment = this.getSectionFragmentFromPage();
if ( fragment ) {
uri.fragment = fragment;
this.viewUri.fragment = fragment;
var target = document.getElementById( fragment );
var hash = this.getSectionHashFromPage();
if ( hash ) {
url.hash = hash;
this.viewUrl.hash = hash;
var target = document.getElementById( hash.slice( 1 ) );
if ( target ) {
// Scroll the page to the edited section
@ -1152,25 +1157,28 @@ ve.init.mw.DesktopArticleTarget.prototype.restorePage = function () {
} );
}
}
delete uri.query.section;
url.searchParams.delete( 'section' );
}
if ( 'action' in uri.query && $( '#wpTextbox1:not(.ve-dummyTextbox)' ).length === 0 ) {
if ( url.searchParams.has( 'action' ) && $( '#wpTextbox1:not(.ve-dummyTextbox)' ).length === 0 ) {
// If we're not overlaid on an edit page, remove action=edit
delete uri.query.action;
url.searchParams.delete( 'action' );
mw.config.set( 'wgAction', 'view' );
}
if ( 'oldid' in uri.query && !this.restoring ) {
if ( url.searchParams.has( 'oldid' ) && !this.restoring ) {
// We have an oldid in the query string but it's the most recent one, so remove it
delete uri.query.oldid;
url.searchParams.delete( 'oldid' );
}
// If there are any other query parameters left, re-use that uri object.
// Otherwise use the canonical style view url (T44553, T102363).
var keys = Object.keys( uri.query );
// If there are any other query parameters left, re-use that URL object.
// Otherwise use the canonical style view URL (T44553, T102363).
var keys = [];
url.searchParams.forEach( function ( val, key ) {
keys.push( key );
} );
if ( !keys.length || ( keys.length === 1 && keys[ 0 ] === 'title' ) ) {
history.pushState( this.popState, '', this.viewUri );
history.pushState( this.popState, '', this.viewUrl );
} else {
history.pushState( this.popState, '', uri );
history.pushState( this.popState, '', url );
}
}
};
@ -1187,11 +1195,11 @@ ve.init.mw.DesktopArticleTarget.prototype.onWindowPopState = function ( e ) {
return;
}
var oldUri = this.currentUri;
var oldUrl = this.currentUrl;
this.currentUri = new mw.Uri( location.href );
var veaction = this.currentUri.query.veaction;
var action = this.currentUri.query.action;
this.currentUrl = new URL( location.href );
var veaction = this.currentUrl.searchParams.get( 'veaction' );
var action = this.currentUrl.searchParams.get( 'action' );
if ( !veaction && action === 'edit' ) {
veaction = this.getDefaultMode() === 'source' ? 'editsource' : 'edit';
@ -1213,8 +1221,8 @@ ve.init.mw.DesktopArticleTarget.prototype.onWindowPopState = function ( e ) {
if ( this.active && veaction !== 'edit' && veaction !== 'editsource' ) {
this.actFromPopState = true;
// "Undo" the pop-state, as the event is not cancellable
history.pushState( this.popState, '', oldUri );
this.currentUri = oldUri;
history.pushState( this.popState, '', oldUrl );
this.currentUrl = oldUrl;
this.tryTeardown( false, 'navigate-back' ).then( function () {
// Teardown was successful, re-apply the undone state
history.back();
@ -1434,19 +1442,22 @@ ve.init.mw.DesktopArticleTarget.prototype.switchToFallbackWikitextEditor = funct
ve.track( 'mwedit.abort', { type: 'switchnochange', mechanism: 'navigate', mode: 'visual' } );
this.submitting = true;
return prefPromise.then( function () {
var uri = target.viewUri.clone().extend( {
action: 'edit',
// No changes, safe to stay in section mode
section: target.section !== null ? target.section : undefined,
veswitched: 1
} );
var url = new URL( target.viewUrl );
url.searchParams.set( 'action', 'edit' );
// No changes, safe to stay in section mode
if ( target.section !== null ) {
url.searchParams.set( 'section', target.section );
} else {
url.searchParams.delete( 'section' );
}
url.searchParams.set( 'veswitched', '1' );
if ( oldId && oldId !== mw.config.get( 'wgCurRevisionId' ) ) {
uri.extend( { oldid: oldId } );
url.searchParams.set( 'oldid', oldId );
}
if ( mw.libs.ve.isWelcomeDialogSuppressed() ) {
uri.extend( { vehidebetadialog: 1 } );
url.searchParams.set( 'vehidebetadialog', '1' );
}
location.href = uri.toString();
location.href = url.toString();
} );
} else {
return this.serialize( this.getDocToSave() ).then( function ( data ) {

View file

@ -429,7 +429,7 @@ ve.init.mw.MobileArticleTarget.prototype.saveComplete = function ( data ) {
// Parent method
ve.init.mw.MobileArticleTarget.super.prototype.saveComplete.apply( this, arguments );
var fragment = this.getSectionFragmentFromPage();
var fragment = this.getSectionHashFromPage().slice( 1 );
this.overlay.sectionId = fragment;
this.overlay.onSaveComplete( data.newrevid );
@ -460,9 +460,11 @@ ve.init.mw.MobileArticleTarget.prototype.teardown = function () {
// Parent method
return ve.init.mw.MobileArticleTarget.super.prototype.teardown.call( this ).then( function () {
if ( !target.isViewPage ) {
location.href = target.viewUri.clone().extend( {
redirect: mw.config.get( 'wgIsRedirect' ) ? 'no' : undefined
} );
var newUrl = new URL( target.viewUrl );
if ( mw.config.get( 'wgIsRedirect' ) ) {
newUrl.searchParams.set( 'redirect', 'no' );
}
location.href = newUrl;
}
} );
};

View file

@ -22,7 +22,7 @@
* @singleton
*/
( function () {
var conf, tabMessages, uri, pageExists, viewUri, veEditUri, veEditSourceUri,
var conf, tabMessages, url, pageExists, viewUrl, veEditUrl, veEditSourceUrl,
init, targetPromise,
tabPreference, initialWikitext, oldId,
isLoading, tempWikitextEditor, tempWikitextEditorData,
@ -200,7 +200,7 @@
updateTabs( false );
// Push read tab URL to history
if ( $( '#ca-view a' ).length ) {
history.pushState( { tag: 'visualeditor' }, '', new mw.Uri( $( '#ca-view a' ).attr( 'href' ) ) );
history.pushState( { tag: 'visualeditor' }, '', $( '#ca-view a' ).attr( 'href' ) );
}
clearLoading();
}
@ -216,7 +216,7 @@
* Parse a section value from a query string object
*
* @example
* parseSection( uri.query.section )
* parseSection( new URL( location.href ).searchParams.get( 'section' ) )
*
* @param {string|undefined} section Section value from query object
* @return {string|null} Section if valid, null otherwise
@ -280,16 +280,10 @@
updateTabs( false );
} );
target.on( 'reactivate', function () {
try {
// T270331, see below.
uri = new mw.Uri( null, { arrayParams: true } );
} catch ( e ) {
uri = viewUri;
}
url = new URL( location.href );
activateTarget(
getEditModeFromUri( uri ),
parseSection( uri.query.section )
getEditModeFromUrl( url ),
parseSection( url.searchParams.get( 'section' ) )
);
} );
target.setContainer( $targetContainer );
@ -308,12 +302,13 @@
}
function trackActivateStart( initData, link ) {
var linkUrl;
if ( link ) {
link = new mw.Uri( $( link ).closest( 'a' ).attr( 'href' ) );
linkUrl = new URL( $( link ).closest( 'a' ).attr( 'href' ), location.href );
} else {
link = uri;
linkUrl = url;
}
if ( link && link.query.wvprov === 'sticky-header' ) {
if ( linkUrl.searchParams.get( 'wvprov' ) === 'sticky-header' ) {
initData.mechanism += '-sticky-header';
}
ve.track( 'trace.activate.enter', { mode: initData.mode } );
@ -450,8 +445,8 @@
var $heading;
$( '#mw-content-text .mw-editsection a:not( .mw-editsection-visualeditor )' ).each( function () {
var linkUri = new mw.Uri( this.href );
if ( section === parseSection( linkUri.query.section ) ) {
var linkUrl = new URL( this.href );
if ( section === parseSection( linkUrl.searchParams.get( 'section' ) ) ) {
$heading = $( this ).closest( 'h1, h2, h3, h4, h5, h6' );
return false;
}
@ -531,9 +526,10 @@
// This is used for stats tracking, so do not change!
targetName: 'mwTarget',
modified: modified,
editintro: uri.query.editintro,
preload: uri.query.preload,
preloadparams: uri.query.preloadparams,
editintro: url.searchParams.get( 'editintro' ),
preload: url.searchParams.get( 'preload' ),
// Handle numbered array parameters like MediaWiki's PHP code does (T231382)
preloadparams: new mw.Uri( url.toString(), { arrayParams: true } ).query.preloadparams,
// If switching to visual with modifications, check if we have wikitext to convert
wikitext: mode === 'visual' && modified ? $( '#wpTextbox1' ).textSelection( 'getContents' ) : undefined
} );
@ -574,8 +570,8 @@
if ( firstVisibleEditSection && firstVisibleEditSection.id !== 'firstHeading' ) {
var firstVisibleSectionLink = firstVisibleEditSection.querySelector( 'a' );
var linkUri = new mw.Uri( firstVisibleSectionLink.href );
visibleSection = parseSection( linkUri.query.section );
var linkUrl = new URL( firstVisibleSectionLink.href );
visibleSection = parseSection( linkUrl.searchParams.get( 'section' ) );
var firstVisibleHeading = $( firstVisibleEditSection ).closest( 'h1, h2, h3, h4, h5, h6' )[ 0 ];
visibleSectionOffset = firstVisibleHeading.getBoundingClientRect().top;
@ -635,16 +631,16 @@
trackActivateStart( { type: 'page', mechanism: mw.config.get( 'wgArticleId' ) ? 'click' : 'new', mode: mode }, link );
if ( !active ) {
if ( uri.query.action !== 'edit' && !( uri.query.veaction in veactionToMode ) ) {
if ( url.searchParams.get( 'action' ) !== 'edit' && !( url.searchParams.get( 'veaction' ) in veactionToMode ) ) {
// Replace the current state with one that is tagged as ours, to prevent the
// back button from breaking when used to exit VE. FIXME: there should be a better
// way to do this. See also similar code in the DesktopArticleTarget constructor.
history.replaceState( { tag: 'visualeditor' }, '', uri );
history.replaceState( { tag: 'visualeditor' }, '', url );
// Set action=edit or veaction=edit/editsource
history.pushState( { tag: 'visualeditor' }, '', mode === 'source' ? veEditSourceUri : veEditUri );
history.pushState( { tag: 'visualeditor' }, '', mode === 'source' ? veEditSourceUrl : veEditUrl );
// Update mw.Uri instance
uri = veEditUri;
// Update URL instance
url = veEditUrl;
}
activateTarget( mode, section, undefined, modified );
@ -678,7 +674,7 @@
function getEditPageEditor() {
// This logic matches VisualEditorHooks::getEditPageEditor
// !!+ casts '0' to false
var isRedLink = !!+uri.query.redlink;
var isRedLink = !!+url.searchParams.get( 'redlink' );
// On dual-edit-tab wikis, the edit page must mean the user wants wikitext,
// unless following a redlink
if ( !mw.config.get( 'wgVisualEditorConfig' ).singleEditTab && !isRedLink ) {
@ -752,16 +748,9 @@
conf = mw.config.get( 'wgVisualEditorConfig' );
tabMessages = conf.tabMessages;
viewUri = new mw.Uri( mw.util.getUrl( mw.config.get( 'wgRelevantPageName' ) ) );
try {
uri = new mw.Uri( null, { arrayParams: true } );
} catch ( e ) {
// URI failed to parse, probably because of query string parameters. (T270331)
// Fall back to the viewUri so that initialization completes.
// Ideally mw.Uri would ignore invalid parameters or characters.
uri = viewUri;
}
// T156998: Don't trust uri.query.oldid, it'll be wrong if uri.query.diff or uri.query.direction
viewUrl = new URL( mw.util.getUrl( mw.config.get( 'wgRelevantPageName' ) ), location.href );
url = new URL( location.href );
// T156998: Don't trust 'oldid' query parameter, it'll be wrong if 'diff' or 'direction'
// is set to 'next' or 'prev'.
oldId = mw.config.get( 'wgRevisionId' ) || $( 'input[name=parentRevId]' ).val();
// wgFlaggedRevsEditLatestRevision is set by FlaggedRevs extension when viewing a stable revision
@ -771,7 +760,7 @@
oldId = undefined;
}
pageExists = !!mw.config.get( 'wgRelevantArticleId' );
var isViewPage = mw.config.get( 'wgIsArticle' ) && !( 'diff' in uri.query );
var isViewPage = mw.config.get( 'wgIsArticle' ) && !url.searchParams.has( 'diff' );
var wgAction = mw.config.get( 'wgAction' );
var isEditPage = wgAction === 'edit' || wgAction === 'submit';
var pageCanLoadEditor = isViewPage || isEditPage;
@ -851,17 +840,11 @@
// Add section is currently a wikitext-only feature
'#ca-addsection a'
).each( function () {
var linkUri;
try {
linkUri = new mw.Uri( this.href );
} catch ( e ) {
// T66884
return;
}
if ( 'action' in linkUri.query ) {
delete linkUri.query.action;
linkUri.query.veaction = 'editsource';
$( this ).attr( 'href', linkUri.toString() );
var linkUrl = new URL( this.href );
if ( linkUrl.searchParams.has( 'action' ) ) {
linkUrl.searchParams.delete( 'action' );
linkUrl.searchParams.set( 'veaction', 'editsource' );
$( this ).attr( 'href', linkUrl.toString() );
}
} );
}
@ -949,7 +932,7 @@
// onEditTabClick is bound.
// 2) when onEditTabClick is not bound (!pageCanLoadEditor) it will
// just work.
veEditUri,
veEditUrl,
getTabMessage( action ),
'ca-ve-edit',
mw.msg( 'tooltip-ca-ve-edit' ),
@ -1056,9 +1039,10 @@
if ( !$( '#ca-view-foreign' ).length ) {
$editLink
.attr( 'href', function ( i, href ) {
var veUri = new mw.Uri( veEditUri );
veUri.query.section = ( new mw.Uri( href ) ).query.section;
return veUri.toString();
var veUrl = new URL( veEditUrl );
var section = new URL( href, location.href ).searchParams.get( 'section' );
veUrl.searchParams.set( 'section', section );
return veUrl.toString();
} )
.addClass( 'mw-editsection-visualeditor' );
@ -1206,14 +1190,14 @@
windowManager.addWindows( [ switchWindow ] );
windowManager.openWindow( switchWindow )
.closed.then( function ( data ) {
var oldUri;
// TODO: windowManager.destroy()?
if ( data && data.action === 'discard' ) {
releaseOldEditWarning();
setEditorPreference( 'visualeditor' );
oldUri = veEditUri.clone();
delete oldUri.query.veswitched;
location.href = oldUri.extend( { wteswitched: 1 } );
var oldUrl = new URL( veEditUrl );
oldUrl.searchParams.delete( 'veswitched' );
oldUrl.searchParams.set( 'wteswitched', '1' );
location.href = oldUrl;
}
} );
} );
@ -1231,16 +1215,16 @@
* @param {string} [section] Override edit section, taken from link URL if not specified
*/
onEditSectionLinkClick: function ( mode, e, section ) {
var linkUri = new mw.Uri( e.target.href ),
title = mw.Title.newFromText( linkUri.query.title || '' );
var linkUrl = new URL( e.target.href ),
title = mw.Title.newFromText( linkUrl.searchParams.get( 'title' ) || '' );
if (
// Modified click (e.g. ctrl+click)
!init.isUnmodifiedLeftClick( e ) ||
// Not an edit action
!( 'action' in linkUri.query || 'veaction' in linkUri.query ) ||
!( linkUrl.searchParams.has( 'action' ) || linkUrl.searchParams.has( 'veaction' ) ) ||
// Edit target is on another host (e.g. commons file)
linkUri.getHostPort() !== location.host ||
linkUrl.host !== location.host ||
// Title param doesn't match current page
title && title.getPrefixedText() !== new mw.Title( mw.config.get( 'wgRelevantPageName' ) ).getPrefixedText()
) {
@ -1254,20 +1238,20 @@
trackActivateStart( { type: 'section', mechanism: section === 'new' ? 'new' : 'click', mode: mode }, e.target );
if ( !active ) {
if ( uri.query.action !== 'edit' && !( uri.query.veaction in veactionToMode ) ) {
if ( url.searchParams.get( 'action' ) !== 'edit' && !( url.searchParams.get( 'veaction' ) in veactionToMode ) ) {
// Replace the current state with one that is tagged as ours, to prevent the
// back button from breaking when used to exit VE. FIXME: there should be a better
// way to do this. See also similar code in the DesktopArticleTarget constructor.
history.replaceState( { tag: 'visualeditor' }, '', uri );
// Use linkUri
history.pushState( { tag: 'visualeditor' }, '', linkUri );
history.replaceState( { tag: 'visualeditor' }, '', url );
// Use linkUrl
history.pushState( { tag: 'visualeditor' }, '', linkUrl );
}
// Update mw.Uri instance
uri = linkUri;
// Update URL instance
url = linkUrl;
// Use section from URL
if ( section === undefined ) {
section = parseSection( linkUri.query.section );
section = parseSection( linkUrl.searchParams.get( 'section' ) );
}
var tPromise = getTarget( mode, section );
activateTarget( mode, section, tPromise, e.target );
@ -1304,9 +1288,9 @@
// Disabled by calling disableWelcomeDialog()?
welcomeDialogDisabled ||
// Hidden using URL parameter?
'vehidebetadialog' in new mw.Uri().query ||
new URL( location.href ).searchParams.has( 'vehidebetadialog' ) ||
// Check for deprecated hidewelcomedialog parameter (T249954)
'hidewelcomedialog' in new mw.Uri().query
new URL( location.href ).searchParams.has( 'hidewelcomedialog' )
);
},
@ -1370,27 +1354,28 @@
init.isSingleEditTab = conf.singleEditTab && tabPreference !== 'multi-tab';
// On a view page, extend the current URI so parameters like oldid are carried over
// On a non-view page, use viewUri
var veEditBaseUri = pageCanLoadEditor ? uri : viewUri;
if ( init.isSingleEditTab ) {
veEditSourceUri = veEditUri = veEditBaseUri.clone().extend( { action: 'edit' } );
delete veEditUri.query.veaction;
} else {
veEditUri = veEditBaseUri.clone().extend( { veaction: 'edit' } );
veEditSourceUri = veEditBaseUri.clone().extend( { veaction: 'editsource' } );
delete veEditUri.query.action;
delete veEditSourceUri.query.action;
}
// On a view page, extend the current URL so extra parameters are carried over
// On a non-view page, use viewUrl
veEditUrl = new URL( pageCanLoadEditor ? url : viewUrl );
if ( oldId ) {
veEditUri.extend( { oldid: oldId } );
veEditUrl.searchParams.set( 'oldid', oldId );
}
veEditUrl.searchParams.delete( 'veaction' );
veEditUrl.searchParams.delete( 'action' );
if ( init.isSingleEditTab ) {
veEditUrl.searchParams.set( 'action', 'edit' );
veEditSourceUrl = veEditUrl;
} else {
veEditSourceUrl = new URL( veEditUrl );
veEditUrl.searchParams.set( 'veaction', 'edit' );
veEditSourceUrl.searchParams.set( 'veaction', 'editsource' );
}
// Whether VisualEditor should be available for the current user, page, wiki, mediawiki skin,
// browser etc.
init.isAvailable = (
VisualEditorSupportCheck() &&
( ( 'vesupported' in uri.query ) || !$.client.test( init.unsupportedList, null, true ) )
( url.searchParams.has( 'vesupported' ) || !$.client.test( init.unsupportedList, null, true ) )
// Extensions can disable VE in certain circumstances using the VisualEditorBeforeEditor hook (T174180)
);
@ -1406,7 +1391,7 @@
init.isAvailable &&
// If forced by the URL parameter, skip the namespace check (T221892) and preference check
( uri.query.veaction === 'edit' || (
( url.searchParams.get( 'veaction' ) === 'edit' || (
// Only in enabled namespaces
conf.namespaces.indexOf( new mw.Title( mw.config.get( 'wgRelevantPageName' ) ).getNamespaceId() ) !== -1 &&
@ -1465,32 +1450,32 @@
// for e.g. "Edit" > "Edit source" even when VE is not available.
}
function isSupportedEditPage( editUri ) {
function isSupportedEditPage( editUrl ) {
return configData.unsupportedEditParams.every( function ( param ) {
return editUri.query[ param ] === undefined;
return !editUrl.searchParams.has( param );
} );
}
/**
* Get the edit mode for the given URI
* Get the edit mode for the given URL
*
* @param {mw.Uri} editUri Edit URI
* @param {URL} editUrl Edit URL
* @return {string} 'visual' or 'source'
*/
function getEditModeFromUri( editUri ) {
function getEditModeFromUrl( editUrl ) {
if ( mw.config.get( 'wgDiscussionToolsStartNewTopicTool' ) ) {
// Avoid conflicts with DiscussionTools
return false;
}
// On view pages if veaction is correctly set
var m = veactionToMode[ editUri.query.veaction ];
var m = veactionToMode[ editUrl.searchParams.get( 'veaction' ) ];
if ( isViewPage && init.isAvailable && availableModes.indexOf( m ) !== -1 ) {
return m;
}
// Edit pages
if ( isEditPage && isSupportedEditPage( editUri ) ) {
if ( isEditPage && isSupportedEditPage( editUrl ) ) {
// Just did a discard-switch from wikitext editor to VE (in no RESTBase mode)
if ( editUri.query.wteswitched === '1' ) {
if ( editUrl.searchParams.get( 'wteswitched' ) === '1' ) {
return init.isVisualAvailable ? 'visual' : null;
}
// User has disabled VE, or we are in view source only mode, or we have landed here with posted data
@ -1513,7 +1498,7 @@
var showWikitextWelcome = true,
numEditButtons = $( '#ca-edit, #ca-viewsource' ).length,
section = parseSection( uri.query.section );
section = parseSection( url.searchParams.get( 'section' ) );
var requiredSkinElements =
$targetContainer.length &&
@ -1521,7 +1506,7 @@
// A link to open the editor is technically not necessary if it's going to open itself
( isEditPage || numEditButtons );
if ( uri.query.action === 'edit' && $( '#wpTextbox1' ).length ) {
if ( url.searchParams.get( 'action' ) === 'edit' && $( '#wpTextbox1' ).length ) {
initialWikitext = $( '#wpTextbox1' ).textSelection( 'getContents' );
}
@ -1543,7 +1528,7 @@
mw.errorLogger.logError( err, 'error.visualeditor' );
}
} else if ( init.isAvailable ) {
var mode = getEditModeFromUri( uri );
var mode = getEditModeFromUrl( url );
if ( mode ) {
showWikitextWelcome = false;
trackActivateStart( {
@ -1561,7 +1546,7 @@
// with accesskey 'v' so create one
$( document.body ).append(
$( '<a>' )
.attr( { accesskey: mw.msg( 'accesskey-ca-ve-edit' ), href: veEditUri } )
.attr( { accesskey: mw.msg( 'accesskey-ca-ve-edit' ), href: veEditUrl } )
// Accesskey fires a click event
.on( 'click.ve-target', init.onEditTabClick.bind( init, 'visual' ) )
.addClass( 'oo-ui-element-hidden' )
@ -1578,7 +1563,7 @@
$( '#wpTextbox1' ).on( 'wikiEditor-toolbar-doneInitialSections', function () {
mw.loader.using( 'ext.visualEditor.switching' ).done( function () {
var windowManager, editingTabDialog, switchToolbar, popup,
showPopup = !!uri.query.veswitched && !mw.user.options.get( 'visualeditor-hidesourceswitchpopup' ),
showPopup = url.searchParams.has( 'veswitched' ) && !mw.user.options.get( 'visualeditor-hidesourceswitchpopup' ),
toolFactory = new OO.ui.ToolFactory(),
toolGroupFactory = new OO.ui.ToolGroupFactory();
@ -1628,7 +1613,7 @@
windowManager.destroy();
if ( data && data.action === 'prefer-ve' ) {
location.href = veEditUri;
location.href = veEditUrl;
} else if ( data && data.action === 'multi-tab' ) {
location.reload();
}
@ -1690,19 +1675,19 @@
} );
}
if ( uri.query.venotify ) {
var notify = uri.query.venotify;
if ( url.searchParams.has( 'venotify' ) ) {
var notify = url.searchParams.get( 'venotify' );
// wgPostEdit can be "saved", "created", "restored" or null for null edits.
// TODO: Also set wgPostEdit on non-redirecting edits (T240041)
mw.config.set( 'wgPostEdit', notify );
// Loadomg postEdit code will trigger the post edit notification as wgPostEdit is set
// Loading postEdit code will trigger the post edit notification as wgPostEdit is set
mw.loader.load( 'mediawiki.action.view.postEdit' );
delete uri.query.venotify;
url.searchParams.delete( 'venotify' );
// Get rid of the ?venotify= from the URL
history.replaceState( null, '', uri );
history.replaceState( null, '', url );
}
} );
}() );