From dacfc42c7dbf996f81b6c143e715b4ccb532f68d Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Thu, 5 Aug 2021 12:54:03 +0200 Subject: [PATCH 01/28] Add DOMParser for HTML --- src/parser/config/config.js | 16 +++++++- src/parser/model/BrowserParserHtml.js | 57 ++++++++++++++++----------- src/parser/model/ParserHtml.js | 8 ++-- 3 files changed, 52 insertions(+), 29 deletions(-) diff --git a/src/parser/config/config.js b/src/parser/config/config.js index d33ad56f8..b6d08fec6 100644 --- a/src/parser/config/config.js +++ b/src/parser/config/config.js @@ -4,6 +4,20 @@ export default { // Custom CSS parser parserCss: null, - // Custom HTML parser + // Default DOMParser mime type + // @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString + htmlType: 'text/html', + + // Custom function which tells which root Element to extract + // from the DOMParser.parseFromString result + // @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString + // @example + // // with htmlType as 'text/html' the function will be (default): + // htmlResult: (doc) => doc.body, + // // in case of `application/xml` + // htmlResult: (doc) => doc, + htmlResult: null, + + // TODO: Custom HTML parser parserHtml: null }; diff --git a/src/parser/model/BrowserParserHtml.js b/src/parser/model/BrowserParserHtml.js index 3a338908f..4c2c16871 100644 --- a/src/parser/model/BrowserParserHtml.js +++ b/src/parser/model/BrowserParserHtml.js @@ -1,4 +1,34 @@ -// PoC +export default (str, config = {}) => { + const { htmlResult } = config; + const parser = new DOMParser(); + const doc = parser.parseFromString(str, config.htmlType); + + return htmlResult ? htmlResult(doc, str) : doc.body; +}; + +/** + * POC, custom html parser specs + * Parse an HTML string to an array of nodes + * example + * parse(`
Hello
World example`) + * // result + * [ + * { + * tagName: 'div', + * attributes: { class: 'mycls', 'data-test': '' }, + * childNodes: ['Hello'], + * },{ + * tagName: 'span', + * childNodes: [ + * 'World ', + * { + * tagName: 'b', + * childNodes: ['example'], + * } + * ], + * } + * ] + * export const parseNodes = nodes => { const result = []; @@ -44,29 +74,7 @@ export const parseNode = el => { }; }; -/** - * Parse an HTML string to an array of nodes - * @example - * parse(`
Hello
World example`) - * // result - * [ - * { - * tagName: 'div', - * attributes: { class: 'mycls', 'data-test': '' }, - * childNodes: ['Hello'], - * },{ - * tagName: 'span', - * childNodes: [ - * 'World ', - * { - * tagName: 'b', - * childNodes: ['example'], - * } - * ], - * } - * ] - */ -export default (str, config) => { +export default (str, config = {}) => { const result = []; const el = document.createElement('div'); el.innerHTML = str; @@ -79,3 +87,4 @@ export default (str, config) => { return result; }; + */ diff --git a/src/parser/model/ParserHtml.js b/src/parser/model/ParserHtml.js index 654031e73..036ca8512 100644 --- a/src/parser/model/ParserHtml.js +++ b/src/parser/model/ParserHtml.js @@ -1,4 +1,5 @@ import { each, isString } from 'underscore'; +import BrowserParserHtml from './BrowserParserHtml'; export default config => { var TEXT_NODE = 'span'; @@ -275,15 +276,14 @@ export default config => { */ parse(str, parserCss) { const { em } = c; - const config = (em && em.get('Config')) || {}; + const conf = (em && em.get('Config')) || {}; const res = { html: '', css: '' }; - const el = document.createElement('div'); - el.innerHTML = str; + const el = BrowserParserHtml(str, config); const scripts = el.querySelectorAll('script'); let i = scripts.length; // Remove all scripts - if (!config.allowScripts) { + if (!conf.allowScripts) { while (i--) scripts[i].parentNode.removeChild(scripts[i]); } From 862311318751117ce1ba7df123878d82ad440cb7 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Thu, 5 Aug 2021 13:11:47 +0200 Subject: [PATCH 02/28] Setup DomParser --- src/parser/config/config.js | 4 ++-- src/parser/model/BrowserParserHtml.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/parser/config/config.js b/src/parser/config/config.js index b6d08fec6..7c6401e07 100644 --- a/src/parser/config/config.js +++ b/src/parser/config/config.js @@ -4,9 +4,9 @@ export default { // Custom CSS parser parserCss: null, - // Default DOMParser mime type + // DOMParser mime type (default 'text/html') // @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString - htmlType: 'text/html', + htmlType: null, // Custom function which tells which root Element to extract // from the DOMParser.parseFromString result diff --git a/src/parser/model/BrowserParserHtml.js b/src/parser/model/BrowserParserHtml.js index 4c2c16871..38a98216c 100644 --- a/src/parser/model/BrowserParserHtml.js +++ b/src/parser/model/BrowserParserHtml.js @@ -1,7 +1,7 @@ export default (str, config = {}) => { const { htmlResult } = config; const parser = new DOMParser(); - const doc = parser.parseFromString(str, config.htmlType); + const doc = parser.parseFromString(str, config.htmlType || 'text/html'); return htmlResult ? htmlResult(doc, str) : doc.body; }; From e4949c3a70ec91352b6b062d17ad3811a418d086 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Sun, 8 Aug 2021 12:12:29 +0200 Subject: [PATCH 03/28] Fix inline script content --- src/dom_components/view/ComponentScriptView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dom_components/view/ComponentScriptView.js b/src/dom_components/view/ComponentScriptView.js index b044bca6e..5291f403c 100644 --- a/src/dom_components/view/ComponentScriptView.js +++ b/src/dom_components/view/ComponentScriptView.js @@ -32,7 +32,7 @@ export default ComponentView.extend({ `; em && em.set('scriptCount', scriptCount + 1); } else { - content = model.get('content'); + content = model.__innerHTML(); } this.el.innerHTML = content; From 8287e8a4434c12ce9a301b66437fd1939dfb7c06 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Mon, 9 Aug 2021 15:36:18 +0200 Subject: [PATCH 04/28] Update browser html parser --- src/parser/config/config.js | 10 ---------- src/parser/model/BrowserParserHtml.js | 19 ++++++++++++++++--- src/utils/index.js | 4 +++- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/parser/config/config.js b/src/parser/config/config.js index 7c6401e07..9964c81e5 100644 --- a/src/parser/config/config.js +++ b/src/parser/config/config.js @@ -8,16 +8,6 @@ export default { // @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString htmlType: null, - // Custom function which tells which root Element to extract - // from the DOMParser.parseFromString result - // @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString - // @example - // // with htmlType as 'text/html' the function will be (default): - // htmlResult: (doc) => doc.body, - // // in case of `application/xml` - // htmlResult: (doc) => doc, - htmlResult: null, - // TODO: Custom HTML parser parserHtml: null }; diff --git a/src/parser/model/BrowserParserHtml.js b/src/parser/model/BrowserParserHtml.js index 38a98216c..b64b2e9ff 100644 --- a/src/parser/model/BrowserParserHtml.js +++ b/src/parser/model/BrowserParserHtml.js @@ -1,9 +1,22 @@ +const htmlType = 'text/html'; +// const defaultType = 'application/xml'; +const defaultType = htmlType; + export default (str, config = {}) => { - const { htmlResult } = config; const parser = new DOMParser(); - const doc = parser.parseFromString(str, config.htmlType || 'text/html'); + const mimeType = config.htmlType || defaultType; + const toHTML = mimeType === htmlType; + const strF = toHTML ? str : `
${str}
`; + const doc = parser.parseFromString(strF, mimeType); + let res; + + if (toHTML) { + res = doc.body; + } else { + res = doc.firstChild; + } - return htmlResult ? htmlResult(doc, str) : doc.body; + return res; }; /** diff --git a/src/utils/index.js b/src/utils/index.js index 14fc93262..c1931d98c 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -1,6 +1,7 @@ import Dragger from './Dragger'; import Sorter from './Sorter'; import Resizer from './Resizer'; +import * as mixins from './mixins'; export default () => { return { @@ -22,6 +23,7 @@ export default () => { Sorter, Resizer, - Dragger + Dragger, + helpers: { ...mixins } }; }; From 2b705caf6b1b3e0bb304c2057d0a55016786691c Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Mon, 9 Aug 2021 17:20:55 +0200 Subject: [PATCH 05/28] Adjust dom parser for HTML type --- src/parser/model/BrowserParserHtml.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/parser/model/BrowserParserHtml.js b/src/parser/model/BrowserParserHtml.js index b64b2e9ff..699446d84 100644 --- a/src/parser/model/BrowserParserHtml.js +++ b/src/parser/model/BrowserParserHtml.js @@ -1,3 +1,5 @@ +import { each } from 'underscore'; + const htmlType = 'text/html'; // const defaultType = 'application/xml'; const defaultType = htmlType; @@ -11,7 +13,16 @@ export default (str, config = {}) => { let res; if (toHTML) { - res = doc.body; + // Replicate the old parser in order to avoid breaking changes + const { head, body } = doc; + // Move all scripts at the bottom of the page + const scripts = head.querySelectorAll('script'); + each(scripts, node => body.appendChild(node)); + // Move inside body all head children + const hEls = []; + each(head.children, n => hEls.push(n)); + each(hEls, (node, i) => body.insertBefore(node, body.children[i])); + res = body; } else { res = doc.firstChild; } From 88420595135ebd601af069e31b5485b325b95885 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Mon, 9 Aug 2021 18:35:45 +0200 Subject: [PATCH 06/28] Improve parsing XML --- src/dom_components/model/Component.js | 10 ++++++++-- src/dom_components/model/ComponentScript.js | 3 ++- src/parser/model/BrowserParserHtml.js | 3 +-- src/parser/model/ParserHtml.js | 4 +++- src/utils/mixins.js | 2 ++ 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/dom_components/model/Component.js b/src/dom_components/model/Component.js index fc1ce726f..67720dfa7 100644 --- a/src/dom_components/model/Component.js +++ b/src/dom_components/model/Component.js @@ -11,7 +11,13 @@ import { bindAll, keys } from 'underscore'; -import { shallowDiff, capitalize, isEmptyObj, isObject } from 'utils/mixins'; +import { + shallowDiff, + capitalize, + isEmptyObj, + isObject, + toLowerCase +} from 'utils/mixins'; import Styleable from 'domain_abstract/model/Styleable'; import { Model } from 'backbone'; import Components from './Components'; @@ -1736,7 +1742,7 @@ export default class Component extends Model.extend(Styleable) { * @private */ Component.isComponent = el => { - return { tagName: el.tagName ? el.tagName.toLowerCase() : '' }; + return { tagName: toLowerCase(el.tagName) }; }; Component.ensureInList = model => { diff --git a/src/dom_components/model/ComponentScript.js b/src/dom_components/model/ComponentScript.js index 8eb17b238..483a2dab0 100644 --- a/src/dom_components/model/ComponentScript.js +++ b/src/dom_components/model/ComponentScript.js @@ -1,4 +1,5 @@ import Component from './Component'; +import { toLowerCase } from 'utils/mixins'; const type = 'script'; @@ -15,7 +16,7 @@ export default Component.extend( }, { isComponent(el) { - if (el.tagName == 'SCRIPT') { + if (toLowerCase(el.tagName) == type) { const result = { type }; if (el.src) { diff --git a/src/parser/model/BrowserParserHtml.js b/src/parser/model/BrowserParserHtml.js index 699446d84..8cfaf27c3 100644 --- a/src/parser/model/BrowserParserHtml.js +++ b/src/parser/model/BrowserParserHtml.js @@ -1,8 +1,7 @@ import { each } from 'underscore'; const htmlType = 'text/html'; -// const defaultType = 'application/xml'; -const defaultType = htmlType; +const defaultType = 'application/xml'; export default (str, config = {}) => { const parser = new DOMParser(); diff --git a/src/parser/model/ParserHtml.js b/src/parser/model/ParserHtml.js index 036ca8512..f0674c2f2 100644 --- a/src/parser/model/ParserHtml.js +++ b/src/parser/model/ParserHtml.js @@ -5,6 +5,7 @@ export default config => { var TEXT_NODE = 'span'; var c = config; var modelAttrStart = 'data-gjs-'; + const event = 'parse:html'; return { compTypes: '', @@ -301,12 +302,13 @@ export default config => { if (styleStr) res.css = parserCss.parse(styleStr); } + em && em.trigger(`${event}:root`, { input: str, root: el }); const result = this.parseNode(el); // I have to keep it otherwise it breaks the DomComponents.addComponent (returns always array) const resHtml = result.length === 1 && !c.returnArray ? result[0] : result; res.html = resHtml; - em && em.trigger('parse:html', { input: str, output: res }); + em && em.trigger(event, { input: str, output: res }); return res; } diff --git a/src/utils/mixins.js b/src/utils/mixins.js index 1730d1b8f..b2283daa9 100644 --- a/src/utils/mixins.js +++ b/src/utils/mixins.js @@ -2,6 +2,8 @@ import { keys, isUndefined, isElement, isArray } from 'underscore'; export const hasWin = () => typeof window !== 'undefined'; +export const toLowerCase = str => (str || '').toLowerCase(); + const elProt = hasWin() ? window.Element.prototype : {}; const matches = elProt.matches || From 7822eb6cc7e9f414d349d7c895669f50817f0ba6 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Mon, 9 Aug 2021 18:40:29 +0200 Subject: [PATCH 07/28] Improve iframe for xml parser --- src/dom_components/model/ComponentFrame.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dom_components/model/ComponentFrame.js b/src/dom_components/model/ComponentFrame.js index da06b0259..b7098bbcb 100644 --- a/src/dom_components/model/ComponentFrame.js +++ b/src/dom_components/model/ComponentFrame.js @@ -1,4 +1,5 @@ import Component from './Component'; +import { toLowerCase } from 'utils/mixins'; const type = 'iframe'; @@ -17,6 +18,6 @@ export default Component.extend( } }, { - isComponent: el => el.tagName === 'IFRAME' + isComponent: el => toLowerCase(el.tagName) === type } ); From 145b1d7458ef76873ed7e354acb9a8f89e50d72d Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Mon, 9 Aug 2021 18:42:04 +0200 Subject: [PATCH 08/28] Start tests for component types --- .../dom_components/model/ComponentTypes.js | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 test/specs/dom_components/model/ComponentTypes.js diff --git a/test/specs/dom_components/model/ComponentTypes.js b/test/specs/dom_components/model/ComponentTypes.js new file mode 100644 index 000000000..7e1947070 --- /dev/null +++ b/test/specs/dom_components/model/ComponentTypes.js @@ -0,0 +1,50 @@ +import Editor from 'editor'; + +describe('Component Types', () => { + let editor; + let wrapper; + + beforeAll(() => { + editor = new Editor({ allowScripts: 1 }); + editor + .getModel() + .get('PageManager') + .onLoad(); + wrapper = editor.getWrapper(); + }); + + afterAll(() => { + editor.destroy(); + }); + + afterEach(() => { + wrapper.components().reset(); + }); + + test('`; + const cmp = wrapper.append(scrFull)[0]; + expect(wrapper.components().length).toBe(1); + expect(cmp.toHTML()).toBe(scrFull); + expect(cmp.is('script')).toBe(true); + }); + + test(''; + const cmp = wrapper.append(str)[0]; + expect(wrapper.components().length).toBe(1); + expect(cmp.toHTML()).toBe(str); + expect(cmp.is('iframe')).toBe(true); + }); + + test.skip(' is correctly recognized', () => { + const cmp = wrapper.append(` + + `)[0]; + expect(wrapper.components().length).toBe(1); + expect(cmp.is('svg')).toBe(true); + }); +}); From 627087826c46ef0fed6221ce32acf83368e60b98 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Tue, 10 Aug 2021 00:48:01 +0200 Subject: [PATCH 09/28] Improve img for xml parser --- src/dom_components/model/ComponentImage.js | 17 ++---------- .../dom_components/model/ComponentTypes.js | 27 +++++++++++-------- 2 files changed, 18 insertions(+), 26 deletions(-) diff --git a/src/dom_components/model/ComponentImage.js b/src/dom_components/model/ComponentImage.js index 5f6cdfe71..9596272eb 100644 --- a/src/dom_components/model/ComponentImage.js +++ b/src/dom_components/model/ComponentImage.js @@ -1,5 +1,6 @@ import { result } from 'underscore'; import Component from './Component'; +import { toLowerCase } from 'utils/mixins'; const svgAttrs = 'xmlns="http://www.w3.org/2000/svg" width="100" viewBox="0 0 24 24" style="fill: rgba(0,0,0,0.15); transform: scale(0.75)"'; @@ -138,20 +139,6 @@ export default Component.extend( } }, { - /** - * Detect if the passed element is a valid component. - * In case the element is valid an object abstracted - * from the element will be returned - * @param {HTMLElement} - * @return {Object} - * @private - */ - isComponent(el) { - var result = ''; - if (el.tagName == 'IMG') { - result = { type: 'image' }; - } - return result; - } + isComponent: el => toLowerCase(el.tagName) === 'img' } ); diff --git a/test/specs/dom_components/model/ComponentTypes.js b/test/specs/dom_components/model/ComponentTypes.js index 7e1947070..134c5d635 100644 --- a/test/specs/dom_components/model/ComponentTypes.js +++ b/test/specs/dom_components/model/ComponentTypes.js @@ -4,6 +4,13 @@ describe('Component Types', () => { let editor; let wrapper; + const expectedType = (input, type) => { + const cmp = wrapper.append(input)[0]; + expect(wrapper.components().length).toBe(1); + expect(cmp.toHTML()).toBe(input); + expect(cmp.is(type)).toBe(true); + }; + beforeAll(() => { editor = new Editor({ allowScripts: 1 }); editor @@ -21,23 +28,21 @@ describe('Component Types', () => { wrapper.components().reset(); }); + test(' is correctly recognized', () => { + expectedType('', 'image'); + }); + test('`; - const cmp = wrapper.append(scrFull)[0]; - expect(wrapper.components().length).toBe(1); - expect(cmp.toHTML()).toBe(scrFull); - expect(cmp.is('script')).toBe(true); + expectedType(``, 'script'); }); test(''; - const cmp = wrapper.append(str)[0]; - expect(wrapper.components().length).toBe(1); - expect(cmp.toHTML()).toBe(str); - expect(cmp.is('iframe')).toBe(true); + expectedType( + ``, + 'iframe' + ); }); test.skip(' is correctly recognized', () => { From de4b1152c38adf74ed28a106ac29f3ee537c1478 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Tue, 10 Aug 2021 01:50:35 +0200 Subject: [PATCH 10/28] Improve label for xml parser --- src/dom_components/model/ComponentLabel.js | 12 ++++++------ test/specs/dom_components/model/ComponentTypes.js | 4 ++++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/dom_components/model/ComponentLabel.js b/src/dom_components/model/ComponentLabel.js index f51095ed0..7df2cd773 100644 --- a/src/dom_components/model/ComponentLabel.js +++ b/src/dom_components/model/ComponentLabel.js @@ -1,18 +1,18 @@ import Component from './ComponentText'; +import { toLowerCase } from 'utils/mixins'; + +const type = 'label'; export default Component.extend( { defaults: { ...Component.prototype.defaults, - tagName: 'label', + type, + tagName: type, traits: ['id', 'title', 'for'] } }, { - isComponent(el) { - if (el.tagName == 'LABEL') { - return { type: 'label' }; - } - } + isComponent: el => toLowerCase(el.tagName) === type } ); diff --git a/test/specs/dom_components/model/ComponentTypes.js b/test/specs/dom_components/model/ComponentTypes.js index 134c5d635..4fd3d16a7 100644 --- a/test/specs/dom_components/model/ComponentTypes.js +++ b/test/specs/dom_components/model/ComponentTypes.js @@ -32,6 +32,10 @@ describe('Component Types', () => { expectedType('', 'image'); }); + test('