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
217 lines
6.1 KiB
JavaScript
217 lines
6.1 KiB
JavaScript
/**
|
|
* VisualEditor user interface FormatDropdownTool class.
|
|
*
|
|
* @copyright 2011-2012 VisualEditor Team and others; see AUTHORS.txt
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
*/
|
|
|
|
/**
|
|
* Creates an ve.ui.FormatDropdownTool object.
|
|
*
|
|
* @class
|
|
* @constructor
|
|
* @extends {ve.ui.DropdownTool}
|
|
* @param {ve.ui.Toolbar} toolbar
|
|
* @param {String} name
|
|
* @param title
|
|
*/
|
|
ve.ui.FormatDropdownTool = function VeUiFormatDropdownTool( toolbar, name, title ) {
|
|
// Parent constructor
|
|
ve.ui.DropdownTool.call( this, toolbar, name, title, [
|
|
{
|
|
'name': 'paragraph',
|
|
'label': ve.msg( 'visualeditor-formatdropdown-format-paragraph' ),
|
|
'type' : 'paragraph'
|
|
},
|
|
{
|
|
'name': 'heading-1',
|
|
'label': ve.msg( 'visualeditor-formatdropdown-format-heading1' ),
|
|
'type' : 'heading',
|
|
'attributes': { 'level': 1 }
|
|
},
|
|
{
|
|
'name': 'heading-2',
|
|
'label': ve.msg( 'visualeditor-formatdropdown-format-heading2' ),
|
|
'type' : 'heading',
|
|
'attributes': { 'level': 2 }
|
|
},
|
|
{
|
|
'name': 'heading-3',
|
|
'label': ve.msg( 'visualeditor-formatdropdown-format-heading3' ),
|
|
'type' : 'heading',
|
|
'attributes': { 'level': 3 }
|
|
},
|
|
{
|
|
'name': 'heading-4',
|
|
'label': ve.msg( 'visualeditor-formatdropdown-format-heading4' ),
|
|
'type' : 'heading',
|
|
'attributes': { 'level': 4 }
|
|
},
|
|
{
|
|
'name': 'heading-5',
|
|
'label': ve.msg( 'visualeditor-formatdropdown-format-heading5' ),
|
|
'type' : 'heading',
|
|
'attributes': { 'level': 5 }
|
|
},
|
|
{
|
|
'name': 'heading-6',
|
|
'label': ve.msg( 'visualeditor-formatdropdown-format-heading6' ),
|
|
'type' : 'heading',
|
|
'attributes': { 'level': 6 }
|
|
},
|
|
{
|
|
'name': 'preformatted',
|
|
'label': ve.msg( 'visualeditor-formatdropdown-format-preformatted' ),
|
|
'type' : 'preformatted'
|
|
}
|
|
] );
|
|
};
|
|
|
|
/* Inheritance */
|
|
|
|
ve.inheritClass( ve.ui.FormatDropdownTool, ve.ui.DropdownTool );
|
|
|
|
/* Methods */
|
|
|
|
ve.ui.FormatDropdownTool.splitAndUnwrap = function ( model, list, firstItem, lastItem, selection ) {
|
|
var doc = model.getDocument(),
|
|
start = firstItem.getOuterRange().start,
|
|
end = lastItem.getOuterRange().end,
|
|
tx;
|
|
// First split the list before, if needed
|
|
if ( list.indexOf( firstItem ) > 0 ) {
|
|
tx = ve.dm.Transaction.newFromInsertion(
|
|
doc, start, [{ 'type': '/list' }, list.getClonedElement()]
|
|
);
|
|
start += 2;
|
|
end += 2;
|
|
selection = tx.translateRange( selection );
|
|
model.change( tx, selection );
|
|
}
|
|
// Split the list after, if needed
|
|
if ( list.indexOf( lastItem ) < list.getChildren().length - 1 ) {
|
|
tx = ve.dm.Transaction.newFromInsertion(
|
|
doc, end, [{ 'type': '/list' }, list.getClonedElement()]
|
|
);
|
|
selection = tx.translateRange( selection );
|
|
model.change( tx, selection );
|
|
}
|
|
// Unwrap the list
|
|
tx = ve.dm.Transaction.newFromWrap( doc, new ve.Range( start, end ),
|
|
[{ 'type': 'list' }], [], [{ 'type': 'listItem' }], []
|
|
);
|
|
selection = tx.translateRange( selection );
|
|
model.change( tx, selection );
|
|
return selection;
|
|
};
|
|
|
|
ve.ui.FormatDropdownTool.prototype.onSelect = function ( item ) {
|
|
var selected, prevList, firstInList, lastInList, i, contentBranch, listItem, txs,
|
|
surfaceView = this.toolbar.getSurfaceView(),
|
|
model = surfaceView.getModel(),
|
|
selection = model.getSelection(),
|
|
doc = model.getDocument();
|
|
if ( item.type !== 'paragraph' ) {
|
|
// We can't have headings or pre's in a list, so if we're trying to convert
|
|
// things that are in lists to a heading or a pre, split the list
|
|
selected = doc.selectNodes( selection, 'leaves' );
|
|
for ( i = 0; i < selected.length; i++ ) {
|
|
contentBranch = selected[i].node.isContent() ?
|
|
selected[i].node.getParent() :
|
|
selected[i].node;
|
|
// Check if it's in a list
|
|
listItem = contentBranch;
|
|
while ( listItem && listItem.getType() !== 'listItem' ) {
|
|
listItem = listItem.getParent();
|
|
}
|
|
if ( !listItem || listItem.getParent() !== prevList ) {
|
|
// Not in a list or in a different list
|
|
if ( prevList ) {
|
|
// Split and unwrap prevList
|
|
selection = ve.ui.FormatDropdownTool.splitAndUnwrap(
|
|
model, prevList, firstInList, lastInList, selection
|
|
);
|
|
}
|
|
if ( listItem ) {
|
|
prevList = listItem.getParent();
|
|
firstInList = listItem;
|
|
lastInList = firstInList;
|
|
}
|
|
} else {
|
|
// This node is in the current list
|
|
lastInList = listItem;
|
|
}
|
|
}
|
|
if ( prevList ) {
|
|
// Split and unwrap prevList
|
|
selection = ve.ui.FormatDropdownTool.splitAndUnwrap(
|
|
model, prevList, firstInList, lastInList, selection
|
|
);
|
|
}
|
|
}
|
|
txs = ve.dm.Transaction.newFromContentBranchConversion(
|
|
doc,
|
|
selection,
|
|
item.type,
|
|
item.attributes
|
|
);
|
|
model.change( txs, selection );
|
|
surfaceView.showSelection( selection );
|
|
};
|
|
|
|
ve.ui.FormatDropdownTool.prototype.getMatchingMenuItems = function ( nodes ) {
|
|
var i, j, nodeType, nodeAttributes, item, key,
|
|
matches = [],
|
|
items = this.menuView.getItems();
|
|
for ( i = 0; i < nodes.length; i++ ) {
|
|
nodeType = nodes[i].getType();
|
|
nodeAttributes = nodes[i].getAttributes();
|
|
// Outer loop continue point
|
|
itemLoop:
|
|
for ( j = 0; j < items.length; j++ ) {
|
|
item = items[j];
|
|
if ( item.type === nodeType ) {
|
|
if ( item.attributes && nodeAttributes ) {
|
|
// Compare attributes
|
|
for ( key in item.attributes ) {
|
|
if (
|
|
// Node must have all the required attributes
|
|
!( key in nodeAttributes ) ||
|
|
// Use weak comparison because numbers sometimes come through as strings
|
|
item.attributes[key] !== nodeAttributes[key]
|
|
) {
|
|
// Skip to the next menu item
|
|
continue itemLoop;
|
|
}
|
|
}
|
|
} else if ( item.attributes && !nodeAttributes ) {
|
|
// Node is required to have certain attributes but doesn't
|
|
// have any, so it doesn't match
|
|
continue itemLoop;
|
|
}
|
|
matches.push( item );
|
|
}
|
|
}
|
|
}
|
|
return matches;
|
|
};
|
|
|
|
ve.ui.FormatDropdownTool.prototype.onUpdateState = function ( annotations, nodes ) {
|
|
if ( nodes.length ) {
|
|
var items = this.getMatchingMenuItems( nodes );
|
|
if ( items.length === 1 ) {
|
|
this.$labelText.text( items[0].label );
|
|
} else {
|
|
this.$labelText.html( ' ' );
|
|
}
|
|
}
|
|
};
|
|
|
|
/* Registration */
|
|
|
|
ve.ui.Tool.tools.format = {
|
|
'constructor': ve.ui.FormatDropdownTool,
|
|
'name': 'format',
|
|
'title': ve.msg( 'visualeditor-formatdropdown-title' )
|
|
};
|