mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Cite
synced 2024-11-28 08:50:07 +00:00
18f616b9b8
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
298 lines
7.7 KiB
JavaScript
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;
|
|
};
|