X-Git-Url: https://scripts.mit.edu/gitweb/autoinstallsdev/mediawiki.git/blobdiff_plain/19e297c21b10b1b8a3acad5e73fc71dcb35db44a..6932310fd58ebef145fa01eb76edf7150284d8ea:/extensions/Cite/modules/ve-cite/ve.ce.MWReferencesListNode.js diff --git a/extensions/Cite/modules/ve-cite/ve.ce.MWReferencesListNode.js b/extensions/Cite/modules/ve-cite/ve.ce.MWReferencesListNode.js new file mode 100644 index 00000000..e4a708e7 --- /dev/null +++ b/extensions/Cite/modules/ve-cite/ve.ce.MWReferencesListNode.js @@ -0,0 +1,292 @@ +/*! + * VisualEditor ContentEditable MWReferencesListNode class. + * + * @copyright 2011-2017 Cite VisualEditor Team and others; see AUTHORS.txt + * @license The MIT License (MIT); see LICENSE.txt + */ + +/** + * ContentEditable MediaWiki references list node. + * + * @class + * @extends ve.ce.LeafNode + * @mixins ve.ce.FocusableNode + * + * @constructor + * @param {ve.dm.MWReferencesListNode} model Model to observe + * @param {Object} [config] Configuration options + */ +ve.ce.MWReferencesListNode = function VeCeMWReferencesListNode() { + // Parent constructor + ve.ce.MWReferencesListNode.super.apply( this, arguments ); + + // Mixin constructors + ve.ce.FocusableNode.call( this ); + + // Properties + this.internalList = null; + this.listNode = null; + + // DOM changes + this.$element.addClass( 've-ce-mwReferencesListNode' ); + this.$reflist = $( '
    ' ).addClass( 'mw-references references' ); + this.$refmsg = $( '

    ' ) + .addClass( 've-ce-mwReferencesListNode-muted' ); + + // Events + this.model.connect( this, { attributeChange: 'onAttributeChange' } ); + + this.updateDebounced = ve.debounce( this.update.bind( this ) ); + + // Initialization + this.updateDebounced(); +}; + +/* Inheritance */ + +OO.inheritClass( ve.ce.MWReferencesListNode, ve.ce.LeafNode ); + +OO.mixinClass( ve.ce.MWReferencesListNode, ve.ce.FocusableNode ); + +/* Static Properties */ + +ve.ce.MWReferencesListNode.static.name = 'mwReferencesList'; + +ve.ce.MWReferencesListNode.static.tagName = 'div'; + +ve.ce.MWReferencesListNode.static.primaryCommandName = 'referencesList'; + +/* Static Methods */ + +/** + * @inheritdoc + */ +ve.ce.MWReferencesListNode.static.getDescription = function ( model ) { + return model.getAttribute( 'refGroup' ); +}; + +/* Methods */ + +/** + * Handle setup events. + * + * @method + */ +ve.ce.MWReferencesListNode.prototype.onSetup = function () { + this.internalList = this.model.getDocument().getInternalList(); + this.listNode = this.internalList.getListNode(); + + this.internalList.connect( this, { update: 'onInternalListUpdate' } ); + this.listNode.connect( this, { update: 'onListNodeUpdate' } ); + + // Parent method + ve.ce.MWReferencesListNode.super.prototype.onSetup.call( this ); +}; + +/** + * Handle teardown events. + * + * @method + */ +ve.ce.MWReferencesListNode.prototype.onTeardown = function () { + this.internalList.disconnect( this, { update: 'onInternalListUpdate' } ); + this.listNode.disconnect( this, { update: 'onListNodeUpdate' } ); + + this.internalList = null; + this.listNode = null; + + // Parent method + ve.ce.MWReferencesListNode.super.prototype.onTeardown.call( this ); +}; + +/** + * Handle the updating of the InternalList object. + * + * This will occur after a document transaction. + * + * @method + * @param {string[]} groupsChanged A list of groups which have changed in this transaction + */ +ve.ce.MWReferencesListNode.prototype.onInternalListUpdate = function ( groupsChanged ) { + // Only update if this group has been changed + if ( groupsChanged.indexOf( this.model.getAttribute( 'listGroup' ) ) !== -1 ) { + this.updateDebounced(); + } +}; + +/** + * Rerender when the 'listGroup' attribute changes in the model. + * + * @param {string} key Attribute key + * @param {string} from Old value + * @param {string} to New value + */ +ve.ce.MWReferencesListNode.prototype.onAttributeChange = function ( key ) { + switch ( key ) { + case 'listGroup': + this.updateDebounced(); + break; + case 'isResponsive': + this.updateClasses(); + break; + } +}; + +/** + * Handle the updating of the InternalListNode. + * + * This will occur after changes to any InternalItemNode. + * + * @method + */ +ve.ce.MWReferencesListNode.prototype.onListNodeUpdate = function () { + // When the list node updates we're not sure which list group the item + // belonged to so we always update + // TODO: Only re-render the reference which has been edited + this.updateDebounced(); +}; + +/** + * Update the references list. + */ +ve.ce.MWReferencesListNode.prototype.update = function () { + var i, j, iLen, jLen, index, firstNode, key, keyedNodes, modelNode, viewNode, + $li, $refSpan, $link, + internalList = this.model.getDocument().internalList, + refGroup = this.model.getAttribute( 'refGroup' ), + listGroup = this.model.getAttribute( 'listGroup' ), + nodes = internalList.getNodeGroup( listGroup ); + + function updateGeneratedContent( viewNode, $li ) { + // HACK: PHP parser doesn't wrap single lines in a paragraph + if ( + viewNode.$element.children().length === 1 && + viewNode.$element.children( 'p' ).length === 1 + ) { + // unwrap inner + viewNode.$element.children().replaceWith( + viewNode.$element.children().contents() + ); + } + $li.append( + $( '' ) + .addClass( 'reference-text' ) + .append( viewNode.$element ) + ); + + // Since this is running after content generation has finished, it's + // safe to destroy the view. + viewNode.destroy(); + } + + this.$reflist.detach().empty(); + this.$refmsg.detach(); + + if ( refGroup !== '' ) { + this.$reflist.attr( 'data-mw-group', refGroup ); + } else { + this.$reflist.removeAttr( 'data-mw-group' ); + } + + if ( !nodes || !nodes.indexOrder.length ) { + if ( refGroup !== '' ) { + this.$refmsg.text( ve.msg( 'cite-ve-referenceslist-isempty', refGroup ) ); + } else { + this.$refmsg.text( ve.msg( 'cite-ve-referenceslist-isempty-default' ) ); + } + this.$element.append( this.$refmsg ); + } else { + for ( i = 0, iLen = nodes.indexOrder.length; i < iLen; i++ ) { + index = nodes.indexOrder[ i ]; + firstNode = nodes.firstNodes[ index ]; + + key = internalList.keys[ index ]; + keyedNodes = nodes.keyedNodes[ key ]; + keyedNodes = keyedNodes.filter( function ( node ) { + // Exclude placeholder references + if ( node.getAttribute( 'placeholder' ) ) { + return false; + } + // Exclude references defined inside the references list node + while ( ( node = node.parent ) && node !== null ) { + if ( node instanceof ve.dm.MWReferencesListNode ) { + return false; + } + } + return true; + } ); + + if ( !keyedNodes.length ) { + continue; + } + + $li = $( '

  1. ' ); + + if ( keyedNodes.length > 1 ) { + $refSpan = $( '' ); + for ( j = 0, jLen = keyedNodes.length; j < jLen; j++ ) { + $link = $( '' ).append( + $( '' ) + .text( ( j + 1 ) + ' ' ) + ); + if ( refGroup !== '' ) { + $link.attr( 'data-mw-group', refGroup ); + } + $refSpan.append( $link ); + } + $li.append( $refSpan ); + } else { + $link = $( '' ).append( + $( '' ).text( '↑ ' ) + ); + if ( refGroup !== '' ) { + $link.attr( 'data-mw-group', refGroup ); + } + $li.append( $link ); + } + + // Generate reference HTML from first item in key + modelNode = internalList.getItemNode( firstNode.getAttribute( 'listIndex' ) ); + if ( modelNode && modelNode.length ) { + viewNode = new ve.ce.InternalItemNode( modelNode ); + + ve.ce.GeneratedContentNode.static.awaitGeneratedContent( viewNode ) + .then( updateGeneratedContent.bind( this, viewNode, $li ) ); + + // Because this update runs a number of times when using the + // basic dialog, disconnect the model here rather than waiting + // for when it's destroyed after the generated content is + // finished. Failing to do this causes teardown errors with + // basic citations. + modelNode.disconnect( viewNode ); + } else { + $li.append( + $( '' ) + .addClass( 've-ce-mwReferencesListNode-muted' ) + .text( ve.msg( 'cite-ve-referenceslist-missingref' ) ) + ); + } + + this.$reflist.append( $li ); + } + this.updateClasses(); + this.$element.append( this.$reflist ); + } +}; + +/** + * Update ref list classes. + * + * Currently used to set responsive layout + */ +ve.ce.MWReferencesListNode.prototype.updateClasses = function () { + var isResponsive = this.model.getAttribute( 'isResponsive' ); + + this.$element + .toggleClass( 'mw-references-wrap', isResponsive ) + .toggleClass( 'mw-references-columns', isResponsive && this.$reflist.children().length > 10 ); +}; + +/* Registration */ + +ve.ce.nodeFactory.register( ve.ce.MWReferencesListNode );