mediawiki-extensions-Visual.../modules/ve-mw/init/ve.init.mw.TargetLoader.js
Bartosz Dziewoński 9be5c85db4 Provide a tool to insert a signature in namespaces that need it
VisualEditor is usually not enabled in talk namespaces... but
sometimes it is. And when users see the button to edit with VE,
they're going to click it and expect to be able to sign their posts.

This tool is only loaded on talk pages and pages in additional
namespaces defined in $wgExtraSignatureNamespaces.

Code adapted with small tweaks from my own gadget
<https://meta.wikimedia.org/wiki/User:Matma_Rex/visualeditor-signature.js?oldid=13461327>,
which is already available under the MIT license.

Changes include:
* The tool is now always visible if the wiki allows signatures in any
  VE namespaces, but disabled when not allowed in the current namespace.
* Register '~~~~' sequence to insert the signature.
* Code style tweaks for stricter lint checks in this repository.
* Documentation corrections.

Swedish translation provided by André Costa (already credited
as a translator as Lokal_Profil).

Depends on changes in VisualEditor core:
* I89fe53890ab59d12260ea6b41de802c38c24e8b9
* I14cd7efac521687ea38580341ae08ddc522edeeb

Bug: T53154
Change-Id: I6be5fb2118cf3eef5098d4c5320228aa81411ccb
2015-11-16 18:06:32 +01:00

212 lines
6.7 KiB
JavaScript

/*!
* VisualEditor MediaWiki TargetLoader.
*
* @copyright 2011-2015 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* Target loader.
*
* Light-weight loader that loads ResourceLoader modules for VisualEditor
* and HTML and page data from the API. Also handles plugin registration.
*
* @class mw.libs.ve.targetLoader
* @singleton
*/
( function () {
var prefName, prefValue,
conf = mw.config.get( 'wgVisualEditorConfig' ),
pluginCallbacks = [],
modules = [
'ext.visualEditor.mwcore',
'ext.visualEditor.mwlink',
'ext.visualEditor.mwformatting',
'ext.visualEditor.data',
'ext.visualEditor.mwtransclusion',
'ext.visualEditor.mwgallery',
'ext.visualEditor.mwalienextension',
'ext.visualEditor.language',
'ext.visualEditor.icons'
]
// Add modules from $wgVisualEditorPluginModules
.concat( conf.pluginModules );
// HACK: Check if Cite is installed by looking for ext.cite.styles before
// loading ext.visualEditor.mwreference. Really ext.visualEditor.mwreference
// should be defined in the Cite extension.
if ( mw.loader.getState( 'ext.cite.styles' ) ) {
modules.push( 'ext.visualEditor.mwreference' );
}
// Allow signing posts in select namespaces
if ( conf.signatureNamespaces.length ) {
modules.push( 'ext.visualEditor.mwsignature' );
}
// Add preference modules
for ( prefName in conf.preferenceModules ) {
prefValue = mw.user.options.get( prefName );
// Check "0" (T89513)
if ( prefValue && prefValue !== '0' ) {
modules.push( conf.preferenceModules[ prefName ] );
}
}
mw.libs.ve = mw.libs.ve || {};
mw.libs.ve.targetLoader = {
/**
* Add a plugin module or callback.
*
* If a module name is passed, that module will be loaded alongside the other modules.
*
* If a callback is passed, it will be executed after the modules have loaded. The callback
* may optionally return a jQuery.Promise; if it does, loading won't be complete until
* that promise is resolved.
*
* @param {string|Function} plugin Plugin module name or callback
*/
addPlugin: function ( plugin ) {
if ( typeof plugin === 'string' ) {
modules.push( plugin );
} else if ( $.isFunction( plugin ) ) {
pluginCallbacks.push( plugin );
}
},
/**
* Load modules needed for VisualEditor, as well as plugins.
*
* This loads the base VE modules as well as any registered plugin modules.
* Once those are loaded, any registered plugin callbacks are executed,
* and we wait for all promises returned by those callbacks to resolve.
*
* @return {jQuery.Promise} Promise resolved when the loading process is complete
*/
loadModules: function () {
ve.track( 'trace.moduleLoad.enter' );
return mw.loader.using( modules )
.then( function () {
ve.track( 'trace.moduleLoad.exit' );
pluginCallbacks.push( ve.init.platform.getInitializedPromise.bind( ve.init.platform ) );
// Execute plugin callbacks and collect promises
return $.when.apply( $, pluginCallbacks.map( function ( callback ) {
return callback();
} ) );
} );
},
/**
* Request the page HTML and various metadata from the MediaWiki API (which will use
* Parsoid or RESTBase).
*
* @return {jQuery.Promise} Abortable promise resolved with a JSON object
*/
requestPageData: function ( pageName, oldid, targetName ) {
var start, apiXhr, restbaseXhr, apiPromise, restbasePromise, dataPromise, pageHtmlUrl,
fromEditedState = false,
data = {
action: 'visualeditor',
paction: ( conf.fullRestbaseUrl || conf.restbaseUrl ) ? 'metadata' : 'parse',
page: pageName,
uselang: mw.config.get( 'wgUserLanguage' )
};
// Only request the API to explicitly load the currently visible revision if we're restoring
// from oldid. Otherwise we should load the latest version. This prevents us from editing an
// old version if an edit was made while the user was viewing the page and/or the user is
// seeing (slightly) stale cache.
if ( oldid !== undefined ) {
data.oldid = oldid;
}
// Load DOM
start = ve.now();
ve.track( 'trace.apiLoad.enter' );
apiXhr = new mw.Api().get( data );
apiPromise = apiXhr.then( function ( data, jqxhr ) {
ve.track( 'trace.apiLoad.exit' );
ve.track( 'mwtiming.performance.system.apiLoad', {
bytes: $.byteLength( jqxhr.responseText ),
duration: ve.now() - start,
cacheHit: /hit/i.test( jqxhr.getResponseHeader( 'X-Cache' ) ),
targetName: targetName
} );
return data;
} );
if ( conf.fullRestbaseUrl || conf.restbaseUrl ) {
ve.track( 'trace.restbaseLoad.enter' );
if ( conf.fullRestbaseUrl && $( '#wpTextbox1' ).length ) {
fromEditedState = true;
window.onbeforeunload = null;
$( window ).off( 'beforeunload' );
restbaseXhr = $.post(
conf.fullRestbaseUrl + 'v1/transform/wikitext/to/html/' +
encodeURIComponent( pageName ) +
( oldid === undefined ? '' : '/' + oldid ),
{
title: pageName,
oldid: oldid,
wikitext: $( '#wpTextbox1' ).val(),
stash: 'true'
}
);
} else {
if ( conf.fullRestbaseUrl ) {
pageHtmlUrl = conf.fullRestbaseUrl + 'v1/page/html/';
} else {
pageHtmlUrl = conf.restbaseUrl;
}
restbaseXhr = $.ajax( {
url: pageHtmlUrl + encodeURIComponent( pageName ) +
( oldid === undefined ? '' : '/' + oldid ),
type: 'GET',
dataType: 'text'
} );
}
restbasePromise = restbaseXhr.then(
function ( data, status, jqxhr ) {
ve.track( 'trace.restbaseLoad.exit' );
ve.track( 'mwtiming.performance.system.restbaseLoad', {
bytes: $.byteLength( jqxhr.responseText ),
duration: ve.now() - start,
targetName: targetName
} );
return [ data, jqxhr.getResponseHeader( 'etag' ) ];
},
function ( response ) {
if ( response.status === 404 ) {
// Page does not exist, so let the user start with a blank document.
return $.Deferred().resolve( [ '', undefined ] ).promise();
} else {
window.alert( mw.msg( 'visualeditor-loaderror-message', 'HTTP ' + response.status ) );
mw.log.warn( 'RESTBase load failed: ' + response.statusText );
}
}
);
dataPromise = $.when( apiPromise, restbasePromise )
.then( function ( apiData, restbaseData ) {
if ( apiData.visualeditor ) {
apiData.visualeditor.content = restbaseData[ 0 ];
apiData.visualeditor.etag = restbaseData[ 1 ];
apiData.visualeditor.fromEditedState = fromEditedState;
}
return apiData;
} )
.promise( { abort: function () {
apiXhr.abort();
restbaseXhr.abort();
} } );
} else {
dataPromise = apiPromise.promise( { abort: apiXhr.abort } );
}
return dataPromise;
}
};
}() );