Issue #3342874 by star-szr, Wim Leers, DamienMcKenna: Allow inline HTML comments in CKEditor 5

(cherry picked from commit 66716dc920)
merge-requests/5366/head
Dave Long 2023-11-11 12:12:12 +00:00
parent e54a7cec2a
commit a9ee5c1044
No known key found for this signature in database
GPG Key ID: ED52AE211E142771
6 changed files with 74 additions and 2 deletions

View File

@ -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]

View File

@ -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("</"),this._append(t),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,"&amp;").replace(/'/g,"&apos;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/\r\n/g,"&#13;").replace(/[\r\n]/g,"&#13;")}}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})()));
!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("</"),this._append(t),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,"&amp;").replace(/'/g,"&apos;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/\r\n/g,"&#13;").replace(/[\r\n]/g,"&#13;")}}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})()));

View File

@ -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('<!--');
this._append(node.textContent);
this._append('-->');
}
/**
* Appends a string to the value.
*

View File

@ -745,4 +745,47 @@ JS;
$assert_session->responseContains('<p dir="ltr" lang="en">Hello World</p><p dir="rtl" lang="ar">مرحبا بالعالم</p>');
}
/**
* 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]', '<!-- Hamsters, alpacas, llamas, and kittens are cute! --><p>This is a <em>test!</em></p>');
$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('<!-- Hamsters, alpacas, llamas, and kittens are cute! --><p>This is a <em>test!</em></p>');
}
}

View File

@ -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',
];

View File

@ -84,7 +84,7 @@ module.exports = {
drupalHtmlBuilder.appendNode(fragment);
assert.equal(drupalHtmlBuilder.build(), '<div>bar</div>');
assert.equal(drupalHtmlBuilder.build(), '<div>bar</div><!--bar-->');
},
'should return correct HTML from fragment with attributes': function () {
const drupalHtmlBuilder = new DrupalHtmlBuilder();