From f61222ec904ed56794f7b97e362ea2006dfceaf6 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Fri, 22 Jul 2022 15:44:25 +0200 Subject: [PATCH] Fix headless mode with image and videos. Closes #4473 #4474 --- src/dom_components/model/ComponentImage.js | 47 ++++++++++++++-------- src/dom_components/model/ComponentVideo.js | 3 +- src/selector_manager/index.ts | 1 + src/style_manager/index.js | 7 ++-- src/utils/mixins.ts | 33 +++++++++++---- test/specs/grapesjs/headless.js | 36 +++++++++++++++++ 6 files changed, 100 insertions(+), 27 deletions(-) diff --git a/src/dom_components/model/ComponentImage.js b/src/dom_components/model/ComponentImage.js index 185ec4d2e..5a9f52bcc 100644 --- a/src/dom_components/model/ComponentImage.js +++ b/src/dom_components/model/ComponentImage.js @@ -1,6 +1,6 @@ import { result } from 'underscore'; import Component from './Component'; -import { toLowerCase, buildBase64UrlFromSvg } from 'utils/mixins'; +import { toLowerCase, buildBase64UrlFromSvg, hasWin } 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)"'; @@ -122,23 +122,38 @@ export default Component.extend( * @private */ parseUri(uri) { - var el = document.createElement('a'); - el.href = uri; - var query = {}; - var qrs = el.search.substring(1).split('&'); - for (var i = 0; i < qrs.length; i++) { - var pair = qrs[i].split('='); - var name = decodeURIComponent(pair[0]); - if (name) query[name] = decodeURIComponent(pair[1]); + let result = {}; + + const getQueryObject = (search = '') => { + const query = {}; + const qrs = search.substring(1).split('&'); + + for (let i = 0; i < qrs.length; i++) { + const pair = qrs[i].split('='); + const name = decodeURIComponent(pair[0]); + if (name) query[name] = decodeURIComponent(pair[1] || ''); + } + + return query; + }; + + if (hasWin()) { + result = document.createElement('a'); + result.href = uri; + } else if (typeof URL !== 'undefined') { + try { + result = new URL(uri); + } catch (e) {} } + return { - hostname: el.hostname, - pathname: el.pathname, - protocol: el.protocol, - search: el.search, - hash: el.hash, - port: el.port, - query, + hostname: result.hostname || '', + pathname: result.pathname || '', + protocol: result.protocol || '', + search: result.search || '', + hash: result.hash || '', + port: result.port || '', + query: getQueryObject(result.search), }; }, }, diff --git a/src/dom_components/model/ComponentVideo.js b/src/dom_components/model/ComponentVideo.js index a21683e6b..586799b67 100644 --- a/src/dom_components/model/ComponentVideo.js +++ b/src/dom_components/model/ComponentVideo.js @@ -47,6 +47,7 @@ export default Component.extend( * @private */ updateTraits() { + const { em } = this; const prov = this.get('provider'); let tagName = 'iframe'; let traits; @@ -66,7 +67,7 @@ export default Component.extend( this.set({ tagName }, { silent: 1 }); // avoid break in view this.set({ traits }); - this.em.trigger('component:toggled'); + em.get('ready') && em.trigger('component:toggled'); }, /** diff --git a/src/selector_manager/index.ts b/src/selector_manager/index.ts index 71f3db900..a0fe23dd7 100644 --- a/src/selector_manager/index.ts +++ b/src/selector_manager/index.ts @@ -480,6 +480,7 @@ export default class SelectorManager extends Module { destroy() { const { selectorTags, model } = this; model.stopListening(); + this.__update.cancel(); this.__destroy(); selectorTags?.remove(); this.selectorTags = undefined; diff --git a/src/style_manager/index.js b/src/style_manager/index.js index 6409aa6cd..bce637055 100644 --- a/src/style_manager/index.js +++ b/src/style_manager/index.js @@ -143,8 +143,8 @@ export default () => { // Triggers for the selection refresh and properties const ev = 'component:toggled component:update:classes change:state change:device frame:resized selector:type'; - const upAll = debounce(() => this.__upSel()); - model.listenTo(em, ev, upAll); + this.upAll = debounce(() => this.__upSel()); + model.listenTo(em, ev, this.upAll); // Clear state target on any component selection change, without debounce (#4208) model.listenTo(em, 'component:toggled', this.__clearStateTarget); @@ -781,7 +781,8 @@ export default () => { coll.stopListening(); }); SectView && SectView.remove(); - [properties, sectors, SectView].forEach(i => (i = {})); + this.model.stopListening(); + this.upAll.cancel(); this.em = {}; this.config = {}; this.builtIn = {}; diff --git a/src/utils/mixins.ts b/src/utils/mixins.ts index 968622285..9c2998b4d 100644 --- a/src/utils/mixins.ts +++ b/src/utils/mixins.ts @@ -23,7 +23,7 @@ export const getUiClass = (em, defCls) => { * Import styles asynchronously * @param {String|Array} styles */ -const appendStyles = (styles: {}, opts: { unique?: boolean, prepand?: boolean } = {}) => { +const appendStyles = (styles: {}, opts: { unique?: boolean; prepand?: boolean } = {}) => { const stls = isArray(styles) ? [...styles] : [styles]; if (stls.length) { @@ -87,7 +87,12 @@ const shallowDiff = (objOrig: Record, objNew: Record) return result; }; -const on = (el: HTMLElement|Window|Document | (Window|HTMLElement|Document)[], ev: string, fn: (ev: Event) => void, opts?: AddEventListenerOptions) => { +const on = ( + el: HTMLElement | Window | Document | (Window | HTMLElement | Document)[], + ev: string, + fn: (ev: Event) => void, + opts?: AddEventListenerOptions +) => { const evs = ev.split(/\s+/); el = el instanceof Array ? el : [el]; @@ -96,7 +101,12 @@ const on = (el: HTMLElement|Window|Document | (Window|HTMLElement|Document)[], e } }; -const off = (el: HTMLElement|Window|Document | (Window|HTMLElement|Document)[], ev: string, fn: (ev: Event) => void, opts?: AddEventListenerOptions) => { +const off = ( + el: HTMLElement | Window | Document | (Window | HTMLElement | Document)[], + ev: string, + fn: (ev: Event) => void, + opts?: AddEventListenerOptions +) => { const evs = ev.split(/\s+/); el = el instanceof Array ? el : [el]; @@ -140,7 +150,7 @@ const hasDnd = (em: any) => { const getElement = (el: HTMLElement) => { if (isElement(el) || isTextNode(el)) { return el; - // @ts-ignore + // @ts-ignore } else if (el && el.getEl) { // @ts-ignore return el.getEl(); @@ -247,7 +257,7 @@ const getElRect = (el?: HTMLElement) => { */ const getPointerEvent = (ev: Event) => // @ts-ignore - (ev.touches && ev.touches[0] ? ev.touches[0] : ev); + ev.touches && ev.touches[0] ? ev.touches[0] : ev; /** * Get cross-browser keycode @@ -282,9 +292,18 @@ const createId = (length = 16) => { export const buildBase64UrlFromSvg = (svg: string) => { if (svg && svg.substr(0, 4) === ' { expect(editor.getHtml()).toBe(fullHtml); expect(editor.getCss()).toBe(styleStr); }); + + test('loadProjectData with different components', () => { + editor.loadProjectData({ + pages: [ + { + frames: [ + { + component: { + type: 'wrapper', + attributes: { id: 'wrapper-id' }, + components: [ + { + type: 'text', + attributes: { id: 'text-id' }, + components: [{ type: 'textnode', content: 'Hello world!' }], + }, + { + type: 'image', + attributes: { id: 'image-id' }, + }, + { + type: 'video', + attributes: { id: 'video-id' }, + }, + ], + }, + }, + ], + id: 'page-id', + }, + ], + }); + + expect(editor.getHtml()).toBeDefined(); + expect(editor.getCss()).toBeDefined(); + }); }); });