mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-11-15 10:35:48 +00:00
218742d806
New changes: f58ddea DiffElement: Use document slices with full internal lists 83800c0 DebugBar: Remove hard-coded i18n b4f89e1 Update OOjs UI to v0.18.1 40c7bf6 Factor out active node functionality from SectionNode for captions 2d967be Move 'mode' property to surface, rename target property to 'defaultMode' 5d8c214 wrapAllNodes in sourcefragment dd3d9e5 Refactor ve.dm.Transaction 9d61aca Use canonical ve.dm.TransactionBuilder.static.newFrom* methods df4f72a Make table caption node an active node b79f72d Core source mode 7533ac4 Localisation updates from https://translatewiki.net. ae30d71 SourceSurfaceFragment: Check range is not collapsed Local changes: Get edit mode from surface where possible Depends-On: Iec758c1892d518ad4bc2c0d1aaf6ca00fa354323 Change-Id: Ifaf6a26078b2731b374aaad2cb40c08928de9c84
406 lines
11 KiB
JavaScript
406 lines
11 KiB
JavaScript
/*!
|
|
* VisualEditor MediaWiki Initialization Target class.
|
|
*
|
|
* @copyright 2011-2016 VisualEditor Team and others; see AUTHORS.txt
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
*/
|
|
|
|
/**
|
|
* Initialization MediaWiki target.
|
|
*
|
|
* @class
|
|
* @extends ve.init.Target
|
|
*
|
|
* @constructor
|
|
* @param {Object} [config] Configuration options
|
|
*/
|
|
ve.init.mw.Target = function VeInitMwTarget( config ) {
|
|
// Parent constructor
|
|
ve.init.mw.Target.super.call( this, config );
|
|
|
|
this.active = false;
|
|
|
|
// Initialization
|
|
this.$element.addClass( 've-init-mw-target' );
|
|
};
|
|
|
|
/* Inheritance */
|
|
|
|
OO.inheritClass( ve.init.mw.Target, ve.init.Target );
|
|
|
|
/* Events */
|
|
|
|
/**
|
|
* @event surfaceReady
|
|
*/
|
|
|
|
/* Static Properties */
|
|
|
|
/**
|
|
* Symbolic name for this target class.
|
|
*
|
|
* @static
|
|
* @property {string}
|
|
* @inheritable
|
|
*/
|
|
ve.init.mw.Target.static.name = null;
|
|
|
|
ve.init.mw.Target.static.toolbarGroups = [
|
|
// History
|
|
{ include: [ 'undo', 'redo' ] },
|
|
// Format
|
|
{
|
|
classes: [ 've-test-toolbar-format' ],
|
|
type: 'menu',
|
|
indicator: 'down',
|
|
title: OO.ui.deferMsg( 'visualeditor-toolbar-format-tooltip' ),
|
|
include: [ { group: 'format' } ],
|
|
promote: [ 'paragraph' ],
|
|
demote: [ 'preformatted', 'blockquote', 'heading1' ]
|
|
},
|
|
// Style
|
|
{
|
|
classes: [ 've-test-toolbar-style' ],
|
|
type: 'list',
|
|
icon: 'textStyle',
|
|
indicator: 'down',
|
|
title: OO.ui.deferMsg( 'visualeditor-toolbar-style-tooltip' ),
|
|
include: [ { group: 'textStyle' }, 'language', 'clear' ],
|
|
forceExpand: [ 'bold', 'italic', 'clear' ],
|
|
promote: [ 'bold', 'italic' ],
|
|
demote: [ 'strikethrough', 'code', 'underline', 'language', 'big', 'small', 'clear' ]
|
|
},
|
|
// Link
|
|
{ include: [ 'link' ] },
|
|
// Structure
|
|
{
|
|
classes: [ 've-test-toolbar-structure' ],
|
|
type: 'list',
|
|
icon: 'listBullet',
|
|
indicator: 'down',
|
|
title: OO.ui.deferMsg( 'visualeditor-toolbar-structure' ),
|
|
include: [ { group: 'structure' } ],
|
|
demote: [ 'outdent', 'indent' ]
|
|
},
|
|
// Insert
|
|
{
|
|
classes: [ 've-test-toolbar-insert' ],
|
|
label: OO.ui.deferMsg( 'visualeditor-toolbar-insert' ),
|
|
indicator: 'down',
|
|
title: OO.ui.deferMsg( 'visualeditor-toolbar-insert' ),
|
|
include: '*',
|
|
forceExpand: [ 'media', 'transclusion', 'insertTable' ],
|
|
promote: [ 'media', 'transclusion', 'insertTable' ]
|
|
},
|
|
// SpecialCharacter
|
|
{ include: [ 'specialCharacter' ] }
|
|
];
|
|
|
|
ve.init.mw.Target.static.importRules = {
|
|
external: {
|
|
blacklist: [
|
|
// Annotations
|
|
'link/mwExternal', 'textStyle/span', 'textStyle/font', 'textStyle/underline', 'meta/language', 'textStyle/datetime',
|
|
// Nodes
|
|
'article', 'section', 'div', 'alienInline', 'alienBlock', 'comment'
|
|
],
|
|
htmlBlacklist: {
|
|
remove: [ 'sup.reference:not( [typeof] )' ],
|
|
unwrap: [ 'fieldset', 'legend' ]
|
|
},
|
|
removeOriginalDomElements: true,
|
|
nodeSanitization: true
|
|
},
|
|
all: null
|
|
};
|
|
|
|
/**
|
|
* Type of integration. Used by ve.init.mw.trackSubscriber.js for event tracking.
|
|
*
|
|
* @static
|
|
* @property {string}
|
|
* @inheritable
|
|
*/
|
|
ve.init.mw.Target.static.integrationType = null;
|
|
|
|
/**
|
|
* Type of platform. Used by ve.init.mw.trackSubscriber.js for event tracking.
|
|
*
|
|
* @static
|
|
* @property {string}
|
|
* @inheritable
|
|
*/
|
|
ve.init.mw.Target.static.platformType = null;
|
|
|
|
/* Static Methods */
|
|
|
|
/**
|
|
* Fix the base URL from Parsoid if necessary.
|
|
*
|
|
* Absolutizes the base URL if it's relative, and sets a base URL based on wgArticlePath
|
|
* if there was no base URL at all.
|
|
*
|
|
* @param {HTMLDocument} doc Parsoid document
|
|
*/
|
|
ve.init.mw.Target.static.fixBase = function ( doc ) {
|
|
ve.fixBase( doc, document, ve.resolveUrl(
|
|
// Don't replace $1 with the page name, because that'll break if
|
|
// the page name contains a slash
|
|
mw.config.get( 'wgArticlePath' ).replace( '$1', '' ),
|
|
document
|
|
) );
|
|
};
|
|
|
|
/* Methods */
|
|
|
|
/**
|
|
* Parse document string into an HTML document
|
|
*
|
|
* @param {string} documentString Document
|
|
* @param {string} mode Editing mode
|
|
* @return {HTMLDocument} HTML document
|
|
*/
|
|
ve.init.mw.Target.prototype.parseDocument = function ( documentString, mode ) {
|
|
var doc;
|
|
if ( mode === 'source' ) {
|
|
// Parse as plain text in source mode
|
|
doc = ve.createDocumentFromHtml( '' );
|
|
|
|
documentString.split( '\n' ).forEach( function ( line ) {
|
|
var p = doc.createElement( 'p' );
|
|
p.appendChild( doc.createTextNode( line ) );
|
|
doc.body.appendChild( p );
|
|
} );
|
|
} else {
|
|
doc = ve.parseXhtml( documentString );
|
|
|
|
// Fix relative or missing base URL if needed
|
|
this.constructor.static.fixBase( doc );
|
|
}
|
|
return doc;
|
|
};
|
|
|
|
/**
|
|
* Handle both DOM and modules being loaded and ready.
|
|
*
|
|
* @param {HTMLDocument} doc HTML document
|
|
*/
|
|
ve.init.mw.Target.prototype.documentReady = function ( doc ) {
|
|
this.setupSurface( doc, this.surfaceReady.bind( this ) );
|
|
};
|
|
|
|
/**
|
|
* Once surface is ready ready, initialize the UI
|
|
*
|
|
* @method
|
|
* @fires surfaceReady
|
|
*/
|
|
ve.init.mw.Target.prototype.surfaceReady = function () {
|
|
this.emit( 'surfaceReady' );
|
|
};
|
|
|
|
/**
|
|
* Get HTML to send to Parsoid. This takes a document generated by the converter and
|
|
* transplants the head tag from the old document into it, as well as the attributes on the
|
|
* html and body tags.
|
|
*
|
|
* @param {HTMLDocument} newDoc Document generated by ve.dm.Converter. Will be modified.
|
|
* @param {HTMLDocument} [oldDoc] Old document to copy attributes from.
|
|
* @return {string} Full HTML document
|
|
*/
|
|
ve.init.mw.Target.prototype.getHtml = function ( newDoc, oldDoc ) {
|
|
var i, len;
|
|
|
|
function copyAttributes( from, to ) {
|
|
var i, len;
|
|
for ( i = 0, len = from.attributes.length; i < len; i++ ) {
|
|
to.setAttribute( from.attributes[ i ].name, from.attributes[ i ].value );
|
|
}
|
|
}
|
|
|
|
if ( oldDoc ) {
|
|
// Copy the head from the old document
|
|
for ( i = 0, len = oldDoc.head.childNodes.length; i < len; i++ ) {
|
|
newDoc.head.appendChild( oldDoc.head.childNodes[ i ].cloneNode( true ) );
|
|
}
|
|
// Copy attributes from the old document for the html, head and body
|
|
copyAttributes( oldDoc.documentElement, newDoc.documentElement );
|
|
copyAttributes( oldDoc.head, newDoc.head );
|
|
copyAttributes( oldDoc.body, newDoc.body );
|
|
}
|
|
|
|
// Filter out junk that may have been added by browser plugins
|
|
$( newDoc )
|
|
.find(
|
|
'script, ' + // T54884, T65229, T96533, T103430
|
|
'noscript, ' + // T144891
|
|
'object, ' + // T65229
|
|
'style, ' + // T55252
|
|
'embed, ' + // T53521, T54791, T65121
|
|
'div[id="myEventWatcherDiv"], ' + // T53423
|
|
'div[id="sendToInstapaperResults"], ' + // T63776
|
|
'div[id="kloutify"], ' + // T69006
|
|
'div[id^="mittoHidden"]' // T70900
|
|
)
|
|
.remove();
|
|
// Add doctype manually
|
|
return '<!doctype html>' + ve.serializeXhtml( newDoc );
|
|
};
|
|
|
|
/**
|
|
* Track an event
|
|
*
|
|
* @param {string} name Event name
|
|
*/
|
|
ve.init.mw.Target.prototype.track = function () {};
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
ve.init.mw.Target.prototype.createTargetWidget = function ( config ) {
|
|
if ( this.getSurface().getMode() === 'source' ) {
|
|
// Reset to visual mode for target widgets
|
|
return new ve.ui.MWTargetWidget( ve.extendObject( {
|
|
commandRegistry: ve.ui.commandRegistry,
|
|
sequenceRegistry: ve.ui.sequenceRegistry,
|
|
dataTransferHandlerFactory: ve.ui.dataTransferHandlerFactory
|
|
}, config ) );
|
|
} else {
|
|
return new ve.ui.MWTargetWidget( config );
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
ve.init.mw.Target.prototype.createSurface = function ( dmDoc, config ) {
|
|
var importRules, surface, surfaceView, $documentNode;
|
|
|
|
if ( config && config.mode === 'source' ) {
|
|
importRules = ve.copy( this.constructor.static.importRules );
|
|
importRules.all = importRules.all || {};
|
|
// Preserve empty linebreaks on paste in source editor
|
|
importRules.all.keepEmptyContentBranches = true;
|
|
config = this.getSurfaceConfig( ve.extendObject( {}, config, {
|
|
commandRegistry: ve.ui.wikitextCommandRegistry,
|
|
sequenceRegistry: ve.ui.wikitextSequenceRegistry,
|
|
dataTransferHandlerFactory: ve.ui.wikitextDataTransferHandlerFactory,
|
|
importRules: importRules
|
|
} ) );
|
|
return new ve.ui.MWDesktopWikitextSurface( dmDoc, config );
|
|
}
|
|
|
|
// Parent method
|
|
surface = ve.init.mw.Target.super.prototype.createSurface.apply( this, arguments );
|
|
|
|
surfaceView = surface.getView();
|
|
$documentNode = surfaceView.getDocument().getDocumentNode().$element;
|
|
|
|
surface.$element.addClass( this.protectedClasses );
|
|
|
|
$documentNode.addClass(
|
|
// Add appropriately mw-content-ltr or mw-content-rtl class
|
|
'mw-content-' + mw.config.get( 'wgVisualEditor' ).pageLanguageDir
|
|
);
|
|
|
|
return surface;
|
|
};
|
|
|
|
/**
|
|
* Create a document model from an HTML document.
|
|
*
|
|
* @param {HTMLDocument} doc HTML document
|
|
* @param {string} mode Editing mode
|
|
* @return {ve.dm.Document} Document model
|
|
*/
|
|
ve.init.mw.Target.prototype.createModelFromDom = function ( doc, mode ) {
|
|
var i, l, children, data,
|
|
conf = mw.config.get( 'wgVisualEditor' );
|
|
|
|
if ( mode === 'source' ) {
|
|
children = doc.body.children;
|
|
data = [];
|
|
|
|
// Wikitext documents are just plain text paragraphs, so we can just do a simple manual conversion.
|
|
for ( i = 0, l = children.length; i < l; i++ ) {
|
|
data.push( { type: 'paragraph' } );
|
|
ve.batchPush( data, children[ i ].textContent.split( '' ) );
|
|
data.push( { type: '/paragraph' } );
|
|
}
|
|
data.push( { type: 'internalList' }, { type: '/internalList' } );
|
|
return new ve.dm.Document( data, doc, null, null, null, conf.pageLanguageCode, conf.pageLanguageDir );
|
|
} else {
|
|
return ve.dm.converter.getModelFromDom( doc, {
|
|
lang: conf.pageLanguageCode,
|
|
dir: conf.pageLanguageDir
|
|
} );
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Switch to editing mode.
|
|
*
|
|
* @method
|
|
* @param {HTMLDocument} doc HTML document
|
|
* @param {Function} [callback] Callback to call when done
|
|
*/
|
|
ve.init.mw.Target.prototype.setupSurface = function ( doc, callback ) {
|
|
var target = this;
|
|
setTimeout( function () {
|
|
// Build model
|
|
var dmDoc;
|
|
|
|
target.track( 'trace.convertModelFromDom.enter' );
|
|
dmDoc = target.createModelFromDom( doc, target.getDefaultMode() );
|
|
target.track( 'trace.convertModelFromDom.exit' );
|
|
|
|
// Build DM tree now (otherwise it gets lazily built when building the CE tree)
|
|
target.track( 'trace.buildModelTree.enter' );
|
|
dmDoc.buildNodeTree();
|
|
target.track( 'trace.buildModelTree.exit' );
|
|
|
|
setTimeout( function () {
|
|
var surface;
|
|
// Clear dummy surfaces
|
|
target.clearSurfaces();
|
|
|
|
// Create ui.Surface (also creates ce.Surface and dm.Surface and builds CE tree)
|
|
target.track( 'trace.createSurface.enter' );
|
|
surface = target.addSurface( dmDoc );
|
|
// Add classes specific to surfaces attached directly to the target,
|
|
// as opposed to TargetWidget surfaces
|
|
surface.$element.addClass( 've-init-mw-target-surface' );
|
|
target.track( 'trace.createSurface.exit' );
|
|
|
|
target.dummyToolbar = false;
|
|
|
|
target.setSurface( surface );
|
|
|
|
setTimeout( function () {
|
|
// Initialize surface
|
|
target.track( 'trace.initializeSurface.enter' );
|
|
|
|
target.active = true;
|
|
// Now that the surface is attached to the document and ready,
|
|
// let it initialize itself
|
|
surface.initialize();
|
|
|
|
target.track( 'trace.initializeSurface.exit' );
|
|
setTimeout( callback );
|
|
} );
|
|
} );
|
|
} );
|
|
};
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
ve.init.mw.Target.prototype.setSurface = function ( surface ) {
|
|
if ( !surface.$element.parent().length ) {
|
|
this.$element.append( surface.$element );
|
|
}
|
|
|
|
// Parent method
|
|
ve.init.mw.Target.super.prototype.setSurface.apply( this, arguments );
|
|
};
|