mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-11-24 22:35:41 +00:00
Node annotation blacklists
Allow nodes to specify a blacklist of annotations which can't be applied to it. Bug: 53151 Change-Id: Iac7649f4b38d8bfa94c64f734634afd45a5afc53
This commit is contained in:
parent
ede7dad318
commit
f001d306ad
|
@ -158,6 +158,15 @@ ve.dm.Node.static.parentNodeTypes = null;
|
|||
*/
|
||||
ve.dm.Node.static.suggestedParentNodeTypes = null;
|
||||
|
||||
/**
|
||||
* Array of annotation types which can't be applied to this node
|
||||
*
|
||||
* @static
|
||||
* @property {string[]} static.blacklistedAnnotationTypes
|
||||
* @inheritable
|
||||
*/
|
||||
ve.dm.Node.static.blacklistedAnnotationTypes = [];
|
||||
|
||||
/**
|
||||
* Default attributes to set for newly created linear model elements. These defaults will be used
|
||||
* when creating a new element in ve.dm.NodeFactory#getDataElement when there is no DOM node or
|
||||
|
|
|
@ -154,6 +154,31 @@ ve.dm.NodeFactory.prototype.canNodeContainContent = function ( type ) {
|
|||
throw new Error( 'Unknown node type: ' + type );
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Check if node can take annotations of a specific type.
|
||||
*
|
||||
* @method
|
||||
* @param {string} type Node type
|
||||
* @param {ve.dm.Annotation} annotation Annotation to test
|
||||
* @returns {boolean} Node can take annotations of this type
|
||||
* @throws {Error} Unknown node type
|
||||
*/
|
||||
ve.dm.NodeFactory.prototype.canNodeTakeAnnotationType = function ( type, annotation ) {
|
||||
if ( !( type in this.registry ) ) {
|
||||
throw new Error( 'Unknown node type: ' + type );
|
||||
}
|
||||
var i, len,
|
||||
blacklist = this.registry[type].static.blacklistedAnnotationTypes;
|
||||
|
||||
for ( i = 0, len = blacklist.length; i < len; i++ ) {
|
||||
if ( annotation instanceof ve.dm.annotationFactory.create( blacklist[i] ).constructor ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if a node is content.
|
||||
*
|
||||
|
|
|
@ -228,7 +228,7 @@ ve.dm.Transaction.newFromAttributeChanges = function ( doc, offset, attr ) {
|
|||
* @returns {ve.dm.Transaction} Transaction that annotates content
|
||||
*/
|
||||
ve.dm.Transaction.newFromAnnotation = function ( doc, range, method, annotation ) {
|
||||
var covered, type,
|
||||
var covered, type, annotatable,
|
||||
tx = new ve.dm.Transaction(),
|
||||
data = doc.data,
|
||||
i = range.start,
|
||||
|
@ -237,9 +237,25 @@ ve.dm.Transaction.newFromAnnotation = function ( doc, range, method, annotation
|
|||
insideContentNode = false;
|
||||
// Iterate over all data in range, annotating where appropriate
|
||||
while ( i < range.end ) {
|
||||
type = data.getType( i );
|
||||
if ( data.isElementData( i ) ) {
|
||||
type = data.getType( i );
|
||||
if ( ve.dm.nodeFactory.isNodeContent( type ) ) {
|
||||
if ( method === 'set' && !ve.dm.nodeFactory.canNodeTakeAnnotationType( type, annotation ) ) {
|
||||
// Blacklisted annotations can't be set
|
||||
annotatable = false;
|
||||
} else {
|
||||
annotatable = true;
|
||||
}
|
||||
} else {
|
||||
// Structural nodes are never annotatable
|
||||
annotatable = false;
|
||||
}
|
||||
} else {
|
||||
// Text is always annotatable
|
||||
annotatable = true;
|
||||
}
|
||||
if (
|
||||
( data.isElementData( i ) && !ve.dm.nodeFactory.isNodeContent( type ) ) ||
|
||||
!annotatable ||
|
||||
( insideContentNode && !data.isCloseElementData( i ) )
|
||||
) {
|
||||
// Structural element opening or closing, or entering a content node
|
||||
|
|
|
@ -764,6 +764,7 @@ QUnit.test( 'newFromAttributeChanges', function ( assert ) {
|
|||
QUnit.test( 'newFromAnnotation', function ( assert ) {
|
||||
var bold = ve.dm.example.createAnnotation( ve.dm.example.bold ),
|
||||
doc = ve.dm.example.createExampleDocument(),
|
||||
annotationDoc = ve.dm.example.createExampleDocument( 'annotationData' ),
|
||||
cases = {
|
||||
'over plain text': {
|
||||
'args': [doc, new ve.Range( 1, 2 ), 'set', bold],
|
||||
|
@ -858,8 +859,63 @@ QUnit.test( 'newFromAnnotation', function ( assert ) {
|
|||
},
|
||||
{ 'type': 'retain', 'length': 52 }
|
||||
]
|
||||
},
|
||||
'over content and content element (image)': {
|
||||
'args': [doc, new ve.Range( 38, 42 ), 'set', bold],
|
||||
'ops': [
|
||||
{ 'type': 'retain', 'length': 38 },
|
||||
{
|
||||
'type': 'annotate',
|
||||
'method': 'set',
|
||||
'bias': 'start',
|
||||
'annotation': bold
|
||||
},
|
||||
{ 'type': 'retain', 'length': 4 },
|
||||
{
|
||||
'type': 'annotate',
|
||||
'method': 'set',
|
||||
'bias': 'stop',
|
||||
'annotation': bold
|
||||
},
|
||||
{ 'type': 'retain', 'length': 21 }
|
||||
]
|
||||
},
|
||||
'over content and unannotatable content element (unboldable node)': {
|
||||
'args': [annotationDoc, new ve.Range( 1, 9 ), 'set', bold],
|
||||
'ops': [
|
||||
{ 'type': 'retain', 'length': 1 },
|
||||
{
|
||||
'type': 'annotate',
|
||||
'method': 'set',
|
||||
'bias': 'start',
|
||||
'annotation': bold
|
||||
},
|
||||
{ 'type': 'retain', 'length': 3 },
|
||||
{
|
||||
'type': 'annotate',
|
||||
'method': 'set',
|
||||
'bias': 'stop',
|
||||
'annotation': bold
|
||||
},
|
||||
{ 'type': 'retain', 'length': 2 },
|
||||
{
|
||||
'type': 'annotate',
|
||||
'method': 'set',
|
||||
'bias': 'start',
|
||||
'annotation': bold
|
||||
},
|
||||
{ 'type': 'retain', 'length': 3 },
|
||||
{
|
||||
'type': 'annotate',
|
||||
'method': 'set',
|
||||
'bias': 'stop',
|
||||
'annotation': bold
|
||||
},
|
||||
{ 'type': 'retain', 'length': 3 }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
QUnit.expect( ve.getObjectKeys( cases ).length );
|
||||
runConstructorTests( assert, ve.dm.Transaction.newFromAnnotation, cases );
|
||||
} );
|
||||
|
|
|
@ -2839,6 +2839,32 @@ ve.dm.example.isolationData = [
|
|||
// 246
|
||||
];
|
||||
|
||||
ve.dm.example.UnboldableNode = function ( lenght, element ) {
|
||||
// Parent constructor
|
||||
ve.dm.LeafNode.call( this, 0, element );
|
||||
};
|
||||
ve.inheritClass( ve.dm.example.UnboldableNode, ve.dm.LeafNode );
|
||||
ve.dm.example.UnboldableNode.static.name = 'exampleUnboldable';
|
||||
ve.dm.example.UnboldableNode.static.isContent = true;
|
||||
ve.dm.example.UnboldableNode.static.blacklistedAnnotationTypes = [ 'textStyle/bold' ];
|
||||
ve.dm.example.UnboldableNode.static.matchTagNames = [];
|
||||
ve.dm.modelRegistry.register( ve.dm.example.UnboldableNode );
|
||||
|
||||
ve.dm.example.annotationData = [
|
||||
{ 'type': 'paragraph' },
|
||||
'F',
|
||||
'o',
|
||||
'o',
|
||||
{ 'type': 'exampleUnboldable' },
|
||||
{ 'type': '/exampleUnboldable' },
|
||||
'B',
|
||||
'a',
|
||||
'r',
|
||||
{ 'type': '/paragraph' },
|
||||
{ 'type': 'internalList' },
|
||||
{ 'type': '/internalList' }
|
||||
];
|
||||
|
||||
ve.dm.example.selectNodesCases = [
|
||||
{
|
||||
'range': new ve.Range( 1 ),
|
||||
|
|
Loading…
Reference in a new issue