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

Reply via email to