diff --git a/packages/core/src/parser/model/ParserCss.ts b/packages/core/src/parser/model/ParserCss.ts index 84f8c6fbc..c9ad2b513 100644 --- a/packages/core/src/parser/model/ParserCss.ts +++ b/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} */ - 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; }, diff --git a/packages/core/src/parser/model/ParserHtml.ts b/packages/core/src/parser/model/ParserHtml.ts index 96c7b3121..d19edd128 100644 --- a/packages/core/src/parser/model/ParserHtml.ts +++ b/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; }, diff --git a/packages/core/src/parser/types.ts b/packages/core/src/parser/types.ts index bf1e2bcf3..b9f3839d1 100644 --- a/packages/core/src/parser/types.ts +++ b/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 += '
Extra content
'; + * }); + */ + 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 diff --git a/packages/core/test/specs/parser/model/ParserCss.ts b/packages/core/test/specs/parser/model/ParserCss.ts index aa1ce3e4b..c72919b13 100644 --- a/packages/core/test/specs/parser/model/ParserCss.ts +++ b/packages/core/test/specs/parser/model/ParserCss.ts @@ -18,16 +18,25 @@ export const CSS_BG_OBJ = { describe('ParserCss', () => { let obj: ReturnType; - 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', () => { diff --git a/packages/core/test/specs/parser/model/ParserHtml.ts b/packages/core/test/specs/parser/model/ParserHtml.ts index c01680be0..53314ec60 100644 --- a/packages/core/test/specs/parser/model/ParserHtml.ts +++ b/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; + 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 = '
'; + 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 = '
'; const result = [{ tagName: 'div' }];