/*! * VisualEditor MediaWiki Initialization Target class. * * @copyright 2011-2019 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; this.pageName = mw.config.get( 'wgRelevantPageName' ); this.editToken = mw.user.tokens.get( 'csrfToken' ); this.recovered = false; this.fromEditedState = false; this.originalHtml = null; // Initialization this.$element.addClass( 've-init-mw-target' ); }; /* Inheritance */ OO.inheritClass( ve.init.mw.Target, ve.init.Target ); /* 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 = [ { name: 'history', include: [ 'undo', 'redo' ] }, { name: 'format', type: 'menu', title: OO.ui.deferMsg( 'visualeditor-toolbar-format-tooltip' ), include: [ { group: 'format' } ], promote: [ 'paragraph' ], demote: [ 'preformatted', 'blockquote', 'heading1' ] }, { name: 'style', type: 'list', icon: 'textStyle', 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' ] }, { name: 'link', include: [ 'link' ] }, // Placeholder for reference tools (e.g. Cite and/or Citoid) { name: 'reference' }, { name: 'structure', type: 'list', icon: 'listBullet', title: OO.ui.deferMsg( 'visualeditor-toolbar-structure' ), include: [ { group: 'structure' } ], demote: [ 'outdent', 'indent' ] }, { name: 'insert', label: OO.ui.deferMsg( 'visualeditor-toolbar-insert' ), title: OO.ui.deferMsg( 'visualeditor-toolbar-insert' ), include: '*', forceExpand: [ 'media', 'transclusion', 'insertTable' ], promote: [ 'media', 'transclusion', 'insertTable' ] }, { name: 'specialCharacter', include: [ 'specialCharacter' ] } ]; ve.init.mw.Target.static.importRules = ve.copy( ve.init.mw.Target.static.importRules ); ve.init.mw.Target.static.importRules.external.removeOriginalDomElements = true; ve.init.mw.Target.static.importRules.external.blacklist = ve.extendObject( { // Annotations 'textStyle/underline': true, 'meta/language': true, 'textStyle/datetime': true, 'link/mwExternal': !mw.config.get( 'wgVisualEditorConfig' ).allowExternalLinkPaste, // Node article: true, section: true }, ve.init.mw.Target.static.importRules.external.blacklist ); ve.init.mw.Target.static.importRules.external.htmlBlacklist.remove = ve.extendObject( { // TODO: Create a plugin system for extending the blacklist, so this code // can be moved to the Cite extension. // Remove reference numbers copied from MW read mode (T150418) 'sup.reference:not( [typeof] )': true, // ...sometimes we need a looser match if the HTML has been mangled // in a third-party editor e.g. LibreOffice (T232461) // This selector would fail if the "cite_reference_link_prefix" message // were ever modified, but currently it isn't. 'a[ href *= "#cite_note" ]': true }, ve.init.mw.Target.static.importRules.external.htmlBlacklist.remove ); /** * 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 ) ); }; /** * @inheritdoc */ ve.init.mw.Target.static.createModelFromDom = function ( doc, mode, options ) { var conf = mw.config.get( 'wgVisualEditor' ); options = ve.extendObject( { lang: conf.pageLanguageCode, dir: conf.pageLanguageDir }, options ); // Parent method return ve.init.mw.Target.super.static.createModelFromDom.call( this, doc, mode, options ); }; // Deprecated alias ve.init.mw.Target.prototype.createModelFromDom = function () { return this.constructor.static.createModelFromDom.apply( this.constructor.static, arguments ); }; /** * @inheritdoc * @param {string} documentString * @param {string} mode * @param {number|string|null} section Section. Use null to unwrap all sections. * @param {boolean} [onlySection] Only return the requested section, otherwise returns the * whole document with just the requested section still wrapped (visual mode only). */ ve.init.mw.Target.static.parseDocument = function ( documentString, mode, section, onlySection ) { var doc, sectionNode; if ( mode === 'source' ) { // Parent method doc = ve.init.mw.Target.super.static.parseDocument.call( this, documentString, mode ); } else { // Parsoid documents are XHTML so we can use parseXhtml which fixed some IE issues. doc = ve.parseXhtml( documentString ); if ( section !== undefined ) { if ( onlySection ) { sectionNode = doc.body.querySelector( '[data-mw-section-id="' + section + '"]' ); doc.body.innerHTML = ''; if ( sectionNode ) { doc.body.appendChild( sectionNode ); } } else { // Strip Parsoid sections ve.unwrapParsoidSections( doc.body, section ); } } // Strip legacy IDs, for example in section headings ve.stripParsoidFallbackIds( doc.body ); // Fix relative or missing base URL if needed this.fixBase( doc ); } return doc; }; /* Methods */ /** * Handle both DOM and modules being loaded and ready. * * @param {HTMLDocument|string} doc HTML document or source text */ ve.init.mw.Target.prototype.documentReady = function ( doc ) { this.setupSurface( doc ); }; /** * Once surface is ready, initialize the UI * * @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:not( [ data-mw ] )', // T55252, but allow