mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-11-25 14:56:20 +00:00
96d97c2aa8
Rather than each tool requesting annotations, and nodes pertaining to selection, Emitted event supplies annotations and nodes to each tool's update method. Using select vs. of traverseLeafNodes for code optimization. Better documentation for updateTools() Removed unneeded code. Change-Id: I7c0baa1cc0f7fb731d6e28b175a76e931e9e2961
221 lines
6.2 KiB
JavaScript
221 lines
6.2 KiB
JavaScript
/**
|
|
* VisualEditor user interface ListButtonTool class.
|
|
*
|
|
* @copyright 2011-2012 VisualEditor Team and others; see AUTHORS.txt
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
*/
|
|
|
|
/**
|
|
* Creates an ve.ui.ListButtonTool object.
|
|
*
|
|
* @class
|
|
* @constructor
|
|
* @extends {ve.ui.ButtonTool}
|
|
* @param {ve.ui.Toolbar} toolbar
|
|
* @param {String} name
|
|
* @param title
|
|
* @param data
|
|
*/
|
|
ve.ui.ListButtonTool = function VeUiListButtonTool( toolbar, name, title, data ) {
|
|
// Parent constructor
|
|
ve.ui.ButtonTool.call( this, toolbar, name, title );
|
|
|
|
// Properties
|
|
this.data = data;
|
|
this.nodes = [];
|
|
};
|
|
|
|
/* Inheritance */
|
|
|
|
ve.inheritClass( ve.ui.ListButtonTool, ve.ui.ButtonTool );
|
|
|
|
/* Methods */
|
|
|
|
ve.ui.ListButtonTool.prototype.list = function ( nodes, style ) {
|
|
var surfaceModel = this.toolbar.getSurfaceView().getModel(),
|
|
documentModel = surfaceModel.getDocument(),
|
|
selection = surfaceModel.getSelection(),
|
|
groups = documentModel.getCoveredSiblingGroups( selection ),
|
|
previousList,
|
|
groupRange,
|
|
group,
|
|
tx, i;
|
|
|
|
for ( i = 0; i < groups.length; i++ ) {
|
|
group = groups[i];
|
|
if ( group.grandparent && group.grandparent.getType() === 'list' ) {
|
|
if ( group.grandparent !== previousList ) {
|
|
// Change the list style
|
|
surfaceModel.change(
|
|
ve.dm.Transaction.newFromAttributeChange(
|
|
documentModel, group.grandparent.getOffset(), 'style', style
|
|
),
|
|
selection
|
|
);
|
|
// Skip this one next time
|
|
previousList = group.grandparent;
|
|
}
|
|
} else {
|
|
// Get a range that covers the whole group
|
|
groupRange = new ve.Range(
|
|
group.nodes[0].getOuterRange().start,
|
|
group.nodes[group.nodes.length - 1].getOuterRange().end
|
|
);
|
|
// Convert everything to paragraphs first
|
|
surfaceModel.change(
|
|
ve.dm.Transaction.newFromContentBranchConversion(
|
|
documentModel, groupRange, 'paragraph'
|
|
),
|
|
selection
|
|
);
|
|
// Wrap everything in a list and each content branch in a listItem
|
|
tx = ve.dm.Transaction.newFromWrap(
|
|
documentModel,
|
|
groupRange,
|
|
[],
|
|
[{ 'type': 'list', 'attributes': { 'style': style } }],
|
|
[],
|
|
[{ 'type': 'listItem' }]
|
|
);
|
|
surfaceModel.change( tx, tx.translateRange( selection ) );
|
|
}
|
|
}
|
|
};
|
|
|
|
ve.ui.ListButtonTool.prototype.unlist = function () {
|
|
/**
|
|
* Recursively prepare to unwrap all lists in a given range.
|
|
*
|
|
* This function will find all lists covered wholly or partially by the given range, as well
|
|
* as all lists inside these lists, and return their inner ranges. This means that all sublists
|
|
* will be found even if range doesn't cover them.
|
|
*
|
|
* To actually unwrap the list, feed the returned ranges to ve.dm.Transaction.newFromWrap(),
|
|
* in order.
|
|
*
|
|
* @param {ve.dm.Document} documentModel
|
|
* @param {ve.Range} range
|
|
* @returns {ve.Range[]} Array of inner ranges of lists
|
|
*/
|
|
function getUnlistRanges( documentModel, range ) {
|
|
var groups = documentModel.getCoveredSiblingGroups( range ),
|
|
// Collect ranges in an object for deduplication
|
|
unlistRanges = {},
|
|
i, j, k, group, previousList, list, listItem,
|
|
subList, endOffset = 0;
|
|
|
|
for ( i = 0; i < groups.length; i++ ) {
|
|
group = groups[i];
|
|
list = group.grandparent;
|
|
if ( list && list.getType() === 'list' && list !== previousList ) {
|
|
// Unwrap the parent list
|
|
range = list.getRange();
|
|
if ( range.end > endOffset ) {
|
|
unlistRanges[range.start + '-' + range.end] = range;
|
|
endOffset = range.end;
|
|
}
|
|
// Skip this list next time
|
|
previousList = list;
|
|
// Recursively unwrap any sublists of the list
|
|
for ( j = 0; j < list.children.length; j++ ) {
|
|
listItem = list.children[j];
|
|
if ( listItem.getType() === 'listItem' ) {
|
|
for ( k = 0; k < listItem.children.length; k++ ) {
|
|
subList = listItem.children[k];
|
|
if ( subList.getType() === 'list' ) {
|
|
// Recurse
|
|
unlistRanges = ve.extendObject( unlistRanges, getUnlistRanges(
|
|
documentModel, subList.getRange()
|
|
) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return unlistRanges;
|
|
}
|
|
|
|
var surfaceModel = this.toolbar.getSurfaceView().getModel(),
|
|
documentModel = surfaceModel.getDocument(),
|
|
selection = surfaceModel.getSelection(),
|
|
unlistRangesObj = getUnlistRanges( documentModel, selection ),
|
|
unlistRangesArr = [],
|
|
i, j, tx;
|
|
|
|
for ( i in unlistRangesObj ) {
|
|
unlistRangesArr.push( unlistRangesObj[i] );
|
|
}
|
|
|
|
for ( i = 0; i < unlistRangesArr.length; i++ ) {
|
|
// Unwrap the range given by unlistRanges[i]
|
|
tx = ve.dm.Transaction.newFromWrap(
|
|
documentModel,
|
|
unlistRangesArr[i],
|
|
[ { 'type': 'list' } ],
|
|
[],
|
|
[ { 'type': 'listItem' } ],
|
|
[]
|
|
);
|
|
selection = tx.translateRange( selection );
|
|
surfaceModel.change( tx );
|
|
// Translate all the remaining ranges for this transaction
|
|
// TODO ideally we'd have a way to merge all these transactions into one and execute that instead
|
|
for ( j = i + 1; j < unlistRangesArr.length; j++ ) {
|
|
unlistRangesArr[j] = tx.translateRange( unlistRangesArr[j] );
|
|
}
|
|
}
|
|
// Update the selection
|
|
surfaceModel.change( null, selection );
|
|
};
|
|
|
|
ve.ui.ListButtonTool.prototype.onClick = function () {
|
|
this.toolbar.surfaceView.model.breakpoint();
|
|
if ( !this.$.hasClass( 've-ui-toolbarButtonTool-down' ) ) {
|
|
this.list( this.nodes, this.name );
|
|
} else {
|
|
this.unlist( this.nodes );
|
|
}
|
|
this.toolbar.surfaceView.model.breakpoint();
|
|
};
|
|
|
|
ve.ui.ListButtonTool.prototype.onUpdateState = function ( annotations, nodes ) {
|
|
function areListItemsOfStyle( nodes, style ) {
|
|
var i, listNode;
|
|
for ( i = 0; i < nodes.length; i++ ) {
|
|
listNode = nodes[i];
|
|
// Get the list node
|
|
while ( listNode && listNode.getType() !== 'list' ) {
|
|
listNode = listNode.getParent();
|
|
if ( listNode === null ) {
|
|
return false;
|
|
}
|
|
}
|
|
if ( listNode.getAttribute( 'style' ) !== style ) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if ( areListItemsOfStyle( nodes, this.name ) ) {
|
|
this.$.addClass( 've-ui-toolbarButtonTool-down' );
|
|
} else {
|
|
this.$.removeClass( 've-ui-toolbarButtonTool-down' );
|
|
}
|
|
};
|
|
|
|
/* Registration */
|
|
|
|
ve.ui.Tool.tools.number = {
|
|
'constructor': ve.ui.ListButtonTool,
|
|
'name': 'number',
|
|
'title': ve.msg( 'visualeditor-listbutton-number-tooltip' )
|
|
};
|
|
|
|
ve.ui.Tool.tools.bullet = {
|
|
'constructor': ve.ui.ListButtonTool,
|
|
'name': 'bullet',
|
|
'title': ve.msg( 'visualeditor-listbutton-bullet-tooltip' )
|
|
};
|