mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-11-30 00:55:00 +00:00
d15094c27d
New changes: 7bdf15b76 Cleanup: Allow a DM surface to be used to construct a UI surface 27b36e04d Cleanup: Move setSynchronizer from view to model Change-Id: I6b13dadcdaf4107fbf5b7ca50d9b5a52767a32ec
285 lines
9 KiB
JavaScript
285 lines
9 KiB
JavaScript
/*!
|
|
* VisualEditor MediaWiki CollabTarget init.
|
|
*
|
|
* @copyright 2011-2018 VisualEditor Team and others; see AUTHORS.txt
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
*/
|
|
|
|
( function () {
|
|
var target,
|
|
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( 'wgVisualEditor' ).usePageImages,
|
|
showDescriptions: mw.config.get( 'wgVisualEditor' ).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 ) {
|
|
setTitle( mw.msg( 'collabpad-doctitle', title.getPrefixedText() ) );
|
|
|
|
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' ).append( 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.setDisabled( 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, title;
|
|
|
|
progressDeferred.resolve();
|
|
target.clearSurfaces();
|
|
// Don't add the surface until the history has been applied
|
|
target.addSurface( surfaceModel );
|
|
// target.getSurface().getView().focus();
|
|
|
|
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 () {
|
|
surfaceModel.selectFirstContentOffset();
|
|
// Resolve progress bar
|
|
// importDeferred.resolve();
|
|
if ( ( title = target.getImportTitle() ) ) {
|
|
$( '#contentSub' ).html(
|
|
ve.htmlMsg(
|
|
'collabpad-import-subtitle',
|
|
$( '<a>' ).attr( 'href', title.getUrl() ).text( title.getMainText() )
|
|
)
|
|
);
|
|
ve.targetLinksToNewWindow( $( '#contentSub' )[ 0 ] );
|
|
} else {
|
|
$( '#contentSub' ).empty();
|
|
}
|
|
} );
|
|
} );
|
|
|
|
} ).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() {
|
|
setTitle( mw.msg( 'collabpad' ) );
|
|
|
|
if ( target ) {
|
|
$( '#firstHeading' ).removeClass( 've-init-mw-desktopArticleTarget-uneditableContent' );
|
|
target.restorePage();
|
|
target.destroy();
|
|
|
|
$( 'body' ).removeClass( 've-activated ve-active' );
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
}
|
|
} );
|
|
}() );
|