mediawiki-extensions-Visual.../modules/ve-mw-collab/ve.init.mw.CollabTarget.init.js
Timo Tijhof ef36f4b0a1 Move usePageImages/usePageDescriptions from page conf to site conf
These do not vary by user or page, and can thus be loaded asynchronously
via the startup module, rather than blocking rendering and fetching
of modules on all pages.

In a future change, it might be better to go a step further and bundle
these with a module so that they only load as-needed instead of still
on all page views, but this should be an improvement nonetheless.

Change-Id: Icae3712ac5546a90bc7ffd787b0f3285dff6a26f
2019-04-17 00:10:27 +01:00

314 lines
10 KiB
JavaScript

/*!
* VisualEditor MediaWiki CollabTarget init.
*
* @copyright 2011-2019 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/* eslint-disable no-jquery/no-global-selector */
( function () {
var target,
$specialTab = $( '#ca-nstab-special' ),
$padTab = $( '#ca-pad' ),
conf = mw.config.get( 'wgVisualEditorConfig' ),
pageName = mw.config.get( 'collabPadPageName' ) || '',
pageTitle = mw.Title.newFromText( pageName ),
modules = [ OO.ui.isMobile() ? 'ext.visualEditor.collabTarget.mobile' : 'ext.visualEditor.collabTarget.desktop' ]
// Add modules from $wgVisualEditorPluginModules
.concat( conf.pluginModules.filter( mw.loader.getState ) ),
modulePromise = mw.loader.using( modules ),
progressBar = OO.ui.infuse( $( '.ve-init-mw-collabTarget-loading' ) ),
documentNameInput = OO.ui.infuse( $( '.ve-init-mw-collabTarget-nameInput' ) ),
documentNameButton = OO.ui.infuse( $( '.ve-init-mw-collabTarget-nameButton' ) ),
importInput = OO.ui.infuse( $( '.ve-init-mw-collabTarget-importInput' ), {
showImages: mw.config.get( 'wgVisualEditorConfig' ).usePageImages,
showDescriptions: mw.config.get( 'wgVisualEditorConfig' ).usePageDescriptions
} ),
importButton = OO.ui.infuse( $( '.ve-init-mw-collabTarget-importButton' ) ),
// Infuse the form last to avoid recursive infusion with no config
form = OO.ui.infuse( $( '.ve-init-mw-collabTarget-form' ) );
if ( !VisualEditorSupportCheck() ) {
// VE not supported - say something?
return;
}
function setTitle( title ) {
$( '#firstHeading' ).text( title );
document.title = title;
}
function showPage( title, importTitle ) {
var specialTitle = mw.Title.newFromText( 'Special:CollabPad/' + title.toString() );
setTitle( mw.msg( 'collabpad-doctitle', title.getPrefixedText() ) );
mw.config.set( 'wgRelevantPageName', specialTitle.getPrefixedText() );
mw.config.set( 'wgPageName', specialTitle.getPrefixedText() );
if ( !$padTab.length ) {
$padTab = $( '<li>' ).attr( 'id', 'ca-pad' ).addClass( 'selected' ).append(
$( '<span>' ).append(
$( '<a>' ).attr( 'href', '' ).text( title.getPrefixedText() )
)
);
}
$padTab.insertAfter( $specialTab.removeClass( 'selected' ) );
progressBar.toggle( true );
form.toggle( false );
modulePromise.done( function () {
var dummySurface, surfaceModel,
progressDeferred = $.Deferred();
target = ve.init.mw.targetFactory.create( 'collab', title, conf.rebaserUrl, { importTitle: importTitle } );
$( 'body' ).addClass( 've-activated ve-active' );
$( '#content' ).prepend( target.$element );
target.transformPage();
$( '#firstHeading' ).addClass( 've-init-mw-desktopArticleTarget-uneditableContent' );
// Add a dummy surface while the doc is loading
dummySurface = target.addSurface( ve.dm.converter.getModelFromDom( ve.createDocumentFromHtml( '' ) ) );
dummySurface.setReadOnly( true );
// TODO: Create the correct model surface type (ve.ui.Surface#createModel)
surfaceModel = new ve.dm.Surface( ve.dm.converter.getModelFromDom( ve.createDocumentFromHtml( '' ) ) );
surfaceModel.createSynchronizer(
title.toString(),
{
server: conf.rebaserUrl,
// TODO: server could communicate with MW (via oauth?) to know the
// current-user's name. Disable changing name if logged in?
// Communicate an I-am-a-valid-user flag to other clients?
defaultName: mw.user.isAnon() ? mw.user.getName() : undefined
}
);
dummySurface.createProgress( progressDeferred.promise(), ve.msg( 'visualeditor-rebase-client-connecting' ), true );
surfaceModel.synchronizer.once( 'initDoc', function () {
var initPromise;
progressDeferred.resolve();
// Resolving the progress bar doesn't close the window in this cycle,
// so wait until we call clearSurfaces which destroys the window manager.
setTimeout( function () {
target.clearSurfaces();
// Don't add the surface until the history has been applied
target.addSurface( surfaceModel );
target.once( 'surfaceReady', function () {
initPromise.then( function () {
surfaceModel.selectFirstContentOffset();
} );
} );
if ( target.importTitle && !surfaceModel.getDocument().getCompleteHistoryLength() ) {
initPromise = mw.libs.ve.targetLoader.requestParsoidData( target.importTitle.toString(), { targetName: 'collabpad' } ).then( function ( response ) {
var doc, dmDoc, fragment,
data = response.visualeditor;
if ( data && data.content ) {
doc = target.constructor.static.parseDocument( data.content );
dmDoc = target.constructor.static.createModelFromDom( doc );
fragment = surfaceModel.getLinearFragment( new ve.Range( 0, 2 ) );
fragment.insertDocument( dmDoc );
target.etag = data.etag;
target.baseTimeStamp = data.basetimestamp;
target.startTimeStamp = data.starttimestamp;
target.revid = data.oldid;
// Store the document metadata as a hidden meta item
fragment.collapseToEnd().insertContent( [
{
type: 'alienMeta',
attributes: {
importedDocument: {
title: target.importTitle.toString(),
etag: target.etag,
baseTimeStamp: target.baseTimeStamp,
startTimeStamp: target.startTimeStamp,
revid: target.revid
}
}
},
{ type: '/alienMeta' }
] );
} else {
// Import failed
return $.Deferred().reject( 'No content for ' + target.importTitle ).promise();
}
} );
} else {
// No import, or history already exists
initPromise = $.Deferred().resolve().promise();
// Look for import metadata in document
surfaceModel = target.getSurface().getModel();
surfaceModel.getMetaList().getItemsInGroup( 'misc' ).some( function ( item ) {
var importedDocument = item.getAttribute( 'importedDocument' );
if ( importedDocument ) {
target.importTitle = mw.Title.newFromText( importedDocument.title );
target.etag = importedDocument.etag;
target.baseTimeStamp = importedDocument.baseTimeStamp;
target.startTimeStamp = importedDocument.startTimeStamp;
target.revid = importedDocument.revid;
return true;
}
} );
}
initPromise.fail( function ( err ) {
setTimeout( function () {
throw new Error( err );
} );
} );
initPromise.always( function () {
progressDeferred.resolve();
} );
} );
} );
} ).always( function () {
form.toggle( false );
progressBar.toggle( false );
} ).fail( function ( err ) {
mw.log.error( err );
// eslint-disable-next-line no-use-before-define
showForm();
} );
}
function showForm( pushState ) {
var specialTitle = mw.Title.newFromText( 'Special:CollabPad' );
if ( pushState ) {
history.pushState( { tag: 'collabTarget' }, mw.msg( 'collabpad' ), specialTitle.getUrl() );
}
if ( target ) {
$( '#firstHeading' ).removeClass( 've-init-mw-desktopArticleTarget-uneditableContent' );
target.restorePage();
target.destroy();
$( 'body' ).removeClass( 've-activated ve-active' );
}
setTitle( mw.msg( 'collabpad' ) );
mw.config.set( 'wgRelevantPageName', specialTitle.getPrefixedText() );
mw.config.set( 'wgPageName', specialTitle.getPrefixedText() );
if ( $padTab ) {
$padTab.detach();
}
$specialTab.addClass( 'selected' );
progressBar.toggle( false );
form.toggle( true );
}
function loadTitle( title, importTitle ) {
var specialTitle = mw.Title.newFromText( 'Special:CollabPad/' + title.toString() );
if ( history.pushState ) {
// TODO: Handle popstate
history.pushState( { tag: 'collabTarget', title: title.toString() }, title.getMain(), specialTitle.getUrl() );
showPage( title, importTitle );
} else {
location.href = specialTitle.getUrl();
}
}
function getRandomTitle() {
return Math.random().toString( 36 ).slice( 2 );
}
function onNameChange() {
documentNameInput.getValidity().then( function () {
documentNameButton.setDisabled( false );
}, function () {
documentNameButton.setDisabled( true );
} );
}
function loadFromName() {
documentNameInput.getValidity().then( function () {
var title = mw.Title.newFromText(
documentNameInput.getValue().trim() || getRandomTitle()
);
if ( title ) {
loadTitle( title );
} else {
documentNameInput.focus();
}
} );
}
documentNameInput.setValidation( function ( value ) {
// Empty input will create a random document name, otherwise must be valid
return value === '' || !!mw.Title.newFromText( value );
} );
documentNameButton.setDisabled( false );
documentNameInput.on( 'change', onNameChange );
documentNameInput.on( 'enter', loadFromName );
documentNameButton.on( 'click', loadFromName );
onNameChange();
function onImportChange() {
importInput.getValidity().then( function () {
importButton.setDisabled( false );
}, function () {
importButton.setDisabled( true );
} );
}
function importTitle() {
importInput.getValidity().then( function () {
var title = mw.Title.newFromText( importInput.getValue().trim() );
if ( title ) {
loadTitle( mw.Title.newFromText( getRandomTitle() ), title );
} else {
documentNameInput.focus();
}
} );
}
importInput.setValidation( function ( value ) {
// TODO: Check page exists?
return !!mw.Title.newFromText( value );
} );
importInput.on( 'change', onImportChange );
importInput.on( 'enter', importTitle );
importButton.on( 'click', importTitle );
onImportChange();
if ( pageTitle ) {
showPage( pageTitle );
} else {
showForm();
}
$specialTab.on( 'click', function ( e ) {
showForm( true );
e.preventDefault();
} );
// Tag current state
if ( history.replaceState ) {
history.replaceState( { tag: 'collabTarget', title: pageName }, document.title, location.href );
}
window.addEventListener( 'popstate', function ( e ) {
if ( e.state && e.state.tag === 'collabTarget' ) {
if ( e.state.title ) {
showPage( mw.Title.newFromText( e.state.title ) );
} else {
showForm();
}
}
} );
}() );