Browse Source

Move Parser module to TS

pull/4746/head
Artur Arseniev 4 years ago
parent
commit
4ec49beab1
  1. 5
      src/editor/model/Editor.ts
  2. 14
      src/parser/config/config.ts
  3. 107
      src/parser/index.js
  4. 92
      src/parser/index.ts
  5. 7
      src/parser/model/ParserCss.ts
  6. 22
      src/parser/model/ParserHtml.ts

5
src/editor/model/Editor.ts

@ -10,6 +10,7 @@ import EditorModule from '..';
import EditorView from '../view/EditorView'; import EditorView from '../view/EditorView';
import { IModule } from '../../abstract/Module'; import { IModule } from '../../abstract/Module';
import CanvasModule from '../../canvas'; import CanvasModule from '../../canvas';
import ComponentManager from '../../dom_components';
//@ts-ignore //@ts-ignore
Backbone.$ = $; Backbone.$ = $;
@ -109,6 +110,10 @@ export default class EditorModel extends Model {
return this.get('Editor'); return this.get('Editor');
} }
get Components(): ComponentManager {
return this.get('DomComponents');
}
constructor(conf = {}) { constructor(conf = {}) {
super(); super();
this._config = conf; this._config = conf;

14
src/parser/config/config.ts

@ -1,3 +1,6 @@
import { CssRuleProperties } from '../../css_composer/model/CssRule';
import EditorModule from '../../editor';
export interface HTMLParserOptions { export interface HTMLParserOptions {
/** /**
* DOMParser mime type. * DOMParser mime type.
@ -18,6 +21,12 @@ export interface HTMLParserOptions {
* @default false * @default false
*/ */
allowUnsafeAttr?: boolean; allowUnsafeAttr?: boolean;
/**
* When false, removes empty text nodes when parsed, unless they contain a space.
* @default false
*/
keepEmptyTextNodes?: boolean;
} }
export interface ParserConfig { export interface ParserConfig {
@ -31,7 +40,7 @@ export interface ParserConfig {
* Custom CSS parser. * Custom CSS parser.
* @see https://grapesjs.com/docs/guides/Custom-CSS-parser.html * @see https://grapesjs.com/docs/guides/Custom-CSS-parser.html
*/ */
parserCss?: any; // TODO parserCss?: (str: string, editor: EditorModule) => CssRuleProperties[];
/** /**
* Custom HTML parser. * Custom HTML parser.
@ -52,12 +61,13 @@ export interface ParserConfig {
const config: ParserConfig = { const config: ParserConfig = {
textTags: ['br', 'b', 'i', 'u', 'a', 'ul', 'ol'], textTags: ['br', 'b', 'i', 'u', 'a', 'ul', 'ol'],
parserCss: null, parserCss: undefined,
parserHtml: null, parserHtml: null,
optionsHtml: { optionsHtml: {
htmlType: 'text/html', htmlType: 'text/html',
allowScripts: false, allowScripts: false,
allowUnsafeAttr: false, allowUnsafeAttr: false,
keepEmptyTextNodes: false,
}, },
}; };

107
src/parser/index.js

@ -1,107 +0,0 @@
/**
* You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object](https://github.com/artf/grapesjs/blob/master/src/parser/config/config.js)
* ```js
* const editor = grapesjs.init({
* parser: {
* // options
* }
* })
* ```
*
* Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance
*
* ```js
* const { Parser } = editor;
* ```
* ## Available Events
* * `parse:html` - On HTML parse, an object containing the input and the output of the parser is passed as an argument
* * `parse:css` - On CSS parse, an object containing the input and the output of the parser is passed as an argument
*
* ## Methods
* * [getConfig](#getconfig)
* * [parseHtml](#parsehtml)
* * [parseCss](#parsecss)
*
* @module Parser
*/
import defaults from './config/config';
import parserCss from './model/ParserCss';
import parserHtml from './model/ParserHtml';
export default () => {
let conf = {};
let pHtml, pCss;
return {
compTypes: '',
parserCss: null,
parserHtml: null,
name: 'Parser',
init(config = {}) {
conf = { ...defaults, ...config };
conf.Parser = this;
pHtml = new parserHtml(conf);
pCss = new parserCss(conf);
this.em = conf.em;
this.parserCss = pCss;
this.parserHtml = pHtml;
return this;
},
/**
* Get the configuration object
* @returns {Object} Configuration object
* @example
* console.log(Parser.getConfig())
*/
getConfig() {
return conf;
},
/**
* Parse HTML string and return the object containing the Component Definition
* @param {String} input HTML string to parse
* @param {Object} [options] Options
* @param {String} [options.htmlType] [HTML mime type](https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString#Argument02) to parse
* @param {Boolean} [options.allowScripts=false] Allow `<script>` tags
* @param {Boolean} [options.allowUnsafeAttr=false] Allow unsafe HTML attributes (eg. `on*` inline event handlers)
* @returns {Object} Object containing the result `{ html: ..., css: ... }`
* @example
* const resHtml = Parser.parseHtml(`<table><div>Hi</div></table>`, {
* htmlType: 'text/html', // default
* });
* // By using the `text/html`, this will fix automatically all the HTML syntax issues
* // Indeed the final representation, in this case, will be `<div>Hi</div><table></table>`
* const resXml = Parser.parseHtml(`<table><div>Hi</div></table>`, {
* htmlType: 'application/xml',
* });
* // This will preserve the original format as, from the XML point of view, is a valid format
*/
parseHtml(input, options = {}) {
const { em, compTypes } = this;
pHtml.compTypes = em ? em.get('DomComponents').getTypes() : compTypes;
return pHtml.parse(input, pCss, options);
},
/**
* Parse CSS string and return an array of valid definition objects for CSSRules
* @param {String} input CSS string to parse
* @returns {Array<Object>} Array containing the result
* @example
* const res = Parser.parseCss('.cls { color: red }');
* // [{ ... }]
*/
parseCss(input) {
return pCss.parse(input);
},
destroy() {
[conf, pHtml, pCss].forEach(i => (i = {}));
['em', 'parserCss', 'parserHtml'].forEach(i => (this[i] = {}));
},
};
};

92
src/parser/index.ts

@ -0,0 +1,92 @@
/**
* You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object](https://github.com/artf/grapesjs/blob/master/src/parser/config/config.js)
* ```js
* const editor = grapesjs.init({
* parser: {
* // options
* }
* })
* ```
*
* Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance
*
* ```js
* const { Parser } = editor;
* ```
* ## Available Events
* * `parse:html` - On HTML parse, an object containing the input and the output of the parser is passed as an argument
* * `parse:css` - On CSS parse, an object containing the input and the output of the parser is passed as an argument
*
* ## Methods
* * [getConfig](#getconfig)
* * [parseHtml](#parsehtml)
* * [parseCss](#parsecss)
*
* @module Parser
*/
import { Module } from '../abstract';
import EditorModel from '../editor/model/Editor';
import defaults, { ParserConfig } from './config/config';
import parserCss from './model/ParserCss';
import parserHtml from './model/ParserHtml';
export default class ParserModule extends Module<ParserConfig & { name?: string }> {
parserHtml: ReturnType<typeof parserHtml>;
parserCss: ReturnType<typeof parserCss>;
constructor(em: EditorModel) {
super(em, 'Parser', defaults);
const { config } = this;
this.parserCss = parserCss(em, config);
this.parserHtml = parserHtml(em, config);
}
/**
* Get the configuration object
* @returns {Object} Configuration object
* @example
* console.log(Parser.getConfig())
*/
getConfig() {
return this.config;
}
/**
* Parse HTML string and return the object containing the Component Definition
* @param {String} input HTML string to parse
* @param {Object} [options] Options
* @param {String} [options.htmlType] [HTML mime type](https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString#Argument02) to parse
* @param {Boolean} [options.allowScripts=false] Allow `<script>` tags
* @param {Boolean} [options.allowUnsafeAttr=false] Allow unsafe HTML attributes (eg. `on*` inline event handlers)
* @returns {Object} Object containing the result `{ html: ..., css: ... }`
* @example
* const resHtml = Parser.parseHtml(`<table><div>Hi</div></table>`, {
* htmlType: 'text/html', // default
* });
* // By using the `text/html`, this will fix automatically all the HTML syntax issues
* // Indeed the final representation, in this case, will be `<div>Hi</div><table></table>`
* const resXml = Parser.parseHtml(`<table><div>Hi</div></table>`, {
* htmlType: 'application/xml',
* });
* // This will preserve the original format as, from the XML point of view, is a valid format
*/
parseHtml(input: string, options = {}) {
const { em, parserHtml } = this;
parserHtml.compTypes = (em.Components.getTypes() || {}) as any;
return parserHtml.parse(input, this.parserCss, options);
}
/**
* Parse CSS string and return an array of valid definition objects for CSSRules
* @param {String} input CSS string to parse
* @returns {Array<Object>} Array containing the result
* @example
* const res = Parser.parseCss('.cls { color: red }');
* // [{ ... }]
*/
parseCss(input: string) {
return this.parserCss.parse(input);
}
destroy() {}
}

7
src/parser/model/ParserCss.ts

@ -1,9 +1,10 @@
import { isString } from 'underscore'; import { isString } from 'underscore';
import { CssRuleProperties } from '../../css_composer/model/CssRule'; import { CssRuleProperties } from '../../css_composer/model/CssRule';
import EditorModel from '../../editor/model/Editor'; import EditorModel from '../../editor/model/Editor';
import { ParserConfig } from '../config/config';
import BrowserCssParser, { parseSelector, createNode } from './BrowserParserCss'; import BrowserCssParser, { parseSelector, createNode } from './BrowserParserCss';
export default (config: { parserCss?: any; em?: EditorModel } = {}) => ({ export default (em?: EditorModel, config: ParserConfig = {}) => ({
/** /**
* Parse CSS string to a desired model object * Parse CSS string to a desired model object
* @param {String} str CSS string * @param {String} str CSS string
@ -11,9 +12,9 @@ export default (config: { parserCss?: any; em?: EditorModel } = {}) => ({
*/ */
parse(str: string) { parse(str: string) {
let result: CssRuleProperties[] = []; let result: CssRuleProperties[] = [];
const { parserCss, em } = config; const { parserCss } = config;
const editor = em?.Editor; const editor = em?.Editor;
const nodes: CssRuleProperties[] = parserCss ? parserCss(str, editor) : BrowserCssParser(str); const nodes: CssRuleProperties[] = parserCss ? parserCss(str, editor!) : BrowserCssParser(str);
nodes.forEach(node => (result = result.concat(this.checkNode(node)))); nodes.forEach(node => (result = result.concat(this.checkNode(node))));
em?.trigger('parse:css', { input: str, output: result }); em?.trigger('parse:css', { input: str, output: result });

22
src/parser/model/ParserHtml.ts

@ -1,5 +1,7 @@
import { each, isString, isFunction, isUndefined } from 'underscore'; import { each, isString, isFunction, isUndefined } from 'underscore';
import { CssRuleProperties } from '../../css_composer/model/CssRule'; import { CssRuleProperties } from '../../css_composer/model/CssRule';
import EditorModel from '../../editor/model/Editor';
import { HTMLParserOptions, ParserConfig } from '../config/config';
import BrowserParserHtml from './BrowserParserHtml'; import BrowserParserHtml from './BrowserParserHtml';
type AnyObject = Record<string, any>; type AnyObject = Record<string, any>;
@ -11,11 +13,10 @@ type HTMLParseResult = {
css: null | CssRuleProperties[]; css: null | CssRuleProperties[];
}; };
export default (config: AnyObject = {}) => { const modelAttrStart = 'data-gjs-';
let c = config; const event = 'parse:html';
const modelAttrStart = 'data-gjs-';
const event = 'parse:html';
export default (em?: EditorModel, config: ParserConfig = {}) => {
return { return {
compTypes: '', compTypes: '',
@ -235,7 +236,7 @@ export default (config: AnyObject = {}) => {
} }
// Throw away empty nodes (keep spaces) // Throw away empty nodes (keep spaces)
if (!config.keepEmptyTextNodes) { if (!opts.keepEmptyTextNodes) {
const content = node.nodeValue; const content = node.nodeValue;
if (content != ' ' && !content!.trim()) { if (content != ' ' && !content!.trim()) {
continue; continue;
@ -259,7 +260,7 @@ export default (config: AnyObject = {}) => {
const comp = comps[ci]; const comp = comps[ci];
const cType = comp.type; const cType = comp.type;
if (['text', 'textnode'].indexOf(cType) < 0 && c.textTags.indexOf(comp.tagName) < 0) { if (['text', 'textnode'].indexOf(cType) < 0 && config.textTags!.indexOf(comp.tagName) < 0) {
allTxt = 0; allTxt = 0;
break; break;
} }
@ -291,14 +292,13 @@ export default (config: AnyObject = {}) => {
* @param {ParserCss} parserCss In case there is style tags inside HTML * @param {ParserCss} parserCss In case there is style tags inside HTML
* @return {Object} * @return {Object}
*/ */
parse(str: string, parserCss: any, opts = {}) { parse(str: string, parserCss: any, opts: HTMLParserOptions = {}) {
const { em } = c; const conf = em?.get('Config') || {};
const conf = (em && em.get('Config')) || {};
const res: HTMLParseResult = { html: null, css: null }; const res: HTMLParseResult = { html: null, css: null };
const cf: AnyObject = { ...config, ...opts }; const cf: AnyObject = { ...config, ...opts };
const options = { const options = {
...config.optionsHtml, ...config.optionsHtml,
// Support previous `configParser.htmlType` option // @ts-ignore Support previous `configParser.htmlType` option
htmlType: config.optionsHtml?.htmlType || config.htmlType, htmlType: config.optionsHtml?.htmlType || config.htmlType,
...opts, ...opts,
}; };
@ -336,7 +336,7 @@ export default (config: AnyObject = {}) => {
em && em.trigger(`${event}:root`, { input: str, root: el }); em && em.trigger(`${event}:root`, { input: str, root: el });
const result = this.parseNode(el, cf); const result = this.parseNode(el, cf);
// I have to keep it otherwise it breaks the DomComponents.addComponent (returns always array) // I have to keep it otherwise it breaks the DomComponents.addComponent (returns always array)
const resHtml = result.length === 1 && !c.returnArray ? result[0] : result; const resHtml = result.length === 1 && !cf.returnArray ? result[0] : result;
res.html = resHtml; res.html = resHtml;
em && em.trigger(event, { input: str, output: res }); em && em.trigger(event, { input: str, output: res });

Loading…
Cancel
Save