/*! * VisualEditor DataModel MWReferenceModel class. * * @copyright 2011-2014 VisualEditor Team and others; see AUTHORS.txt * @license The MIT License (MIT); see LICENSE.txt */ /** * MediaWiki reference model. * * @class * @mixins OO.EventEmitter * * @constructor */ ve.dm.MWReferenceModel = function VeDmMWReferenceModel() { // Mixin constructors OO.EventEmitter.call( this ); // Properties this.listKey = ''; this.listGroup = ''; this.listIndex = null; this.group = ''; this.doc = null; this.deferDoc = null; }; /* Inheritance */ OO.mixinClass( ve.dm.MWReferenceModel, OO.EventEmitter ); /* Static Methods */ /** * Create a reference model from a reference internal item. * * @param {ve.dm.MWReferenceNode} node Reference node * @return {ve.dm.MWReferenceModel} Reference model */ ve.dm.MWReferenceModel.static.newFromReferenceNode = function ( node ) { var doc = node.getDocument(), internalList = doc.getInternalList(), attr = node.getAttributes(), ref = new ve.dm.MWReferenceModel(); ref.setListKey( attr.listKey ); ref.setListGroup( attr.listGroup ); ref.setListIndex( attr.listIndex ); ref.setGroup( attr.refGroup ); ref.deferDoc = function () { // cloneFromRange is very expensive, so lazy evaluate it return doc.cloneFromRange( internalList.getItemNode( attr.listIndex ).getRange() ); }; return ref; }; /* Methods */ /** * Find matching item in a surface. * * @param {ve.dm.Surface} surfaceModel Surface reference is in * @returns {ve.dm.InternalItemNode|null} Internal reference item, null if none exists */ ve.dm.MWReferenceModel.prototype.findInternalItem = function ( surfaceModel ) { if ( this.listIndex !== null ) { return surfaceModel.getDocument().getInternalList().getItemNode( this.listIndex ); } return null; }; /** * Insert reference internal item into a surface. * * If the internal item for this reference doesn't exist, use this method to create one. * The inserted reference is empty and auto-numbered. * * @param {ve.dm.Surface} surfaceModel Surface model of main document */ ve.dm.MWReferenceModel.prototype.insertInternalItem = function ( surfaceModel ) { // Create new internal item var item, doc = surfaceModel.getDocument(), internalList = doc.getInternalList(); // Fill in data this.setListKey( 'auto/' + internalList.getNextUniqueNumber() ); this.setListGroup( 'mwReference/' + this.group ); // Insert internal reference item into document item = internalList.getItemInsertion( this.listGroup, this.listKey, [] ); surfaceModel.change( item.transaction ); this.setListIndex( item.index ); // Inject reference document into internal reference item surfaceModel.change( ve.dm.Transaction.newFromDocumentInsertion( doc, internalList.getItemNode( item.index ).getRange().start, this.getDocument() ) ); }; /** * Update an internal reference item. * * An internal item for the reference will be created if no `ref` argument is given. * * @param {ve.dm.Surface} surfaceModel Surface model of main document */ ve.dm.MWReferenceModel.prototype.updateInternalItem = function ( surfaceModel ) { var i, len, txs, group, refNodes, keyIndex, itemNodeRange, doc = surfaceModel.getDocument(), internalList = doc.getInternalList(), listGroup = 'mwReference/' + this.group; // Group/key has changed if ( this.listGroup !== listGroup ) { // Get all reference nodes with the same group and key group = internalList.getNodeGroup( this.listGroup ); refNodes = group.keyedNodes[this.listKey] ? group.keyedNodes[this.listKey].slice() : [ group.firstNodes[this.listIndex] ]; // Check for name collision when moving items between groups keyIndex = internalList.getKeyIndex( this.listGroup, this.listKey ); if ( keyIndex !== undefined ) { // Resolve name collision by generating a new list key this.listKey = 'auto/' + internalList.getNextUniqueNumber(); } // Update the group name of all references nodes with the same group and key txs = []; for ( i = 0, len = refNodes.length; i < len; i++ ) { // HACK: Removing and re-inserting nodes to/from the internal list is done // because internal list doesn't yet support attribute changes refNodes[i].removeFromInternalList(); txs.push( ve.dm.Transaction.newFromAttributeChanges( doc, refNodes[i].getOuterRange().start, { 'refGroup': this.group, 'listGroup': listGroup } ) ); } surfaceModel.change( txs ); // HACK: Same as above, internal list issues for ( i = 0, len = refNodes.length; i < len; i++ ) { refNodes[i].addToInternalList(); } this.listGroup = listGroup; } // Update internal node content itemNodeRange = internalList.getItemNode( this.listIndex ).getRange(); surfaceModel.change( ve.dm.Transaction.newFromRemoval( doc, itemNodeRange, true ) ); surfaceModel.change( ve.dm.Transaction.newFromDocumentInsertion( doc, itemNodeRange.start, this.getDocument() ) ); }; /** * Insert reference at the end of a surface fragment. * * @param {ve.dm.SurfaceFragment} surfaceModel Surface fragment to insert at */ ve.dm.MWReferenceModel.prototype.insertReferenceNode = function ( surfaceFragment ) { surfaceFragment .insertContent( [ { 'type': 'mwReference', 'attributes': { 'listKey': this.listKey, 'listGroup': this.listGroup, 'listIndex': this.listIndex, 'refGroup': this.group } }, { 'type': '/mwReference' } ] ); }; /** * Get key of reference in list. * * @returns {string} Reference list key */ ve.dm.MWReferenceModel.prototype.getListKey = function () { return this.listKey; }; /** * Get name of group reference list is in. * * @returns {string} Reference list group */ ve.dm.MWReferenceModel.prototype.getListGroup = function () { return this.listGroup; }; /** * Get index of reference in list. * * @returns {string} Reference list group */ ve.dm.MWReferenceModel.prototype.getListIndex = function () { return this.listIndex; }; /** * Get name of group reference is in. * * @returns {string} Reference group */ ve.dm.MWReferenceModel.prototype.getGroup = function () { return this.group; }; /** * Get reference document. * * Auto-generates a blank document if no document exists. * * @returns {ve.dm.Document} Reference document */ ve.dm.MWReferenceModel.prototype.getDocument = function () { if ( !this.doc ) { if ( this.deferDoc ) { this.doc = this.deferDoc(); } else { this.doc = new ve.dm.Document( [ { 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } }, { 'type': '/paragraph' }, { 'type': 'internalList' }, { 'type': '/internalList' } ] ); } } return this.doc; }; /** * Set key of reference in list. * * @param {string} Reference list key */ ve.dm.MWReferenceModel.prototype.setListKey = function ( listKey ) { this.listKey = listKey; }; /** * Set name of group reference list is in. * * @param {string} Reference list group */ ve.dm.MWReferenceModel.prototype.setListGroup = function ( listGroup ) { this.listGroup = listGroup; }; /** * Set index of reference in list. * * @param {string} Reference list group */ ve.dm.MWReferenceModel.prototype.setListIndex = function ( listIndex ) { this.listIndex = listIndex; }; /** * Set name of group reference is in. * * @param {string} group Reference group */ ve.dm.MWReferenceModel.prototype.setGroup = function ( group ) { this.group = group; }; /** * Set reference document. * * @param {ve.dm.Document} Reference document */ ve.dm.MWReferenceModel.prototype.setDocument = function ( doc ) { this.doc = doc; };