mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-11-24 14:33:59 +00:00
Add basic support for about groups
About groups are HTML structures like the following: <div about="#mwt1">....</div> <span about="#mwt1">...</span> <div about="#mwt1">...</div> When about groups are alienated, they are now merged into one alien node, rather than producing a separate alien node for each sibling. This is very basic about group handling, because it only works for groups of directly adjacent siblings (text nodes are permitted in between, but nothing else) assumes all about groups are aliens (which is currently true). * Before processing an element in the DOM->data converter, perform about grouping on its children. This temporarily wraps about groups in <div data-ve-aboutgroup="value of about attribute"> * Extended createAlien() to handle single nodes as well as wrappers holding multiple nodes. * In the data->DOM converter, temporarily wrap multi-node aliens in <div data-ve-multi-child-alien-wrapper="true"> . This makes the rest of the algorithm easier. Change-Id: I2df5f62bc222b570fc11a89fe43d353f8363ead8
This commit is contained in:
parent
f10ca89888
commit
d4ea93b872
|
@ -206,13 +206,18 @@ ve.dm.Converter.prototype.getDomElementFromDataAnnotation = function ( dataAnnot
|
|||
* @returns {Array} Linear model data
|
||||
*/
|
||||
ve.dm.Converter.prototype.getDataFromDom = function ( domElement, annotations, dataElement, path, alreadyWrapped ) {
|
||||
function createAlien( domElement, isInline ) {
|
||||
var type = isInline ? 'alienInline' : 'alienBlock';
|
||||
function createAlien( domElement, isInline, isWrapper ) {
|
||||
var type = isInline ? 'alienInline' : 'alienBlock', html;
|
||||
if ( isWrapper ) {
|
||||
html = $( domElement ).html();
|
||||
} else {
|
||||
html = $( '<div>' ).append( $( domElement ).clone() ).html();
|
||||
}
|
||||
return [
|
||||
{
|
||||
'type': type,
|
||||
'attributes': {
|
||||
'html': $( '<div>' ).append( $( domElement ).clone() ).html()
|
||||
'html': html
|
||||
}
|
||||
},
|
||||
{ 'type': '/' + type }
|
||||
|
@ -243,6 +248,64 @@ ve.dm.Converter.prototype.getDataFromDom = function ( domElement, annotations, d
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to group adjacent child elements with the same about attribute together.
|
||||
* If there are multiple adjacent child nodes with the same about attribute, they are
|
||||
* wrapped in a <div> with the data-ve-aboutgroup attribute set.
|
||||
*
|
||||
* This function does not wrap single-element about groups, and does not descend into the
|
||||
* child elements.
|
||||
*
|
||||
* @param element {HTMLElement} Element to process
|
||||
*/
|
||||
function doAboutGrouping( element ) {
|
||||
var child = element.firstChild, textNodes = [],
|
||||
prevChild, aboutGroup, aboutWrapper, childAbout, nextChild, i;
|
||||
while ( child ) {
|
||||
nextChild = child.nextSibling;
|
||||
if ( !child.getAttribute ) {
|
||||
// Text nodes don't have a getAttribute() method. Thanks HTML DOM,
|
||||
// that's really helpful ^^
|
||||
textNodes.push( child );
|
||||
child = nextChild;
|
||||
continue;
|
||||
}
|
||||
childAbout = child.getAttribute( 'about' );
|
||||
if ( childAbout && !aboutGroup ) {
|
||||
// Start of a new about group
|
||||
aboutGroup = childAbout;
|
||||
} else if ( childAbout && childAbout === aboutGroup ) {
|
||||
// Continuation of the current about group
|
||||
if ( !aboutWrapper ) {
|
||||
// This is the second child in this group, so the
|
||||
// previous child is the first child in this group.
|
||||
// Wrap the previous child
|
||||
aboutWrapper = document.createElement( 'div' );
|
||||
aboutWrapper.setAttribute( 'data-ve-aboutgroup', aboutGroup );
|
||||
element.insertBefore( aboutWrapper, prevChild );
|
||||
aboutWrapper.appendChild( prevChild );
|
||||
}
|
||||
// Append any outstanding text nodes to the wrapper
|
||||
for ( i = 0; i < textNodes.length; i++ ) {
|
||||
aboutWrapper.appendChild( textNodes[i] );
|
||||
}
|
||||
// Append this child to the wrapper
|
||||
aboutWrapper.appendChild( child );
|
||||
} else if ( aboutGroup ) {
|
||||
// This child isn't in the current about group
|
||||
aboutGroup = undefined;
|
||||
aboutWrapper = undefined;
|
||||
if ( childAbout ) {
|
||||
// Start of a new about group
|
||||
aboutGroup = childAbout;
|
||||
}
|
||||
}
|
||||
prevChild = child;
|
||||
child = nextChild;
|
||||
textNodes = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to defaults
|
||||
annotations = annotations || [];
|
||||
path = path || ['document'];
|
||||
|
@ -259,11 +322,23 @@ ve.dm.Converter.prototype.getDataFromDom = function ( domElement, annotations, d
|
|||
if ( dataElement ) {
|
||||
data.push( dataElement );
|
||||
}
|
||||
// Do about grouping
|
||||
// FIXME this assumes every about group is an alien
|
||||
doAboutGrouping( domElement );
|
||||
// Add contents
|
||||
for ( i = 0; i < domElement.childNodes.length; i++ ) {
|
||||
childDomElement = domElement.childNodes[i];
|
||||
switch ( childDomElement.nodeType ) {
|
||||
case Node.ELEMENT_NODE:
|
||||
// Alienate about groups
|
||||
if ( childDomElement.hasAttribute( 'data-ve-aboutgroup' ) ) {
|
||||
alien = createAlien( childDomElement, branchIsContent, true );
|
||||
data = data.concat( alien );
|
||||
processNextWhitespace( alien[0] );
|
||||
prevElement = alien[0];
|
||||
break;
|
||||
}
|
||||
|
||||
// HACK handle <meta>/<link> separately because of the
|
||||
// metaInline/metaBlock distinction
|
||||
if (
|
||||
|
@ -795,13 +870,18 @@ ve.dm.Converter.prototype.getDomFromData = function ( data ) {
|
|||
// Create nodes from source
|
||||
wrapper = document.createElement( 'div' );
|
||||
wrapper.innerHTML = dataElement.attributes.html;
|
||||
// Add element - adds first child element, any other
|
||||
// children are ignored but shouldn't exist
|
||||
//parentDomElement = domElement;
|
||||
childDomElement = wrapper.firstChild;
|
||||
//parentDomElement.appendChild( domElement );
|
||||
// Make sure the alien closing is skipped
|
||||
//parentDomElement = domElement; domElement = wrapper; //i++;
|
||||
if ( wrapper.childNodes.length > 1 ) {
|
||||
// Wrap the HTML in a single element, this makes
|
||||
// it much easier to deal with. It'll be unwrapped
|
||||
// at the end of this function.
|
||||
childDomElement = document.createElement( 'div' );
|
||||
childDomElement.setAttribute( 'data-ve-multi-child-alien-wrapper', 'true' );
|
||||
while ( wrapper.firstChild ) {
|
||||
childDomElement.appendChild( wrapper.firstChild );
|
||||
}
|
||||
} else {
|
||||
childDomElement = wrapper.firstChild;
|
||||
}
|
||||
} else {
|
||||
// Create node from data
|
||||
childDomElement = this.getDomElementFromDataElement( dataElement );
|
||||
|
@ -870,6 +950,11 @@ ve.dm.Converter.prototype.getDomFromData = function ( data ) {
|
|||
}
|
||||
delete container.lastOuterPost;
|
||||
}
|
||||
|
||||
// Unwrap multi-child alien wrappers
|
||||
$( container ).find( '[data-ve-multi-child-alien-wrapper]' ) .each( function() {
|
||||
$( this ).replaceWith( $( this ).contents() );
|
||||
} );
|
||||
return container;
|
||||
};
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ QUnit.test( 'getDomElementFromDataElement', 20, function ( assert ) {
|
|||
}
|
||||
} );
|
||||
|
||||
QUnit.test( 'getDataFromDom', 31, function ( assert ) {
|
||||
QUnit.test( 'getDataFromDom', 33, function ( assert ) {
|
||||
var msg,
|
||||
cases = ve.dm.example.domToDataCases;
|
||||
|
||||
|
@ -51,7 +51,7 @@ QUnit.test( 'getDataFromDom', 31, function ( assert ) {
|
|||
}
|
||||
} );
|
||||
|
||||
QUnit.test( 'getDomFromData', 33, function ( assert ) {
|
||||
QUnit.test( 'getDomFromData', 35, function ( assert ) {
|
||||
var msg,
|
||||
cases = ve.dm.example.domToDataCases;
|
||||
|
||||
|
|
|
@ -1507,5 +1507,70 @@ ve.dm.example.domToDataCases = {
|
|||
'normalizedHtml': '<p data-ve-changed="{"content":1}">' +
|
||||
'Foo<img data-ve-changed="{"attributes":2}" />' +
|
||||
'</p><p data-ve-changed="{"created":1}">Bar</p>'
|
||||
},
|
||||
'about grouping': {
|
||||
'html': '<div typeof="mw:Placeholder" about="#mwt1">Foo</div>' +
|
||||
'<figure typeof="mw:Placeholder" about="#mwt1">Bar</figure>' +
|
||||
'<figure typeof="mw:Placeholder" about="#mwt2">Baz</figure>' +
|
||||
'<span typeof="mw:Placeholder" about="#mwt2">Quux</span>' +
|
||||
'<p>Whee</p><span typeof="mw:Placeholder" about="#mwt2">Yay</span>' +
|
||||
'<div typeof="mw:Placeholder" about="#mwt2">Blah</div>' +
|
||||
'<span typeof="mw:Placeholder" about="#mwt3">Meh</span>',
|
||||
'data': [
|
||||
{
|
||||
'type': 'alienBlock',
|
||||
'attributes': {
|
||||
'html': '<div typeof="mw:Placeholder" about="#mwt1">Foo</div>' +
|
||||
'<figure typeof="mw:Placeholder" about="#mwt1">Bar</figure>'
|
||||
}
|
||||
},
|
||||
{ 'type': '/alienBlock' },
|
||||
{
|
||||
'type': 'alienBlock',
|
||||
'attributes': {
|
||||
'html': '<figure typeof="mw:Placeholder" about="#mwt2">Baz</figure>' +
|
||||
'<span typeof="mw:Placeholder" about="#mwt2">Quux</span>'
|
||||
}
|
||||
},
|
||||
{ 'type': '/alienBlock' },
|
||||
{ 'type': 'paragraph' },
|
||||
'W',
|
||||
'h',
|
||||
'e',
|
||||
'e',
|
||||
{ 'type': '/paragraph' },
|
||||
{
|
||||
'type': 'alienBlock',
|
||||
'attributes': {
|
||||
'html': '<span typeof="mw:Placeholder" about="#mwt2">Yay</span>' +
|
||||
'<div typeof="mw:Placeholder" about="#mwt2">Blah</div>'
|
||||
}
|
||||
},
|
||||
{ 'type': '/alienBlock' },
|
||||
{
|
||||
'type': 'alienBlock',
|
||||
'attributes': {
|
||||
'html': '<span typeof="mw:Placeholder" about="#mwt3">Meh</span>'
|
||||
}
|
||||
},
|
||||
{ 'type': '/alienBlock' }
|
||||
]
|
||||
},
|
||||
'whitespace preservation with an about group': {
|
||||
'html': ' <div typeof="mw:Placeholder" about="#mwt1">\tFoo\t\t</div>\t\t\t' +
|
||||
'<div typeof="mw:Placeholder" about="#mwt1"> Bar </div> ',
|
||||
'data': [
|
||||
{
|
||||
'type': 'alienBlock',
|
||||
'attributes': {
|
||||
'html': '<div typeof="mw:Placeholder" about="#mwt1">\tFoo\t\t</div>\t\t\t' +
|
||||
'<div typeof="mw:Placeholder" about="#mwt1"> Bar </div>'
|
||||
},
|
||||
'internal': {
|
||||
'whitespace': [ ' ', undefined, undefined, ' ' ]
|
||||
}
|
||||
},
|
||||
{ 'type': '/alienBlock' }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue