diff --git a/README.md b/README.md
index 6e2d3dd2b..d6b54cc9b 100644
--- a/README.md
+++ b/README.md
@@ -5,6 +5,8 @@
[](https://cdnjs.com/libraries/grapesjs)
[](https://www.npmjs.com/package/grapesjs)
+> If you looking to embed the [Studio](https://app.grapesjs.com/studio) editor in your application, we now offer the [Studio SDK](https://app.grapesjs.com/dashboard/sdk/licenses?ref=repo-readme), a ready-to-use visual builder that's easy to embed in external applications, with GrapesJS team support included.
+

GrapesJS is a free and open source Web Builder Framework which helps building HTML templates, faster and easily, to be delivered in sites, newsletters or mobile apps. Mainly, GrapesJS was designed to be used inside a [CMS] to speed up the creation of dynamic templates. To better understand this concept check the image below
diff --git a/src/dom_components/model/types.ts b/src/dom_components/model/types.ts
index 2075edaed..4a603f125 100644
--- a/src/dom_components/model/types.ts
+++ b/src/dom_components/model/types.ts
@@ -65,6 +65,14 @@ export interface ComponentDelegateProps {
* }
*/
select?: (cmp: Component) => Component | Nullable;
+ /**
+ * Delegate another component as a layer in the LayerManager.
+ * @example
+ * delegate: {
+ * layer: (cmp) => cmp.findType('other-type')[0],
+ * }
+ */
+ layer?: (cmp: Component) => Component | Nullable;
}
export interface ComponentProperties {
diff --git a/src/domain_abstract/model/TypeableCollection.ts b/src/domain_abstract/model/TypeableCollection.ts
index a7b35f3d7..ec216b801 100644
--- a/src/domain_abstract/model/TypeableCollection.ts
+++ b/src/domain_abstract/model/TypeableCollection.ts
@@ -27,6 +27,9 @@ const TypeableCollection = {
const model = new Model(attrs, { ...options, em });
model.typeView = View;
+ // As we're using a dynamic model function, backbone collection is unable to
+ // get `model.prototype.idAttribute`
+ this.model.prototype = Model.prototype;
return model;
};
const init = this.init && this.init.bind(this);
diff --git a/src/navigator/index.ts b/src/navigator/index.ts
index 2c2773948..01cf4a029 100644
--- a/src/navigator/index.ts
+++ b/src/navigator/index.ts
@@ -127,9 +127,10 @@ export default class LayerManager extends Module {
root = wrapper.find(component)[0] || wrapper;
}
- this.model.set('root', root);
+ const result = this.__getLayerFromComponent(root);
+ this.model.set('root', result);
- return root;
+ return result;
}
/**
@@ -152,7 +153,10 @@ export default class LayerManager extends Module {
* console.log(components);
*/
getComponents(component: Component): Component[] {
- return component.components().filter((cmp: any) => this.__isLayerable(cmp));
+ return component
+ .components()
+ .map((cmp) => this.__getLayerFromComponent(cmp))
+ .filter((cmp: any) => this.__isLayerable(cmp));
}
/**
@@ -360,12 +364,16 @@ export default class LayerManager extends Module {
this.__trgCustom();
}
+ __getLayerFromComponent(cmp: Component) {
+ return cmp.delegate?.layer?.(cmp) || cmp;
+ }
+
__onComponent(component: Component) {
this.updateLayer(component);
}
__isLayerable(cmp: Component): boolean {
- const tag = cmp.get('tagName');
+ const tag = cmp.tagName;
const hideText = this.config.hideTextnode;
const isValid = !hideText || (!cmp.isInstanceOf('textnode') && tag !== 'br');
diff --git a/src/navigator/view/ItemsView.ts b/src/navigator/view/ItemsView.ts
index bc2f9bdc7..4356881a8 100644
--- a/src/navigator/view/ItemsView.ts
+++ b/src/navigator/view/ItemsView.ts
@@ -4,12 +4,14 @@ import Component from '../../dom_components/model/Component';
import EditorModel from '../../editor/model/Editor';
import ItemView from './ItemView';
import Components from '../../dom_components/model/Components';
+import LayerManager from '..';
export default class ItemsView extends View {
items: ItemView[];
opt: any;
config: any;
parentView: ItemView;
+ module: LayerManager;
/** @ts-ignore */
collection!: Components;
@@ -19,6 +21,7 @@ export default class ItemsView extends View {
this.opt = opt;
const config = opt.config || {};
this.config = config;
+ this.module = opt.module;
this.parentView = opt.parentView;
const pfx = config.stylePrefix || '';
const ppfx = config.pStylePrefix || '';
@@ -128,10 +131,12 @@ export default class ItemsView extends View {
}
render() {
+ const { el, module } = this;
const frag = document.createDocumentFragment();
- const el = this.el;
el.innerHTML = '';
- this.collection.each((model) => this.addToCollection(model, frag));
+ this.collection
+ .map((cmp) => module.__getLayerFromComponent(cmp))
+ .forEach((model) => this.addToCollection(model, frag));
el.appendChild(frag);
el.className = this.className!;
return this;
diff --git a/src/storage_manager/index.ts b/src/storage_manager/index.ts
index 3675a9946..4db8dbfe6 100644
--- a/src/storage_manager/index.ts
+++ b/src/storage_manager/index.ts
@@ -270,12 +270,12 @@ export default class StorageManager extends Module<
const { onStore, onLoad } = this.getConfig();
let result;
- this.onStart(ev, data);
-
if (!storage) {
return data || {};
}
+ this.onStart(ev, data);
+
try {
const editor = this.em?.getEditor();
let response: any;
diff --git a/test/specs/css_composer/index.ts b/test/specs/css_composer/index.ts
index 47c570774..be95cfd4d 100644
--- a/test/specs/css_composer/index.ts
+++ b/test/specs/css_composer/index.ts
@@ -418,8 +418,7 @@ describe('Css Composer', () => {
expect(getCSS(obj)).toEqual(cssRule.trim());
});
- // TODO update jest to be able to test keyframes
- test.skip('Add rules with @keyframes at rule', () => {
+ test('Add rules with @keyframes at rule', () => {
const cssRule = `
@keyframes keyname {
from { width: 0% }
@@ -428,14 +427,24 @@ describe('Css Composer', () => {
}
`;
const result = obj.addCollection(cssRule);
- console.log({ result });
const [rule1, rule2, rule3] = result;
+ console.log({ result: JSON.parse(JSON.stringify(rule1)) });
expect(result.length).toEqual(3);
expect(obj.getAll().length).toEqual(3);
- expect(rule1.get('mediaText')).toBe('keyname');
- expect(rule1.get('atRuleType')).toBe('keyframes');
- // TODO to complete
+ result.forEach((rule) => {
+ expect(rule.get('mediaText')).toBe('keyname');
+ expect(rule.get('atRuleType')).toBe('keyframes');
+ });
+
+ expect(rule1.getSelectorsString()).toBe('from');
+ expect(rule1.getStyle()).toEqual({ width: '0%' });
+
+ expect(rule2.getSelectorsString()).toBe('40%, 50%');
+ expect(rule2.getStyle()).toEqual({ width: '50%' });
+
+ expect(rule3.getSelectorsString()).toBe('to');
+ expect(rule3.getStyle()).toEqual({ width: '100%' });
});
});
});
diff --git a/test/specs/parser/model/ParserCss.ts b/test/specs/parser/model/ParserCss.ts
index fb6f03db0..aa1ce3e4b 100644
--- a/test/specs/parser/model/ParserCss.ts
+++ b/test/specs/parser/model/ParserCss.ts
@@ -251,8 +251,7 @@ describe('ParserCss', () => {
expect(obj.parse(str)).toEqual([result]);
});
- // Can't test keyframes https://github.com/NV/CSSOM/issues/95
- test.skip('Parse rule with a keyframes at-rule', () => {
+ test('Parse rule with a keyframes at-rule', () => {
var str = `@keyframes {
from {opacity: 0;}
to {opacity: 1;}