/*! * VisualEditor UserInterface MWSaveDialog class. * * @copyright 2011-2018 VisualEditor Team and others; see AUTHORS.txt * @license The MIT License (MIT); see LICENSE.txt */ /** * Dialog for saving MediaWiki pages. * * Note that most methods are not safe to call before the dialog has initialized, except where * noted otherwise. * * @class * @extends OO.ui.ProcessDialog * * @constructor * @param {Object} [config] Config options */ ve.ui.MWSaveDialog = function VeUiMwSaveDialog( config ) { // Parent constructor ve.ui.MWSaveDialog.super.call( this, config ); // Properties this.editSummaryByteLimit = mw.config.get( 'wgCommentByteLimit' ); this.editSummaryCodePointLimit = mw.config.get( 'wgCommentCodePointLimit' ); this.restoring = false; this.messages = {}; this.setupDeferred = $.Deferred(); this.checkboxesByName = null; this.changedEditSummary = false; this.canReview = false; this.canPreview = false; this.hasDiff = false; this.diffElement = null; this.diffElementPromise = null; this.getDiffElementPromise = null; // Initialization this.$element.addClass( 've-ui-mwSaveDialog' ); }; /* Inheritance */ OO.inheritClass( ve.ui.MWSaveDialog, OO.ui.ProcessDialog ); /* Static Properties */ ve.ui.MWSaveDialog.static.name = 'mwSave'; ve.ui.MWSaveDialog.static.title = OO.ui.deferMsg( 'visualeditor-savedialog-title-save' ); ve.ui.MWSaveDialog.static.feedbackUrl = 'https://www.mediawiki.org/wiki/Talk:VisualEditor/Diffs'; ve.ui.MWSaveDialog.static.actions = [ { action: 'save', // May be overridden by config.saveButtonLabel label: OO.ui.deferMsg( 'visualeditor-savedialog-label-review' ), flags: [ 'primary', 'progressive' ], modes: [ 'save', 'review', 'preview' ] }, { label: OO.ui.deferMsg( 'visualeditor-savedialog-label-resume-editing' ), flags: [ 'safe', 'back' ], modes: [ 'save', 'review', 'preview', 'conflict' ] }, { action: 'review', label: OO.ui.deferMsg( 'visualeditor-savedialog-label-review' ), modes: [ 'save', 'preview' ] }, { action: 'preview', label: OO.ui.deferMsg( 'showpreview' ), modes: [ 'save', 'review' ] }, { action: 'approve', label: OO.ui.deferMsg( 'visualeditor-savedialog-label-review-good' ), modes: [ 'review', 'preview' ] }, { action: 'resolve', label: OO.ui.deferMsg( 'visualeditor-savedialog-label-resolve-conflict' ), flags: [ 'primary', 'progressive' ], modes: 'conflict' }, { action: 'report', label: OO.ui.deferMsg( 'visualeditor-savedialog-label-visual-diff-report' ), flags: [ 'progressive' ], modes: 'review', framed: false, icon: 'feedback', classes: [ 've-ui-mwSaveDialog-visualDiffFeedback' ], href: ve.ui.MWSaveDialog.static.feedbackUrl } ]; /* Events */ /** * @event save * @param {jQuery.Deferred} saveDeferred Deferred object to resolve/reject when the save * succeeds/fails. * Emitted when the user clicks the save button */ /** * @event review * Emitted when the user clicks the review changes button */ /** * @event preview * Emitted when the user clicks the show preview button */ /** * @event resolve * Emitted when the user clicks the resolve conflict button */ /** * @event retry * Emitted when the user clicks the retry/continue save button after an error. */ /* Methods */ /** * Set review content and show review panel. * * @param {jQuery.Promise} wikitextDiffPromise Wikitext diff HTML promise * @param {jQuery.Promise} visualDiffGeneratorPromise Visual diff promise * @param {HTMLDocument} [baseDoc] Base document against which to normalise links when rendering visualDiff */ ve.ui.MWSaveDialog.prototype.setDiffAndReview = function ( wikitextDiffPromise, visualDiffGeneratorPromise, baseDoc ) { var dialog = this; this.clearDiff(); function createDiffElement( visualDiff ) { var diffElement = new ve.ui.DiffElement( visualDiff ); diffElement.$document.addClass( 'mw-body-content mw-parser-output' ); // Run styles so links render with their appropriate classes ve.init.platform.linkCache.styleParsoidElements( diffElement.$document, baseDoc ); return diffElement; } // Visual diff this.$reviewVisualDiff.append( new OO.ui.ProgressBarWidget().$element ); // Don't generate the DiffElement until the tab is switched to this.getDiffElementPromise = function () { return visualDiffGeneratorPromise.then( function ( visualDiffGenerator ) { return createDiffElement( visualDiffGenerator() ); } ); }; this.baseDoc = baseDoc; // Wikitext diff this.$reviewWikitextDiff.append( new OO.ui.ProgressBarWidget().$element ); wikitextDiffPromise.then( function ( wikitextDiff ) { if ( wikitextDiff ) { dialog.$reviewWikitextDiff.empty().append( wikitextDiff ); } else { dialog.$reviewWikitextDiff.empty().append( $( '
' ).addClass( 've-ui-mwSaveDialog-no-changes' ).text( ve.msg( 'visualeditor-diff-no-changes' ) ) ); } }, function ( error ) { dialog.$reviewWikitextDiff.empty().append( error ); } ).always( function () { dialog.updateSize(); } ); this.hasDiff = true; this.popPending(); this.swapPanel( 'review' ); }; /** * Set preview content and show preview panel. * * @param {HTMLDocument|string} docOrMsg Document to preview, or error message * @param {HTMLDocument} [baseDoc] Base document against which to normalise links, if document provided */ ve.ui.MWSaveDialog.prototype.showPreview = function ( docOrMsg, baseDoc ) { var body, contents, $heading, redirectMeta, $redirect = $(), categories = [], modules = []; if ( docOrMsg instanceof HTMLDocument ) { // Extract required modules for stylesheet tags (avoids re-loading styles) Array.prototype.forEach.call( docOrMsg.head.querySelectorAll( 'link[rel~=stylesheet]' ), function ( link ) { var uri = new mw.Uri( link.href ); if ( uri.query.modules ) { modules = modules.concat( ve.expandModuleNames( uri.query.modules ) ); } } ); // Remove skin-specific modules (T187075) modules = modules.filter( function ( module ) { return module.indexOf( 'skins.' ) !== 0; } ); mw.loader.using( modules ); body = docOrMsg.body; // Take a snapshot of all categories Array.prototype.forEach.call( body.querySelectorAll( 'link[rel~="mw:PageProp/Category"]' ), function ( element ) { categories.push( ve.dm.MWCategoryMetaItem.static.toDataElement( [ element ] ).attributes.category ); } ); // Import body to current document, then resolve attributes against original document (parseDocument called #fixBase) document.adoptNode( body ); // TODO: This code is very similar to ve.ui.PreviewElement+ve.ui.MWPreviewElement ve.resolveAttributes( body, docOrMsg, ve.dm.Converter.static.computedAttributes ); // Remove metadata contents = ve.filterMetaElements( Array.prototype.slice.call( body.childNodes ) ); $heading = $( '

' ).addClass( 'firstHeading' ); // Document title will only be set if wikitext contains {{DISPLAYTITLE}} if ( docOrMsg.title ) { // HACK: Parse title as it can contain basic wikitext (T122976) new mw.Api().post( { action: 'parse', title: ve.init.target.pageName, prop: 'displaytitle', text: '{{DISPLAYTITLE:' + docOrMsg.title + '}}\n' } ).then( function ( response ) { if ( ve.getProp( response, 'parse', 'displaytitle' ) ) { $heading.html( response.parse.displaytitle ); } } ); } // Redirect redirectMeta = body.querySelector( 'link[rel="mw:PageProp/redirect"]' ); if ( redirectMeta ) { $redirect = ve.init.mw.ArticleTarget.static.buildRedirectMsg( ve.dm.MWInternalLinkAnnotation.static.getTargetDataFromHref( redirectMeta.getAttribute( 'href' ), document ).title ); } this.$previewViewer.empty().append( // TODO: This won't work with formatted titles (T122976) $heading.text( docOrMsg.title || mw.Title.newFromText( ve.init.target.pageName ).getPrefixedText() ), $redirect, $( '
' ).addClass( 'mw-content-' + mw.config.get( 'wgVisualEditor' ).pageLanguageDir ).append( contents ) ); if ( categories.length ) { // Simple category list rendering this.$previewViewer.append( $( '
' ).addClass( 'catlinks' ).append( document.createTextNode( ve.msg( 'pagecategories', categories.length ) + ve.msg( 'colon-separator' ) ), $( '