Browse Source

Fix and add a few events (#6397)

* Remove unused styles

* Remove deprecated command options

* Remove deprecated Commands events

* Add `command:call` event for generic Command calls

* Add `project:get` event

* Add `parse:html:before` and `parse:css:before` events

* Avoid using toggleFramesEvents on component resize
pull/6398/head
Artur Arseniev 1 year ago
committed by GitHub
parent
commit
13d1c23b32
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      packages/core/src/canvas/index.ts
  2. 15
      packages/core/src/commands/index.ts
  3. 21
      packages/core/src/commands/types.ts
  4. 24
      packages/core/src/commands/view/CommandAbstract.ts
  5. 2
      packages/core/src/commands/view/SelectComponent.ts
  6. 4
      packages/core/src/editor/model/Editor.ts
  7. 7
      packages/core/src/editor/types.ts
  8. 10
      packages/core/src/parser/model/ParserCss.ts
  9. 9
      packages/core/src/parser/model/ParserHtml.ts
  10. 22
      packages/core/src/parser/types.ts
  11. 7
      packages/core/src/styles/scss/main.scss
  12. 3
      packages/core/test/specs/commands/index.ts
  13. 33
      packages/core/test/specs/commands/view/CommandAbstract.ts
  14. 19
      packages/core/test/specs/grapesjs/index.ts
  15. 25
      packages/core/test/specs/parser/model/ParserCss.ts
  16. 16
      packages/core/test/specs/parser/model/ParserHtml.ts

1
packages/core/src/canvas/index.ts

@ -696,6 +696,7 @@ export default class CanvasModule extends Module<CanvasConfig> {
}
toggleFramesEvents(on: boolean) {
// Seems like this causing a bug for iframes in Chrome: https://issues.chromium.org/issues/41336877
const { style } = this.getFramesEl();
style.pointerEvents = on ? '' : 'none';
}

15
packages/core/src/commands/index.ts

@ -383,18 +383,13 @@ export default class CommandsModule extends Module<CommandsConfig & { pStylePref
runCommand(command?: CommandObject, options: CommandOptions = {}) {
let result;
if (command && command.run) {
if (command?.run) {
const { em, config } = this;
const id = command.id as string;
const editor = em.Editor;
if (!this.isActive(id) || options.force || !config.strict) {
// @ts-ignore
result = editor && command.callRun(editor, options);
if (id && command.stop && !command.noStop && !options.abort) {
this.active[id] = result;
}
result = editor && (command as any).callRun(editor, options);
}
}
@ -411,15 +406,13 @@ export default class CommandsModule extends Module<CommandsConfig & { pStylePref
stopCommand(command?: CommandObject, options: CommandOptions = {}) {
let result;
if (command && command.run) {
if (command?.run) {
const { em, config } = this;
const id = command.id as string;
const editor = em.Editor;
if (this.isActive(id) || options.force || !config.strict) {
if (id) delete this.active[id];
// @ts-ignore
result = command.callStop(editor, options);
result = (command as any).callStop(editor, options);
}
}

21
packages/core/src/commands/types.ts

@ -8,7 +8,6 @@ export enum CommandsEvents {
* });
*/
run = 'command:run',
_run = 'run',
/**
* @event `command:run:COMMAND-ID` Triggered on run of a specific command.
@ -16,7 +15,6 @@ export enum CommandsEvents {
* editor.on('command:run:my-command', ({ result, options }) => { ... });
*/
runCommand = 'command:run:',
_runCommand = 'run:',
/**
* @event `command:run:before:COMMAND-ID` Triggered before the command is called.
@ -38,7 +36,6 @@ export enum CommandsEvents {
* });
*/
abort = 'command:abort:',
_abort = 'abort:',
/**
* @event `command:stop` Triggered on stop of any command.
@ -48,7 +45,6 @@ export enum CommandsEvents {
* });
*/
stop = 'command:stop',
_stop = 'stop',
/**
* @event `command:stop:COMMAND-ID` Triggered on stop of a specific command.
@ -56,7 +52,6 @@ export enum CommandsEvents {
* editor.on('command:run:my-command', ({ result, options }) => { ... });
*/
stopCommand = 'command:stop:',
_stopCommand = 'stop:',
/**
* @event `command:stop:before:COMMAND-ID` Triggered before the command is called to stop.
@ -64,6 +59,22 @@ export enum CommandsEvents {
* editor.on('command:stop:before:my-command', ({ options }) => { ... });
*/
stopBeforeCommand = 'command:stop:before:',
/**
* @event `command:call` Triggered on run or stop of a command.
* @example
* editor.on('command:call', ({ id, result, options, type }) => {
* console.log('Command id', id, 'command result', result, 'call type', type);
* });
*/
call = 'command:call',
/**
* @event `command:call:COMMAND-ID` Triggered on run or stop of a specific command.
* @example
* editor.on('command:call:my-command', ({ result, options, type }) => { ... });
*/
callCommand = 'command:call:',
}
/**{END_EVENTS}*/

24
packages/core/src/commands/view/CommandAbstract.ts

@ -35,6 +35,7 @@ export default class CommandAbstract<O extends ObjectAny = any> extends Model {
plhClass: string;
freezClass: string;
canvas: CanvasModule;
noStop?: boolean;
constructor(o: any) {
super(0);
@ -111,22 +112,26 @@ export default class CommandAbstract<O extends ObjectAny = any> extends Model {
callRun(editor: Editor, options: any = {}) {
const { id } = this;
editor.trigger(`${CommandsEvents.runBeforeCommand}${id}`, { options });
editor.trigger(`${CommandsEvents._runCommand}${id}:before`, options);
if (options.abort) {
editor.trigger(`${CommandsEvents.abort}${id}`, { options });
editor.trigger(`${CommandsEvents._abort}${id}`, options);
return;
}
const sender = options.sender || editor;
const result = this.run(editor, sender, options);
const data = { id, result, options };
const dataCall = { ...data, type: 'run' };
if (!this.noStop) {
editor.Commands.active[id] = result;
}
editor.trigger(`${CommandsEvents.runCommand}${id}`, data);
editor.trigger(`${CommandsEvents.callCommand}${id}`, dataCall);
editor.trigger(CommandsEvents.run, data);
// deprecated
editor.trigger(`${CommandsEvents._runCommand}${id}`, result, options);
editor.trigger(CommandsEvents._run, id, result, options);
editor.trigger(CommandsEvents.call, dataCall);
return result;
}
@ -140,15 +145,14 @@ export default class CommandAbstract<O extends ObjectAny = any> extends Model {
const { id } = this;
const sender = options.sender || editor;
editor.trigger(`${CommandsEvents.stopBeforeCommand}${id}`, { options });
editor.trigger(`${CommandsEvents._stopCommand}${id}:before`, options);
const result = this.stop(editor, sender, options);
const data = { id, result, options };
const dataCall = { ...data, type: 'stop' };
delete editor.Commands.active[id];
editor.trigger(`${CommandsEvents.stopCommand}${id}`, data);
editor.trigger(`${CommandsEvents.callCommand}${id}`, dataCall);
editor.trigger(CommandsEvents.stop, data);
// deprecated
editor.trigger(`${CommandsEvents._stopCommand}${id}`, result, options);
editor.trigger(CommandsEvents._stop, id, result, options);
editor.trigger(CommandsEvents.call, dataCall);
return result;
}

2
packages/core/src/commands/view/SelectComponent.ts

@ -439,7 +439,6 @@ export default {
const { keyHeight, keyWidth, currentUnit, keepAutoHeight, keepAutoWidth } = config;
toggleBodyClass('add', ev, opts);
modelToStyle = em.Styles.getModelToStyle(model);
canvas.toggleFramesEvents(false);
const computedStyle = getComputedStyle(el);
const modelStyle = modelToStyle.getStyle();
@ -477,7 +476,6 @@ export default {
onEnd(ev, opts);
toggleBodyClass('remove', ev, opts);
editor.trigger('component:resize', { ...resizeEventOpts, type: 'end' });
canvas.toggleFramesEvents(true);
showOffsets = true;
self.activeResizer = false;
},

4
packages/core/src/editor/model/Editor.ts

@ -875,7 +875,9 @@ export default class EditorModel extends Model {
this.storables.forEach((m) => {
result = { ...result, ...m.store(1) };
});
return JSON.parse(JSON.stringify(result));
const project = JSON.parse(JSON.stringify(result));
this.trigger(EditorEvents.projectGet, { project });
return project;
}
loadData(project: ProjectData = {}, opts: EditorLoadOptions = {}): ProjectData {

7
packages/core/src/editor/types.ts

@ -35,6 +35,13 @@ export enum EditorEvents {
*/
projectLoad = 'project:load',
/**
* @event `project:get` Event triggered on request of the project data. This can be used to extend the project with custom data.
* @example
* editor.on('project:get', ({ project }) => { project.myCustomKey = 'value' });
*/
projectGet = 'project:get',
/**
* @event `log` Log message triggered.
* @example

10
packages/core/src/parser/model/ParserCss.ts

@ -8,15 +8,19 @@ import { ParserEvents } from '../types';
const ParserCss = (em?: EditorModel, config: ParserConfig = {}) => ({
/**
* Parse CSS string to a desired model object
* @param {String} input CSS string
* @param {String} str CSS string
* @return {Array<Object>}
*/
parse(input: string, opts: { throwOnError?: boolean } = {}) {
parse(str: string, opts: { throwOnError?: boolean } = {}) {
let output: CssRuleJSON[] = [];
const { parserCss } = config;
const editor = em?.Editor;
let nodes: CssRuleJSON[] | ParsedCssRule[] = [];
let error: unknown;
const Parser = em?.Parser;
const inputOptions = { input: str };
Parser?.__emitEvent(ParserEvents.cssBefore, inputOptions);
const { input } = inputOptions;
try {
nodes = parserCss ? parserCss(input, editor!) : BrowserCssParser(input);
@ -26,7 +30,7 @@ const ParserCss = (em?: EditorModel, config: ParserConfig = {}) => ({
}
nodes.forEach((node) => (output = output.concat(this.checkNode(node))));
em?.Parser?.__emitEvent(ParserEvents.css, { input, output, nodes, error });
Parser?.__emitEvent(ParserEvents.css, { input, output, nodes, error });
return output;
},

9
packages/core/src/parser/model/ParserHtml.ts

@ -312,6 +312,7 @@ const ParserHtml = (em?: EditorModel, config: ParserConfig & { returnArray?: boo
*/
parse(str: string, parserCss?: any, opts: HTMLParserOptions = {}) {
const conf = em?.get('Config') || {};
const Parser = em?.Parser;
const res: HTMLParseResult = { html: [] };
const cf = { ...config, ...opts };
const preOptions = {
@ -325,7 +326,9 @@ const ParserHtml = (em?: EditorModel, config: ParserConfig & { returnArray?: boo
asDocument: this.__checkAsDocument(str, preOptions),
};
const { preParser, asDocument } = options;
const input = isFunction(preParser) ? preParser(str, { editor: em?.getEditor()! }) : str;
const inputOptions = { input: isFunction(preParser) ? preParser(str, { editor: em?.getEditor()! }) : str };
Parser?.__emitEvent(ParserEvents.htmlBefore, inputOptions);
const { input } = inputOptions;
const parseRes = isFunction(cf.parserHtml) ? cf.parserHtml(input, options) : BrowserParserHtml(input, options);
let root = parseRes as HTMLElement;
const docEl = parseRes as Document;
@ -365,7 +368,7 @@ const ParserHtml = (em?: EditorModel, config: ParserConfig & { returnArray?: boo
if (styleStr) res.css = parserCss.parse(styleStr);
}
em?.Parser?.__emitEvent(ParserEvents.htmlRoot, { input, root });
Parser?.__emitEvent(ParserEvents.htmlRoot, { input, root });
let resHtml: HTMLParseResult['html'] = [];
if (asDocument) {
@ -379,7 +382,7 @@ const ParserHtml = (em?: EditorModel, config: ParserConfig & { returnArray?: boo
}
res.html = resHtml;
em?.Parser?.__emitEvent(ParserEvents.html, { input, output: res, options });
Parser?.__emitEvent(ParserEvents.html, { input, output: res, options });
return res;
},

22
packages/core/src/parser/types.ts

@ -8,6 +8,17 @@ export enum ParserEvents {
html = 'parse:html',
htmlRoot = 'parse:html:root',
/**
* @event `parse:html:before` Event triggered before the HTML parsing starts. An object containing the input is passed as an argument.
* @example
* editor.on('parse:html:before', (options) => {
* console.log('Parser input', options.input);
* // You can also process the input and update `options.input`
* options.input += '<div>Extra content</div>';
* });
*/
htmlBefore = 'parse:html:before',
/**
* @event `parse:css` On CSS parse, an object containing the input and the output of the parser is passed as an argument.
* @example
@ -15,6 +26,17 @@ export enum ParserEvents {
*/
css = 'parse:css',
/**
* @event `parse:css:before` Event triggered before the CSS parsing starts. An object containing the input is passed as an argument.
* @example
* editor.on('parse:css:before', (options) => {
* console.log('Parser input', options.input);
* // You can also process the input and update `options.input`
* options.input += '.my-class { color: red; }';
* });
*/
cssBefore = 'parse:css:before',
/**
* @event `parse` Catch-all event for all the events mentioned above. An object containing all the available data about the triggered event is passed as an argument to the callback.
* @example

7
packages/core/src/styles/scss/main.scss

@ -99,18 +99,15 @@ $colorsAll: (one, var(--gjs-primary-color)), (two, var(--gjs-secondary-color)),
border: none;
color: inherit;
}
.#{$app-prefix}no-app {
height: 10px;
}
.#{$app-prefix}test {
&btn {
color: '#fff';
}
}
.opac50 {
@include opacity(0.5);
}
.#{$app-prefix}checker-bg,
.checker-bg {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==');

3
packages/core/test/specs/commands/index.ts

@ -1,4 +1,3 @@
import Backbone from 'backbone';
import Commands from '../../../src/commands';
import EditorModel from '../../../src/editor/model/Editor';
import { Command, CommandFunction } from '../../../src/commands/view/CommandAbstract';
@ -24,7 +23,7 @@ describe('Commands', () => {
};
commFunc = () => commResultRun;
em = new EditorModel();
em.set('Editor', { ...Backbone.Events });
em.set('Editor', em);
obj = em.Commands;
});

33
packages/core/test/specs/commands/view/CommandAbstract.ts

@ -20,15 +20,16 @@ describe('CommandAbstract', () => {
const result = command.callRun(editor);
const options = {};
const resOptions = { options, id: command.id, result: returnValue };
expect(triggerSpy.mock.calls.length).toBe(6);
const resCallOptions = { ...resOptions, type: 'run' };
expect(triggerSpy.mock.calls.length).toBe(5);
expect(triggerSpy.mock.calls[0]).toEqual([`${CommandsEvents.runBeforeCommand}test`, { options }]);
expect(triggerSpy.mock.calls[1]).toEqual(['run:test:before', options]);
expect(triggerSpy.mock.calls[2]).toEqual([`${CommandsEvents.runCommand}test`, resOptions]);
expect(triggerSpy.mock.calls[1]).toEqual([`${CommandsEvents.runCommand}test`, resOptions]);
expect(triggerSpy.mock.calls[2]).toEqual([`${CommandsEvents.callCommand}test`, resCallOptions]);
expect(triggerSpy.mock.calls[3]).toEqual([CommandsEvents.run, resOptions]);
expect(triggerSpy.mock.calls[4]).toEqual(['run:test', returnValue, options]);
expect(triggerSpy.mock.calls[5]).toEqual(['run', 'test', returnValue, options]);
expect(triggerSpy.mock.calls[4]).toEqual([CommandsEvents.call, resCallOptions]);
expect(runSpy).toBeCalledTimes(1);
expect(runSpy).toHaveBeenCalledTimes(1);
expect(result).toEqual(returnValue);
});
@ -40,13 +41,11 @@ describe('CommandAbstract', () => {
runSpy.mockReturnValue(returnValue as any);
const result = command.callRun(editor, options);
expect(triggerSpy.mock.calls.length).toBe(4);
expect(triggerSpy.mock.calls.length).toBe(2);
expect(triggerSpy.mock.calls[0]).toEqual([`${CommandsEvents.runBeforeCommand}test`, { options }]);
expect(triggerSpy.mock.calls[1]).toEqual(['run:test:before', options]);
expect(triggerSpy.mock.calls[2]).toEqual([`${CommandsEvents.abort}test`, { options }]);
expect(triggerSpy.mock.calls[3]).toEqual(['abort:test', options]);
expect(triggerSpy.mock.calls[1]).toEqual([`${CommandsEvents.abort}test`, { options }]);
expect(runSpy).toBeCalledTimes(0);
expect(runSpy).toHaveBeenCalledTimes(0);
expect(result).toEqual(undefined);
});
@ -58,16 +57,16 @@ describe('CommandAbstract', () => {
const result = command.callStop(editor);
const options = {};
const resOptions = { options, id: command.id, result: returnValue };
const resCallOptions = { ...resOptions, type: 'stop' };
expect(triggerSpy.mock.calls.length).toBe(6);
expect(triggerSpy.mock.calls.length).toBe(5);
expect(triggerSpy.mock.calls[0]).toEqual([`${CommandsEvents.stopBeforeCommand}test`, { options }]);
expect(triggerSpy.mock.calls[1]).toEqual(['stop:test:before', options]);
expect(triggerSpy.mock.calls[2]).toEqual([`${CommandsEvents.stopCommand}test`, resOptions]);
expect(triggerSpy.mock.calls[1]).toEqual([`${CommandsEvents.stopCommand}test`, resOptions]);
expect(triggerSpy.mock.calls[2]).toEqual([`${CommandsEvents.callCommand}test`, resCallOptions]);
expect(triggerSpy.mock.calls[3]).toEqual([CommandsEvents.stop, resOptions]);
expect(triggerSpy.mock.calls[4]).toEqual(['stop:test', returnValue, options]);
expect(triggerSpy.mock.calls[5]).toEqual(['stop', 'test', returnValue, options]);
expect(triggerSpy.mock.calls[4]).toEqual([CommandsEvents.call, resCallOptions]);
expect(runSpy).toBeCalledTimes(1);
expect(runSpy).toHaveBeenCalledTimes(1);
expect(result).toEqual(returnValue);
});
});

19
packages/core/test/specs/grapesjs/index.ts

@ -241,18 +241,25 @@ describe('GrapesJS', () => {
const id = 'test-command';
const editor = grapesjs.init(config);
const result: Record<string, any> = {};
editor.on(`run:${id}`, () => (result.run = 1));
editor.on(`run:${id}:before`, () => (result.runBefore = 1));
editor.on(`stop:${id}`, () => (result.stop = 1));
editor.on(`stop:${id}:before`, () => (result.stopBefore = 1));
editor.on(`abort:${id}`, () => (result.abort = 1));
const events = editor.Commands.events;
editor.on(`${events.run}:${id}`, () => {
expect(editor.Commands.isActive(id)).toBe(true);
result.run = 1;
});
editor.on(`${events.runBeforeCommand}${id}`, () => (result.runBefore = 1));
editor.on(`${events.stop}:${id}`, () => {
expect(editor.Commands.isActive(id)).toBe(false);
result.stop = 1;
});
editor.on(`${events.stopBeforeCommand}${id}`, () => (result.stopBefore = 1));
editor.on(`${events.abort}${id}`, () => (result.abort = 1));
editor.Commands.add(id, {
run() {},
stop() {},
});
editor.runCommand(id);
editor.stopCommand(id);
editor.on(`run:${id}:before`, (opts) => (opts.abort = 1));
editor.on(`${events.runBeforeCommand}${id}`, ({ options }) => (options.abort = 1));
editor.runCommand(id);
expect(result).toEqual({
run: 1,

25
packages/core/test/specs/parser/model/ParserCss.ts

@ -18,16 +18,25 @@ export const CSS_BG_OBJ = {
describe('ParserCss', () => {
let obj: ReturnType<typeof ParserCss>;
let config;
let customParser: any;
let em = {
getCustomParserCss: () => customParser,
trigger: () => {},
} as unknown as EditorModel;
let em: EditorModel;
beforeEach(() => {
config = {};
obj = ParserCss(em, config);
em = new EditorModel({});
obj = ParserCss(em, {});
});
afterEach(() => {
em.destroy();
});
test('Extend parser input', () => {
const str = ' .test1 { color:red; }';
const result = {
selectors: ['test1'],
style: { color: 'red' },
};
em.on(em.Parser.events.cssBefore, (opts) => (opts.input += str));
expect(obj.parse(str)).toEqual([result, result]);
});
describe('parseSelector', () => {

16
packages/core/test/specs/parser/model/ParserHtml.ts

@ -6,9 +6,10 @@ import { CSS_BG_OBJ, CSS_BG_STR } from './ParserCss';
describe('ParserHtml', () => {
let obj: ReturnType<typeof ParserHtml>;
let em: Editor;
beforeEach(() => {
const em = new Editor({});
em = new Editor({});
const dom = new DomComponents(em);
obj = ParserHtml(em, {
textTags: ['br', 'b', 'i', 'u'],
@ -18,6 +19,19 @@ describe('ParserHtml', () => {
obj.compTypes = dom.componentTypes;
});
afterEach(() => {
em.destroy();
});
test('Extend parser input', () => {
const str = '<div></div>';
const result = { tagName: 'div' };
em.on(em.Parser.events.htmlBefore, (opts) => {
opts.input += str;
});
expect(obj.parse(str).html).toEqual([result, result]);
});
test('Simple div node', () => {
const str = '<div></div>';
const result = [{ tagName: 'div' }];

Loading…
Cancel
Save