jenkins-bot has submitted this change and it was merged. Change subject: For empty / whitespace-only headings, output <p> instead of <h#> ......................................................................
For empty / whitespace-only headings, output <p> instead of <h#> Because sending HTML like <h2> </h2> or <h2></h2> to Parsoid produces undesirable output like == == or ==<nowiki />== Bug: T51452 Bug: T52100 Bug: T57769 Bug: T61647 Change-Id: If15a1b4b31d4f08c23ecdf2ecf61a8a14a77259a --- M modules/ve-mw/dm/nodes/ve.dm.MWHeadingNode.js M modules/ve-mw/tests/dm/ve.dm.mwExample.js 2 files changed, 169 insertions(+), 0 deletions(-) Approvals: Trevor Parscal: Looks good to me, approved jenkins-bot: Verified diff --git a/modules/ve-mw/dm/nodes/ve.dm.MWHeadingNode.js b/modules/ve-mw/dm/nodes/ve.dm.MWHeadingNode.js index 9653b3f..bf6b528 100644 --- a/modules/ve-mw/dm/nodes/ve.dm.MWHeadingNode.js +++ b/modules/ve-mw/dm/nodes/ve.dm.MWHeadingNode.js @@ -30,6 +30,77 @@ ve.dm.MWHeadingNode.static.suggestedParentNodeTypes = [ 'document', 'tableCell' ]; +ve.dm.MWHeadingNode.static.handlesOwnChildren = true; + +ve.dm.MWHeadingNode.static.toDataElement = function ( domElements, converter ) { + var shouldConvert = this.shouldConvertToParagraph( domElements[0] ), + parentResult = ve.dm.MWHeadingNode.super.static.toDataElement.apply( this, arguments ); + parentResult.type = this.name; + if ( shouldConvert ) { + ve.setProp( parentResult, 'attributes', 'noconvert', true ); + } + return converter.getDataFromDomSubtree( + domElements[0], + parentResult, + new ve.dm.AnnotationSet( converter.getStore() ) + ); +}; + +ve.dm.MWHeadingNode.static.toDomElements = function ( dataElements, doc, converter ) { + var paragraph, + parentResult = ve.dm.MWHeadingNode.super.static.toDomElements.call( this, dataElements[0], doc, converter ); + converter.getDomSubtreeFromData( dataElements.slice( 1, -1 ), parentResult[0] ); + + if ( + ( !dataElements[0].attributes || !dataElements[0].attributes.noconvert ) && + this.shouldConvertToParagraph( parentResult[0] ) + ) { + // Change parentResult[0] to a paragraph + paragraph = doc.createElement( 'p' ); + while ( parentResult[0].firstChild ) { + paragraph.appendChild( parentResult[0].firstChild ); + } + parentResult[0] = paragraph; + } + + return parentResult; +}; + +/** + * Determine whether a heading should be converted to a paragraph. + * + * This function is called by toDomElements to determine whether the heading should be + * converted to a paragraph, and also by toDataElement to determine whether the heading + * already met the conversion criteria coming in (in which case it won't be converted). + * + * This implementation returns true if the heading does not contain a non-whitespace character, + * but subclasses may override this to add their own logic. + * + * @param {HTMLElement} headingElement Heading DOM element + * @return {boolean} Whether the heading should be converted to a paragraph + */ +ve.dm.MWHeadingNode.static.shouldConvertToParagraph = function ( headingElement ) { + function containsNonWhitespace( el ) { + var i, len, childNode; + for ( i = 0, len = el.childNodes.length; i < len; i++ ) { + childNode = el.childNodes[i]; + if ( + childNode.nodeType === Node.TEXT_NODE && + childNode.data.match( /\S/ ) + ) { + // Text node with a non-whitespace character + return true; + } + if ( childNode.nodeType === Node.ELEMENT_NODE && containsNonWhitespace( childNode ) ) { + return true; + } + } + return false; + } + + return !containsNonWhitespace( headingElement ); +}; + /* Registration */ ve.dm.modelRegistry.register( ve.dm.MWHeadingNode ); diff --git a/modules/ve-mw/tests/dm/ve.dm.mwExample.js b/modules/ve-mw/tests/dm/ve.dm.mwExample.js index dd19ee5..190e0de 100644 --- a/modules/ve-mw/tests/dm/ve.dm.mwExample.js +++ b/modules/ve-mw/tests/dm/ve.dm.mwExample.js @@ -1818,5 +1818,103 @@ model.data.data[7][1].push( model.getStore().index( ve.dm.example.createAnnotation( ve.dm.example.bold ) ) ); }, normalizedBody: '<p>Foo[[B<b>a</b>r]]Baz</p>' + }, + 'mwHeading with no content': { + data: [ + { type: 'mwHeading', attributes: { level: 1 } }, + { type: '/mwHeading' }, + { type: 'internalList' }, + { type: '/internalList' } + ], + normalizedBody: '<p></p>' + }, + 'mwHeading with whitespace content': { + data: [ + { type: 'mwHeading', attributes: { level: 2 } }, + ' ', ' ', '\t', ' ', + { type: '/mwHeading' }, + { type: 'internalList' }, + { type: '/internalList' } + ], + normalizedBody: '<p> \t </p>' + }, + 'mwHeading containing metadata': { + data: [ + { type: 'mwHeading', attributes: { level: 3 } }, + { type: 'alienMeta', originalDomElements: $( '<meta />' ).toArray() }, + { type: '/alienMeta' }, + { type: '/mwHeading' }, + { type: 'internalList' }, + { type: '/internalList' } + ], + normalizedBody: '<p><meta /></p>' + }, + 'mwHeading containing alienated text': { + data: [ + { + type: 'mwHeading', + attributes: { level: 4 } + }, + { type: 'alienInline', originalDomElements: $( '<span rel="ve:Alien">Alien</span>' ).toArray() }, + { type: '/alienInline' }, + { type: '/mwHeading' }, + { type: 'internalList' }, + { type: '/internalList' } + ], + body: '<h4><span rel="ve:Alien">Alien</span></h4>' + }, + 'existing empty mwHeading is not converted to paragraph': { + data: [ + { + type: 'mwHeading', + attributes: { + level: 5, + noconvert: true + } + }, + { type: '/mwHeading' }, + { type: 'internalList' }, + { type: '/internalList' } + ], + body: '<h5></h5>' + }, + 'adding whitespace to existing empty mwHeading does not convert to paragraph': { + data: [ + { + type: 'mwHeading', + attributes: { + level: 6, + noconvert: true + } + }, + { type: '/mwHeading' }, + { type: 'internalList' }, + { type: '/internalList' } + ], + modify: function ( doc ) { + doc.data.data.splice( 1, 0, ' ' ); + }, + body: '<h6></h6>', + normalizedBody: '<h6> </h6>' + }, + 'emptying existing meta-only mwHeading does not convert to paragraph': { + data: [ + { + type: 'mwHeading', + attributes: { + level: 1, + noconvert: true + } + }, + { type: 'alienMeta', originalDomElements: $( '<meta />' ).toArray() }, + { type: '/alienMeta' }, + { type: '/mwHeading' }, + { type: 'internalList' }, + { type: '/internalList' } + ], + modify: function ( doc ) { + doc.metadata.data[1].splice( 0, 1 ); + }, + normalizedBody: '<h1></h1>' } }; -- To view, visit https://gerrit.wikimedia.org/r/162310 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: If15a1b4b31d4f08c23ecdf2ecf61a8a14a77259a Gerrit-PatchSet: 8 Gerrit-Project: mediawiki/extensions/VisualEditor Gerrit-Branch: master Gerrit-Owner: Jforrester <jforres...@wikimedia.org> Gerrit-Reviewer: Catrope <roan.katt...@gmail.com> Gerrit-Reviewer: Jforrester <jforres...@wikimedia.org> Gerrit-Reviewer: John Vandenberg <jay...@gmail.com> Gerrit-Reviewer: Trevor Parscal <tpars...@wikimedia.org> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits