From a9ee5c1044c7f44a29a91dfc9307ba698ac7d7e3 Mon Sep 17 00:00:00 2001 From: Dave Long Date: Sat, 11 Nov 2023 12:12:12 +0000 Subject: [PATCH] Issue #3342874 by star-szr, Wim Leers, DamienMcKenna: Allow inline HTML comments in CKEditor 5 (cherry picked from commit 66716dc9200f824e08c23cde39539b123add0821) --- .../modules/ckeditor5/ckeditor5.ckeditor5.yml | 11 +++++ .../ckeditor5/js/build/drupalHtmlEngine.js | 2 +- .../drupalHtmlEngine/src/drupalhtmlbuilder.js | 16 +++++++ .../FunctionalJavascript/CKEditor5Test.php | 43 +++++++++++++++++++ .../src/Kernel/CKEditor5PluginManagerTest.php | 2 + .../Nightwatch/Tests/drupalHtmlBuilderTest.js | 2 +- 6 files changed, 74 insertions(+), 2 deletions(-) diff --git a/core/modules/ckeditor5/ckeditor5.ckeditor5.yml b/core/modules/ckeditor5/ckeditor5.ckeditor5.yml index a553162fc3ef..2f9d032d9093 100644 --- a/core/modules/ckeditor5/ckeditor5.ckeditor5.yml +++ b/core/modules/ckeditor5/ckeditor5.ckeditor5.yml @@ -83,6 +83,17 @@ ckeditor5_style: elements: - <$any-html5-element class> +ckeditor5_htmlComments: + ckeditor5: + plugins: + - htmlSupport.HtmlComment + drupal: + label: HTML Comment support + elements: false + library: core/ckeditor5.htmlSupport + # @see \Drupal\ckeditor5\Plugin\CKEditor5PluginManagerInterface::getEnabledDefinitions() + conditions: [] + ckeditor5_arbitraryHtmlSupport: ckeditor5: plugins: [htmlSupport.GeneralHtmlSupport] diff --git a/core/modules/ckeditor5/js/build/drupalHtmlEngine.js b/core/modules/ckeditor5/js/build/drupalHtmlEngine.js index 4853b220fa3c..5d926e5e2862 100644 --- a/core/modules/ckeditor5/js/build/drupalHtmlEngine.js +++ b/core/modules/ckeditor5/js/build/drupalHtmlEngine.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.CKEditor5=t():(e.CKEditor5=e.CKEditor5||{},e.CKEditor5.drupalHtmlEngine=t())}(globalThis,(()=>(()=>{var e={"ckeditor5/src/core.js":(e,t,r)=>{e.exports=r("dll-reference CKEditor5.dll")("./src/core.js")},"dll-reference CKEditor5.dll":e=>{"use strict";e.exports=CKEditor5.dll}},t={};function r(n){var p=t[n];if(void 0!==p)return p.exports;var s=t[n]={exports:{}};return e[n](s,s.exports,r),s.exports}r.d=(e,t)=>{for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t);var n={};return(()=>{"use strict";r.d(n,{default:()=>o});var e=r("ckeditor5/src/core.js");class t{constructor(){this.chunks=[],this.selfClosingTags=["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]}build(){return this.chunks.join("")}appendNode(e){e.nodeType===Node.TEXT_NODE?this._appendText(e):e.nodeType===Node.ELEMENT_NODE?this._appendElement(e):e.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&this._appendChildren(e)}_appendElement(e){const t=e.nodeName.toLowerCase();this._append("<"),this._append(t),this._appendAttributes(e),this._append(">"),this.selfClosingTags.includes(t)||(this._appendChildren(e),this._append(""))}_appendChildren(e){Object.keys(e.childNodes).forEach((t=>{this.appendNode(e.childNodes[t])}))}_appendAttributes(e){Object.keys(e.attributes).forEach((t=>{this._append(" "),this._append(e.attributes[t].name),this._append('="'),this._append(this.constructor._escapeAttribute(e.attributes[t].value)),this._append('"')}))}_appendText(e){const t=document.implementation.createHTMLDocument("").createElement("p");t.textContent=e.textContent,this._append(t.innerHTML)}_append(e){this.chunks.push(e)}static _escapeAttribute(e){return e.replace(/&/g,"&").replace(/'/g,"'").replace(/"/g,""").replace(//g,">").replace(/\r\n/g," ").replace(/[\r\n]/g," ")}}class p{getHtml(e){const r=new t;return r.appendNode(e),r.build()}}class s extends e.Plugin{init(){this.editor.data.processor.htmlWriter=new p}static get pluginName(){return"DrupalHtmlEngine"}}const o={DrupalHtmlEngine:s}})(),n=n.default})())); \ No newline at end of file +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.CKEditor5=t():(e.CKEditor5=e.CKEditor5||{},e.CKEditor5.drupalHtmlEngine=t())}(globalThis,(()=>(()=>{var e={"ckeditor5/src/core.js":(e,t,n)=>{e.exports=n("dll-reference CKEditor5.dll")("./src/core.js")},"dll-reference CKEditor5.dll":e=>{"use strict";e.exports=CKEditor5.dll}},t={};function n(p){var r=t[p];if(void 0!==r)return r.exports;var s=t[p]={exports:{}};return e[p](s,s.exports,n),s.exports}n.d=(e,t)=>{for(var p in t)n.o(t,p)&&!n.o(e,p)&&Object.defineProperty(e,p,{enumerable:!0,get:t[p]})},n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t);var p={};return(()=>{"use strict";n.d(p,{default:()=>o});var e=n("ckeditor5/src/core.js");class t{constructor(){this.chunks=[],this.selfClosingTags=["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]}build(){return this.chunks.join("")}appendNode(e){e.nodeType===Node.TEXT_NODE?this._appendText(e):e.nodeType===Node.ELEMENT_NODE?this._appendElement(e):e.nodeType===Node.DOCUMENT_FRAGMENT_NODE?this._appendChildren(e):e.nodeType===Node.COMMENT_NODE&&this._appendComment(e)}_appendElement(e){const t=e.nodeName.toLowerCase();this._append("<"),this._append(t),this._appendAttributes(e),this._append(">"),this.selfClosingTags.includes(t)||(this._appendChildren(e),this._append(""))}_appendChildren(e){Object.keys(e.childNodes).forEach((t=>{this.appendNode(e.childNodes[t])}))}_appendAttributes(e){Object.keys(e.attributes).forEach((t=>{this._append(" "),this._append(e.attributes[t].name),this._append('="'),this._append(this.constructor._escapeAttribute(e.attributes[t].value)),this._append('"')}))}_appendText(e){const t=document.implementation.createHTMLDocument("").createElement("p");t.textContent=e.textContent,this._append(t.innerHTML)}_appendComment(e){this._append("\x3c!--"),this._append(e.textContent),this._append("--\x3e")}_append(e){this.chunks.push(e)}static _escapeAttribute(e){return e.replace(/&/g,"&").replace(/'/g,"'").replace(/"/g,""").replace(//g,">").replace(/\r\n/g," ").replace(/[\r\n]/g," ")}}class r{getHtml(e){const n=new t;return n.appendNode(e),n.build()}}class s extends e.Plugin{init(){this.editor.data.processor.htmlWriter=new r}static get pluginName(){return"DrupalHtmlEngine"}}const o={DrupalHtmlEngine:s}})(),p=p.default})())); \ No newline at end of file diff --git a/core/modules/ckeditor5/js/ckeditor5_plugins/drupalHtmlEngine/src/drupalhtmlbuilder.js b/core/modules/ckeditor5/js/ckeditor5_plugins/drupalHtmlEngine/src/drupalhtmlbuilder.js index 48178378d5de..5e0321664c8f 100644 --- a/core/modules/ckeditor5/js/ckeditor5_plugins/drupalHtmlEngine/src/drupalhtmlbuilder.js +++ b/core/modules/ckeditor5/js/ckeditor5_plugins/drupalHtmlEngine/src/drupalhtmlbuilder.js @@ -61,6 +61,8 @@ export default class DrupalHtmlBuilder { this._appendElement(node); } else if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { this._appendChildren(node); + } else if (node.nodeType === Node.COMMENT_NODE) { + this._appendComment(node); } } @@ -142,6 +144,20 @@ export default class DrupalHtmlBuilder { this._append(container.innerHTML); } + /** + * Appends a comment to the value. + * + * @param {DocumentFragment} node + * A document fragment to be appended to the value. + * + * @private + */ + _appendComment(node) { + this._append(''); + } + /** * Appends a string to the value. * diff --git a/core/modules/ckeditor5/tests/src/FunctionalJavascript/CKEditor5Test.php b/core/modules/ckeditor5/tests/src/FunctionalJavascript/CKEditor5Test.php index 38acd7ac4a50..ccf5d2b187e0 100644 --- a/core/modules/ckeditor5/tests/src/FunctionalJavascript/CKEditor5Test.php +++ b/core/modules/ckeditor5/tests/src/FunctionalJavascript/CKEditor5Test.php @@ -745,4 +745,47 @@ JS; $assert_session->responseContains('

Hello World

مرحبا بالعالم

'); } + /** + * Ensures that HTML comments are preserved in CKEditor 5. + */ + public function testComments(): void { + $page = $this->getSession()->getPage(); + $assert_session = $this->assertSession(); + + // Add a node with text rendered via the Plain Text format. + $this->drupalGet('node/add'); + $page->fillField('title[0][value]', 'My test content'); + $page->fillField('body[0][value]', '

This is a test!

'); + $page->pressButton('Save'); + + FilterFormat::create([ + 'format' => 'ckeditor5', + 'name' => 'CKEditor 5 HTML comments test', + 'roles' => [RoleInterface::AUTHENTICATED_ID], + ])->save(); + Editor::create([ + 'format' => 'ckeditor5', + 'editor' => 'ckeditor5', + ])->save(); + $this->assertSame([], array_map( + function (ConstraintViolation $v) { + return (string) $v->getMessage(); + }, + iterator_to_array(CKEditor5::validatePair( + Editor::load('ckeditor5'), + FilterFormat::load('ckeditor5') + )) + )); + + $this->drupalGet('node/1/edit'); + $page->selectFieldOption('body[0][format]', 'ckeditor5'); + $this->assertNotEmpty($assert_session->waitForText('Change text format?')); + $page->pressButton('Continue'); + + $this->assertNotEmpty($assert_session->waitForElement('css', '.ck-editor')); + $page->pressButton('Save'); + + $assert_session->responseContains('

This is a test!

'); + } + } diff --git a/core/modules/ckeditor5/tests/src/Kernel/CKEditor5PluginManagerTest.php b/core/modules/ckeditor5/tests/src/Kernel/CKEditor5PluginManagerTest.php index 0dbd9faa9d85..469e582d2441 100644 --- a/core/modules/ckeditor5/tests/src/Kernel/CKEditor5PluginManagerTest.php +++ b/core/modules/ckeditor5/tests/src/Kernel/CKEditor5PluginManagerTest.php @@ -1092,6 +1092,7 @@ PHP, 'ckeditor5_globalAttributeDir', 'ckeditor5_globalAttributeLang', 'ckeditor5_heading', + 'ckeditor5_htmlComments', 'ckeditor5_paragraph', 'ckeditor5_pasteFromOffice', ]; @@ -1214,6 +1215,7 @@ PHP, 'ckeditor5_emphasis', 'ckeditor5_essentials', 'ckeditor5_heading', + 'ckeditor5_htmlComments', 'ckeditor5_paragraph', 'ckeditor5_pasteFromOffice', ]; diff --git a/core/modules/ckeditor5/tests/src/Nightwatch/Tests/drupalHtmlBuilderTest.js b/core/modules/ckeditor5/tests/src/Nightwatch/Tests/drupalHtmlBuilderTest.js index 906097a3f7be..0a051e5d8ad6 100644 --- a/core/modules/ckeditor5/tests/src/Nightwatch/Tests/drupalHtmlBuilderTest.js +++ b/core/modules/ckeditor5/tests/src/Nightwatch/Tests/drupalHtmlBuilderTest.js @@ -84,7 +84,7 @@ module.exports = { drupalHtmlBuilder.appendNode(fragment); - assert.equal(drupalHtmlBuilder.build(), '
bar
'); + assert.equal(drupalHtmlBuilder.build(), '
bar
'); }, 'should return correct HTML from fragment with attributes': function () { const drupalHtmlBuilder = new DrupalHtmlBuilder();