Browse Source

Fix headless mode with image and videos. Closes #4473 #4474

pull/4487/head
Artur Arseniev 4 years ago
parent
commit
f61222ec90
  1. 47
      src/dom_components/model/ComponentImage.js
  2. 3
      src/dom_components/model/ComponentVideo.js
  3. 1
      src/selector_manager/index.ts
  4. 7
      src/style_manager/index.js
  5. 33
      src/utils/mixins.ts
  6. 36
      test/specs/grapesjs/headless.js

47
src/dom_components/model/ComponentImage.js

@ -1,6 +1,6 @@
import { result } from 'underscore'; import { result } from 'underscore';
import Component from './Component'; import Component from './Component';
import { toLowerCase, buildBase64UrlFromSvg } from 'utils/mixins'; import { toLowerCase, buildBase64UrlFromSvg, hasWin } from '../../utils/mixins';
const svgAttrs = 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)"'; '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 * @private
*/ */
parseUri(uri) { parseUri(uri) {
var el = document.createElement('a'); let result = {};
el.href = uri;
var query = {}; const getQueryObject = (search = '') => {
var qrs = el.search.substring(1).split('&'); const query = {};
for (var i = 0; i < qrs.length; i++) { const qrs = search.substring(1).split('&');
var pair = qrs[i].split('=');
var name = decodeURIComponent(pair[0]); for (let i = 0; i < qrs.length; i++) {
if (name) query[name] = decodeURIComponent(pair[1]); 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 { return {
hostname: el.hostname, hostname: result.hostname || '',
pathname: el.pathname, pathname: result.pathname || '',
protocol: el.protocol, protocol: result.protocol || '',
search: el.search, search: result.search || '',
hash: el.hash, hash: result.hash || '',
port: el.port, port: result.port || '',
query, query: getQueryObject(result.search),
}; };
}, },
}, },

3
src/dom_components/model/ComponentVideo.js

@ -47,6 +47,7 @@ export default Component.extend(
* @private * @private
*/ */
updateTraits() { updateTraits() {
const { em } = this;
const prov = this.get('provider'); const prov = this.get('provider');
let tagName = 'iframe'; let tagName = 'iframe';
let traits; let traits;
@ -66,7 +67,7 @@ export default Component.extend(
this.set({ tagName }, { silent: 1 }); // avoid break in view this.set({ tagName }, { silent: 1 }); // avoid break in view
this.set({ traits }); this.set({ traits });
this.em.trigger('component:toggled'); em.get('ready') && em.trigger('component:toggled');
}, },
/** /**

1
src/selector_manager/index.ts

@ -480,6 +480,7 @@ export default class SelectorManager extends Module {
destroy() { destroy() {
const { selectorTags, model } = this; const { selectorTags, model } = this;
model.stopListening(); model.stopListening();
this.__update.cancel();
this.__destroy(); this.__destroy();
selectorTags?.remove(); selectorTags?.remove();
this.selectorTags = undefined; this.selectorTags = undefined;

7
src/style_manager/index.js

@ -143,8 +143,8 @@ export default () => {
// Triggers for the selection refresh and properties // Triggers for the selection refresh and properties
const ev = 'component:toggled component:update:classes change:state change:device frame:resized selector:type'; const ev = 'component:toggled component:update:classes change:state change:device frame:resized selector:type';
const upAll = debounce(() => this.__upSel()); this.upAll = debounce(() => this.__upSel());
model.listenTo(em, ev, upAll); model.listenTo(em, ev, this.upAll);
// Clear state target on any component selection change, without debounce (#4208) // Clear state target on any component selection change, without debounce (#4208)
model.listenTo(em, 'component:toggled', this.__clearStateTarget); model.listenTo(em, 'component:toggled', this.__clearStateTarget);
@ -781,7 +781,8 @@ export default () => {
coll.stopListening(); coll.stopListening();
}); });
SectView && SectView.remove(); SectView && SectView.remove();
[properties, sectors, SectView].forEach(i => (i = {})); this.model.stopListening();
this.upAll.cancel();
this.em = {}; this.em = {};
this.config = {}; this.config = {};
this.builtIn = {}; this.builtIn = {};

33
src/utils/mixins.ts

@ -23,7 +23,7 @@ export const getUiClass = (em, defCls) => {
* Import styles asynchronously * Import styles asynchronously
* @param {String|Array<String>} styles * @param {String|Array<String>} styles
*/ */
const appendStyles = (styles: {}, opts: { unique?: boolean, prepand?: boolean } = {}) => { const appendStyles = (styles: {}, opts: { unique?: boolean; prepand?: boolean } = {}) => {
const stls = isArray(styles) ? [...styles] : [styles]; const stls = isArray(styles) ? [...styles] : [styles];
if (stls.length) { if (stls.length) {
@ -87,7 +87,12 @@ const shallowDiff = (objOrig: Record<string, any>, objNew: Record<string, any>)
return result; 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+/); const evs = ev.split(/\s+/);
el = el instanceof Array ? el : [el]; 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+/); const evs = ev.split(/\s+/);
el = el instanceof Array ? el : [el]; el = el instanceof Array ? el : [el];
@ -140,7 +150,7 @@ const hasDnd = (em: any) => {
const getElement = (el: HTMLElement) => { const getElement = (el: HTMLElement) => {
if (isElement(el) || isTextNode(el)) { if (isElement(el) || isTextNode(el)) {
return el; return el;
// @ts-ignore // @ts-ignore
} else if (el && el.getEl) { } else if (el && el.getEl) {
// @ts-ignore // @ts-ignore
return el.getEl(); return el.getEl();
@ -247,7 +257,7 @@ const getElRect = (el?: HTMLElement) => {
*/ */
const getPointerEvent = (ev: Event) => const getPointerEvent = (ev: Event) =>
// @ts-ignore // @ts-ignore
(ev.touches && ev.touches[0] ? ev.touches[0] : ev); ev.touches && ev.touches[0] ? ev.touches[0] : ev;
/** /**
* Get cross-browser keycode * Get cross-browser keycode
@ -282,9 +292,18 @@ const createId = (length = 16) => {
export const buildBase64UrlFromSvg = (svg: string) => { export const buildBase64UrlFromSvg = (svg: string) => {
if (svg && svg.substr(0, 4) === '<svg') { if (svg && svg.substr(0, 4) === '<svg') {
return `data:image/svg+xml;base64,${window.btoa(svg)}`; let base64Str = '';
if (hasWin()) {
base64Str = window.btoa(svg);
} else if (typeof Buffer !== 'undefined') {
base64Str = Buffer.from(svg, 'utf8').toString('base64');
}
return base64Str ? `data:image/svg+xml;base64,${base64Str}` : svg;
} }
return svg
return svg;
}; };
export { export {

36
test/specs/grapesjs/headless.js

@ -88,5 +88,41 @@ describe('GrapesJS Headless', () => {
expect(editor.getHtml()).toBe(fullHtml); expect(editor.getHtml()).toBe(fullHtml);
expect(editor.getCss()).toBe(styleStr); 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();
});
}); });
}); });

Loading…
Cancel
Save