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 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),
};
},
},

3
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');
},
/**

1
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;

7
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 = {};

33
src/utils/mixins.ts

@ -23,7 +23,7 @@ export const getUiClass = (em, defCls) => {
* Import styles asynchronously
* @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];
if (stls.length) {
@ -87,7 +87,12 @@ const shallowDiff = (objOrig: Record<string, any>, objNew: Record<string, any>)
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) === '<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 {

36
test/specs/grapesjs/headless.js

@ -88,5 +88,41 @@ describe('GrapesJS Headless', () => {
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();
});
});
});

Loading…
Cancel
Save