mediawiki-extensions-Visual.../modules/ve/ui/tools/ve.ui.ListButtonTool.js
Rob Moen 96d97c2aa8 Optimize UI tool state updates.
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
2012-09-19 11:16:10 -07:00

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' )
};