mediawiki-extensions-Cite/modules/ve-cite/ve.dm.MWReferenceModel.js
Ed Sanders 18f616b9b8 VisualEditor: Move code for Cite into this repo
This code has been developed over three years now in the repo of MediaWiki's
integration of VisualEditor. It has grown and developed significantly during
that time, but now is pretty stable. A number of hacks inside the MediaWiki-
VisualEditor code base have been used to prevent this code from being loaded
on wikis where the Cite extension is not deployed, but this state of affairs
is and always was meant to be temporary.

This code is under the MIT licence which is a tad messy, but not impossible.
It's clearly labelled as such. The list of authors has been updated to take
into account the influx of new functionality.

Bug: T41621
Bug: T104928
Change-Id: I39936ed83d5a60471a0a75da753f498e80aef234
2016-02-04 08:41:54 -08:00

298 lines
7.7 KiB
JavaScript

/*!
* VisualEditor DataModel MWReferenceModel class.
*
* @copyright 2011-2016 Cite VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* MediaWiki reference model.
*
* @class
* @mixins OO.EventEmitter
*
* @constructor
* @param {ve.dm.Document} parentDoc Document that contains or will contain the reference
*/
ve.dm.MWReferenceModel = function VeDmMWReferenceModel( parentDoc ) {
// Mixin constructors
OO.EventEmitter.call( this );
// Properties
this.listKey = '';
this.listGroup = '';
this.listIndex = null;
this.group = '';
this.doc = null;
this.parentDoc = parentDoc;
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( doc );
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
* @return {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++ ) {
txs.push( ve.dm.Transaction.newFromAttributeChanges(
doc,
refNodes[ i ].getOuterRange().start,
{ refGroup: this.group, listGroup: listGroup }
) );
}
surfaceModel.change( txs );
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} surfaceFragment Surface fragment to insert at
* @param {boolean} [placeholder] Reference is a placeholder for staging purposes
*/
ve.dm.MWReferenceModel.prototype.insertReferenceNode = function ( surfaceFragment, placeholder ) {
var attributes = {
listKey: this.listKey,
listGroup: this.listGroup,
listIndex: this.listIndex,
refGroup: this.group
};
if ( placeholder ) {
attributes.placeholder = true;
}
surfaceFragment
.insertContent( [
{
type: 'mwReference',
attributes: attributes
},
{ type: '/mwReference' }
] );
};
/**
* Get the key of a reference in the references list.
*
* @return {string} Reference's list key
*/
ve.dm.MWReferenceModel.prototype.getListKey = function () {
return this.listKey;
};
/**
* Get the name of the group a references list is in.
*
* @return {string} References list's group
*/
ve.dm.MWReferenceModel.prototype.getListGroup = function () {
return this.listGroup;
};
/**
* Get the index of reference in the references list.
*
* @return {string} Reference's index
*/
ve.dm.MWReferenceModel.prototype.getListIndex = function () {
return this.listIndex;
};
/**
* Get the name of the group a reference is in.
*
* @return {string} Reference's group
*/
ve.dm.MWReferenceModel.prototype.getGroup = function () {
return this.group;
};
/**
* Get reference document.
*
* Auto-generates a blank document if no document exists.
*
* @return {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' }
],
// htmlDocument
this.parentDoc.getHtmlDocument(),
// parentDocument
null,
// internalList
null,
// innerWhitespace
null,
// lang
this.parentDoc.getLang(),
// dir
this.parentDoc.getDir()
);
}
}
return this.doc;
};
/**
* Set key of reference in list.
*
* @param {string} listKey Reference's list key
*/
ve.dm.MWReferenceModel.prototype.setListKey = function ( listKey ) {
this.listKey = listKey;
};
/**
* Set name of the group a references list is in.
*
* @param {string} listGroup References list's group
*/
ve.dm.MWReferenceModel.prototype.setListGroup = function ( listGroup ) {
this.listGroup = listGroup;
};
/**
* Set the index of reference in list.
*
* @param {string} listIndex Reference's list index
*/
ve.dm.MWReferenceModel.prototype.setListIndex = function ( listIndex ) {
this.listIndex = listIndex;
};
/**
* Set the name of the group a reference is in.
*
* @param {string} group Reference's group
*/
ve.dm.MWReferenceModel.prototype.setGroup = function ( group ) {
this.group = group;
};
/**
* Set the reference document.
*
* @param {ve.dm.Document} doc Reference document
*/
ve.dm.MWReferenceModel.prototype.setDocument = function ( doc ) {
this.doc = doc;
};