mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-12-03 02:16:51 +00:00
88f6089952
'''Kranitor commits''' are commits by Krinkle with his janitor hat on. Must never contain functional changes mixed with miscellaneous changes. .gitignore: * Add .DS_Store to the ignore list so that browsing the directories on Mac OS X, will not add these files to the list of untracked files. * Fix missing newline at end of file .jshintrc * raises -> throws * +module (QUnit.module) * remove 'Node' (as of node-jshint 1.7.2 this is now part of 'browser:true', as it should be) Authors: * Adding myself MWExtension/VisualEditor.php * Fix default value of wgVisualEditorParsoidURL to not point to the experimental instance in WMF Labs. Issues: * ve.ce.TextNode: - Fix TODO: Don't perform a useless clone of an already-jQuerified object. - Use .html() to set html content instead of encapsulating between two strings. This is slightly faster but more importantly safer, and prevents situations where the resulting jQuery collection actually contains 2 elements instead of 1, thus messing up what .contents() is iterating over. * ve.ce.Document.test.js - Fix: ReferenceError: assert is not defined * ve.dm.Document.test.js - Fix: ReferenceError: assert is not defined * ve.dm.Transaction.test.js - Fix: ReferenceError: assert is not defined * ve.dm.TransactionProcessor.test.js - Fix: ReferenceError: assert is not defined * ext.visualEditor.viewPageTarget - Missing dependency on 'mediawiki.Title' Code conventions / Misc cleanup * Various JSHint warnings. * Whitespace * jQuery(): Use '<tag>' for element creation, use '<valid><xml/></valid>' for parsing * Use the default operator instead of ternary when the condition and first value are the same. x = foo ? foo : bar; -> x = foo || bar; Because contrary to some programming language (PHP...), in JS the default operator does not enforce a boolean result but returns the original value, hence it being called the 'default' operator, as opposed to the 'or' operator. * No need to call addClass() twice, it takes a space-separated list (jQuery splits by space and adds if needed) * Use .on( event[, selector], fn ) instead of the deprecated routers to it such as .bind(), .delegate() and .live(). All these three are now built-in and fully compatible with .on() * Add 'XXX:' comments for suspicious code that I don't want to change as part of a clean up commit. * Remove unused variables (several var x = this; where x was not used anywhere, possibly from boilerplate copy/paste) * Follows-up Trevor's commit that converts test suites to the new QUnit format. Also removed the globals since we no longer use those any more. Change-Id: I7e37c9bff812e371c7f65a6fd85d9e2af3e0a22f
226 lines
6.3 KiB
JavaScript
226 lines
6.3 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
|
|
*/
|
|
ve.ui.ListButtonTool = function ( toolbar, name, title, data ) {
|
|
// Inheritance
|
|
ve.ui.ButtonTool.call( this, toolbar, name, title );
|
|
|
|
// Properties
|
|
this.data = data;
|
|
this.nodes = [];
|
|
};
|
|
|
|
/* 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 ( node ) {
|
|
/**
|
|
* 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( 'es-toolbarButtonTool-down' ) ) {
|
|
this.list( this.nodes, this.name );
|
|
} else {
|
|
this.unlist( this.nodes );
|
|
}
|
|
this.toolbar.surfaceView.model.breakpoint();
|
|
};
|
|
|
|
ve.ui.ListButtonTool.prototype.updateState = function ( annotations, nodes ) {
|
|
var surfaceView = this.toolbar.getSurfaceView(),
|
|
surfaceModel = surfaceView.getModel(),
|
|
doc = surfaceView.getDocument(),
|
|
selection = surfaceModel.getSelection(),
|
|
leaves = doc.selectNodes( selection, 'leaves' );
|
|
|
|
function areListItemsOfStyle( leaves, style ){
|
|
var i, listNode;
|
|
|
|
for ( i = 0; i < leaves.length; i++ ) {
|
|
listNode = leaves[i].node;
|
|
// Get the list node
|
|
while ( listNode && listNode.getType() !== 'list' ) {
|
|
listNode = listNode.getParent();
|
|
if ( listNode === null ) {
|
|
return false;
|
|
}
|
|
}
|
|
if ( listNode.getModel().getAttribute('style') !== style ) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if ( areListItemsOfStyle( leaves, this.name ) ) {
|
|
this.$.addClass( 'es-toolbarButtonTool-down' );
|
|
} else {
|
|
this.$.removeClass( 'es-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' )
|
|
};
|
|
|
|
/* Inheritance */
|
|
|
|
ve.extendClass( ve.ui.ListButtonTool, ve.ui.ButtonTool );
|