X-Git-Url: https://scripts.mit.edu/gitweb/autoinstallsdev/mediawiki.git/blobdiff_plain/19e297c21b10b1b8a3acad5e73fc71dcb35db44a..6932310fd58ebef145fa01eb76edf7150284d8ea:/extensions/Cite/modules/ve-cite/ve.ui.MWReferenceDialog.js diff --git a/extensions/Cite/modules/ve-cite/ve.ui.MWReferenceDialog.js b/extensions/Cite/modules/ve-cite/ve.ui.MWReferenceDialog.js new file mode 100644 index 00000000..a8fcd0ca --- /dev/null +++ b/extensions/Cite/modules/ve-cite/ve.ui.MWReferenceDialog.js @@ -0,0 +1,434 @@ +/*! + * VisualEditor UserInterface MediaWiki MWReferenceDialog class. + * + * @copyright 2011-2017 Cite VisualEditor Team and others; see AUTHORS.txt + * @license The MIT License (MIT); see LICENSE.txt + */ + +/** + * Dialog for editing MediaWiki references. + * + * @class + * @extends ve.ui.NodeDialog + * + * @constructor + * @param {Object} [config] Configuration options + */ +ve.ui.MWReferenceDialog = function VeUiMWReferenceDialog( config ) { + // Parent constructor + ve.ui.MWReferenceDialog.super.call( this, config ); + + // Properties + this.referenceModel = null; + this.useExisting = false; +}; + +/* Inheritance */ + +OO.inheritClass( ve.ui.MWReferenceDialog, ve.ui.NodeDialog ); + +/* Static Properties */ + +ve.ui.MWReferenceDialog.static.name = 'reference'; + +ve.ui.MWReferenceDialog.static.title = + OO.ui.deferMsg( 'cite-ve-dialog-reference-title' ); + +ve.ui.MWReferenceDialog.static.actions = [ + { + action: 'apply', + label: OO.ui.deferMsg( 'visualeditor-dialog-action-apply' ), + flags: [ 'progressive', 'primary' ], + modes: 'edit' + }, + { + action: 'insert', + label: OO.ui.deferMsg( 'visualeditor-dialog-action-insert' ), + flags: [ 'primary', 'constructive' ], + modes: 'insert' + }, + { + label: OO.ui.deferMsg( 'visualeditor-dialog-action-cancel' ), + flags: [ 'safe', 'back' ], + modes: [ 'insert', 'edit', 'insert-select' ] + }, + { + action: 'select', + label: OO.ui.deferMsg( 'cite-ve-dialog-reference-useexisting-label' ), + modes: [ 'insert', 'edit' ] + }, + { + action: 'back', + label: OO.ui.deferMsg( 'visualeditor-dialog-action-goback' ), + flags: 'safe', + modes: 'select' + } +]; + +ve.ui.MWReferenceDialog.static.modelClasses = [ ve.dm.MWReferenceNode ]; + +ve.ui.MWReferenceDialog.static.includeCommands = null; + +ve.ui.MWReferenceDialog.static.excludeCommands = [ + // No formatting + 'paragraph', + 'heading1', + 'heading2', + 'heading3', + 'heading4', + 'heading5', + 'heading6', + 'preformatted', + 'blockquote', + // No tables + 'insertTable', + 'deleteTable', + 'mergeCells', + 'tableCaption', + 'tableCellHeader', + 'tableCellData', + // No structure + 'bullet', + 'bulletWrapOnce', + 'number', + 'numberWrapOnce', + 'indent', + 'outdent', + // References + 'reference', + 'reference/existing', + 'citefromid', + 'referencesList' +]; + +/** + * Get the import rules for the surface widget in the dialog. + * + * @see ve.dm.ElementLinearData#sanitize + * @return {Object} Import rules + */ +ve.ui.MWReferenceDialog.static.getImportRules = function () { + return ve.extendObject( + ve.copy( ve.init.target.constructor.static.importRules ), + { + all: { + blacklist: OO.simpleArrayUnion( + ve.getProp( ve.init.target.constructor.static.importRules, 'all', 'blacklist' ) || [], + [ + // Nested references are impossible + 'mwReference', 'mwReferencesList', + // Lists and tables are actually possible in wikitext with a leading + // line break but we prevent creating these with the UI + 'list', 'listItem', 'definitionList', 'definitionListItem', + 'table', 'tableCaption', 'tableSection', 'tableRow', 'tableCell' + ] + ), + // Headings are not possible in wikitext without HTML + conversions: { + mwHeading: 'paragraph' + } + } + } + ); +}; + +/* Methods */ + +/** + * Determine whether the reference document we're editing has any content. + * + * @return {boolean} Document has content + */ +ve.ui.MWReferenceDialog.prototype.documentHasContent = function () { + // TODO: Check for other types of empty, e.g. only whitespace? + return this.referenceModel.getDocument().data.hasContent(); +}; + +/* + * Determine whether any changes have been made (and haven't been undone). + * + * @return {boolean} Dialog can be applied + */ +ve.ui.MWReferenceDialog.prototype.canApply = function () { + return this.documentHasContent() && + ( this.referenceTarget.hasBeenModified() || + this.referenceGroupInput.getValue() !== this.originalGroup ); +}; + +/** + * Handle reference target widget change events + */ +ve.ui.MWReferenceDialog.prototype.onTargetChange = function () { + var hasContent = this.documentHasContent(); + + this.actions.setAbilities( { + apply: this.canApply(), + insert: hasContent, + select: !hasContent && !this.search.isIndexEmpty() + } ); +}; + +/** + * Handle reference group input change events. + */ +ve.ui.MWReferenceDialog.prototype.onReferenceGroupInputChange = function () { + this.actions.setAbilities( { + apply: this.canApply() + } ); +}; + +/** + * Handle search results choose events. + * + * @param {ve.ui.MWReferenceResultWidget} item Chosen item + */ +ve.ui.MWReferenceDialog.prototype.onSearchResultsChoose = function ( item ) { + var ref = item.getData(); + + if ( this.selectedNode instanceof ve.dm.MWReferenceNode ) { + this.getFragment().removeContent(); + this.selectedNode = null; + } + this.useReference( ref ); + this.executeAction( 'insert' ); +}; + +/** + * @inheritdoc + */ +ve.ui.MWReferenceDialog.prototype.getReadyProcess = function ( data ) { + return ve.ui.MWReferenceDialog.super.prototype.getReadyProcess.call( this, data ) + .next( function () { + if ( this.useExisting ) { + this.search.getQuery().focus().select(); + } else { + this.referenceTarget.focus(); + } + }, this ); +}; + +/** + * @inheritdoc + */ +ve.ui.MWReferenceDialog.prototype.getBodyHeight = function () { + // Clamp value to between 300 and 400px height, preferring the actual height if available + return Math.min( + 400, + Math.max( + 300, + Math.ceil( this.panels.getCurrentItem().$element[ 0 ].scrollHeight ) + ) + ); +}; + +// eslint-disable-next-line valid-jsdoc +/** + * Work on a specific reference. + * + * @param {ve.dm.MWReferenceModel} [ref] Reference model, omit to work on a new reference + * @chainable + */ +ve.ui.MWReferenceDialog.prototype.useReference = function ( ref ) { + var group; + + // Properties + if ( ref instanceof ve.dm.MWReferenceModel ) { + // Use an existing reference + this.referenceModel = ref; + } else { + // Create a new reference + this.referenceModel = new ve.dm.MWReferenceModel( this.getFragment().getDocument() ); + } + + this.referenceTarget.setDocument( this.referenceModel.getDocument() ); + + // Initialization + this.originalGroup = this.referenceModel.getGroup(); + // Set the group input while it's disabled, so this doesn't pop up the group-picker menu + this.referenceGroupInput.setDisabled( true ); + this.referenceGroupInput.setValue( this.originalGroup ); + this.referenceGroupInput.setDisabled( false ); + this.referenceTarget.initialize(); + + group = this.getFragment().getDocument().getInternalList() + .getNodeGroup( this.referenceModel.getListGroup() ); + if ( ve.getProp( group, 'keyedNodes', this.referenceModel.getListKey(), 'length' ) > 1 ) { + this.$reuseWarning.removeClass( 'oo-ui-element-hidden' ); + this.$reuseWarningText.text( mw.msg( + 'cite-ve-dialog-reference-editing-reused', + group.keyedNodes[ this.referenceModel.getListKey() ].length + ) ); + } else { + this.$reuseWarning.addClass( 'oo-ui-element-hidden' ); + } + + return this; +}; + +/** + * @inheritdoc + */ +ve.ui.MWReferenceDialog.prototype.initialize = function () { + var citeCommands = Object.keys( ve.init.target.getSurface().commandRegistry.registry ).filter( function ( command ) { + return command.indexOf( 'cite-' ) !== -1; + } ); + + // Parent method + ve.ui.MWReferenceDialog.super.prototype.initialize.call( this ); + + // Properties + this.panels = new OO.ui.StackLayout(); + this.editPanel = new OO.ui.PanelLayout( { + scrollable: true, padded: true + } ); + this.searchPanel = new OO.ui.PanelLayout(); + + this.reuseWarningIcon = new OO.ui.IconWidget( { icon: 'alert' } ); + this.$reuseWarningText = $( '' ); + this.$reuseWarning = $( '' ).append( this.reuseWarningIcon.$element, this.$reuseWarningText ); + + this.referenceTarget = ve.init.target.createTargetWidget( + { + tools: ve.copy( ve.init.target.constructor.static.toolbarGroups ), + includeCommands: this.constructor.static.includeCommands, + excludeCommands: this.constructor.static.excludeCommands.concat( citeCommands ), + importRules: this.constructor.static.getImportRules(), + inDialog: this.constructor.static.name, + placeholder: ve.msg( 'cite-ve-dialog-reference-placeholder' ) + } + ); + + this.contentFieldset = new OO.ui.FieldsetLayout(); + this.optionsFieldset = new OO.ui.FieldsetLayout( { + label: ve.msg( 'cite-ve-dialog-reference-options-section' ), + icon: 'settings' + } ); + this.contentFieldset.$element.append( this.referenceTarget.$element ); + + this.referenceGroupInput = new ve.ui.MWReferenceGroupInputWidget( { + $overlay: this.$overlay, + emptyGroupName: ve.msg( 'cite-ve-dialog-reference-options-group-placeholder' ) + } ); + this.referenceGroupInput.connect( this, { change: 'onReferenceGroupInputChange' } ); + this.referenceGroupField = new OO.ui.FieldLayout( this.referenceGroupInput, { + align: 'top', + label: ve.msg( 'cite-ve-dialog-reference-options-group-label' ) + } ); + this.search = new ve.ui.MWReferenceSearchWidget(); + + // Events + this.search.getResults().connect( this, { choose: 'onSearchResultsChoose' } ); + this.referenceTarget.connect( this, { change: 'onTargetChange' } ); + + // Initialization + this.panels.addItems( [ this.editPanel, this.searchPanel ] ); + this.editPanel.$element.append( this.$reuseWarning, this.contentFieldset.$element, this.optionsFieldset.$element ); + this.optionsFieldset.addItems( [ this.referenceGroupField ] ); + this.searchPanel.$element.append( this.search.$element ); + this.$body.append( this.panels.$element ); +}; + +/** + * Switches dialog to use existing reference mode. + * + * @param {string} [action='select'] Symbolic name of action, either 'select' or 'insert-select' + */ +ve.ui.MWReferenceDialog.prototype.useExistingReference = function ( action ) { + action = action || 'select'; + if ( action === 'insert-select' || action === 'select' ) { + this.actions.setMode( action ); + } + this.search.buildIndex(); + this.panels.setItem( this.searchPanel ); + this.search.getQuery().focus().select(); +}; + +/** + * @inheritdoc + */ +ve.ui.MWReferenceDialog.prototype.getActionProcess = function ( action ) { + if ( action === 'insert' || action === 'apply' ) { + return new OO.ui.Process( function () { + var surfaceModel = this.getFragment().getSurface(); + + this.referenceModel.setGroup( this.referenceGroupInput.getValue() ); + + // Insert reference (will auto-create an internal item if needed) + if ( !( this.selectedNode instanceof ve.dm.MWReferenceNode ) ) { + if ( !this.referenceModel.findInternalItem( surfaceModel ) ) { + this.referenceModel.insertInternalItem( surfaceModel ); + } + // Collapse returns a new fragment, so update this.fragment + this.fragment = this.getFragment().collapseToEnd(); + this.referenceModel.insertReferenceNode( this.getFragment() ); + } + + // Update internal item + this.referenceModel.updateInternalItem( surfaceModel ); + + this.close( { action: action } ); + }, this ); + } else if ( action === 'back' ) { + this.actions.setMode( this.selectedNode ? 'edit' : 'insert' ); + this.panels.setItem( this.editPanel ); + this.editPanel.$element.find( '.ve-ce-documentNode' )[ 0 ].focus(); + } else if ( action === 'select' || action === 'insert-select' ) { + this.useExistingReference( action ); + } + return ve.ui.MWReferenceDialog.super.prototype.getActionProcess.call( this, action ); +}; + +/** + * @inheritdoc + * @param {Object} [data] Setup data + * @param {boolean} [data.useExistingReference] Open the dialog in "use existing reference" mode + */ +ve.ui.MWReferenceDialog.prototype.getSetupProcess = function ( data ) { + data = data || {}; + return ve.ui.MWReferenceDialog.super.prototype.getSetupProcess.call( this, data ) + .next( function () { + this.panels.setItem( this.editPanel ); + if ( this.selectedNode instanceof ve.dm.MWReferenceNode ) { + this.useReference( + ve.dm.MWReferenceModel.static.newFromReferenceNode( this.selectedNode ) + ); + } else { + this.useReference( null ); + this.actions.setAbilities( { apply: false, insert: false } ); + } + + this.actions.setMode( this.selectedNode ? 'edit' : 'insert' ); + this.search.setInternalList( this.getFragment().getDocument().getInternalList() ); + + if ( data.useExisting ) { + this.useExistingReference( 'insert-select' ); + } + this.useExisting = !!data.useExisting; + // If we're using an existing reference, start off disabled + // If not, set disabled based on whether or not there are any existing ones. + this.actions.setAbilities( { + select: !( this.selectedNode instanceof ve.dm.MWReferenceNode ) && + !this.search.isIndexEmpty(), + apply: false + } ); + + this.referenceGroupInput.populateMenu( this.getFragment().getDocument().getInternalList() ); + }, this ); +}; + +/** + * @inheritdoc + */ +ve.ui.MWReferenceDialog.prototype.getTeardownProcess = function ( data ) { + return ve.ui.MWReferenceDialog.super.prototype.getTeardownProcess.call( this, data ) + .first( function () { + this.referenceTarget.getSurface().getModel().disconnect( this ); + this.search.getQuery().setValue( '' ); + this.referenceTarget.clear(); + this.referenceModel = null; + }, this ); +}; + +/* Registration */ + +ve.ui.windowFactory.register( ve.ui.MWReferenceDialog );