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 { IModule } from '../../abstract/Module';
import CanvasModule from '../../canvas';
import ComponentManager from '../../dom_components';
//@ts-ignore
Backbone.$ = $;
@ -109,6 +110,10 @@ export default class EditorModel extends Model {
return this.get('Editor');
}
get Components(): ComponentManager {
return this.get('DomComponents');
}
constructor(conf = {}) {
super();
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 {
/**
* DOMParser mime type.
@ -18,6 +21,12 @@ export interface HTMLParserOptions {
* @default false
*/
allowUnsafeAttr?: boolean;
/**
* When false, removes empty text nodes when parsed, unless they contain a space.
* @default false
*/
keepEmptyTextNodes?: boolean;
}
export interface ParserConfig {
@ -31,7 +40,7 @@ export interface ParserConfig {
* Custom CSS parser.
* @see https://grapesjs.com/docs/guides/Custom-CSS-parser.html
*/
parserCss?: any; // TODO
parserCss?: (str: string, editor: EditorModule) => CssRuleProperties[];
/**
* Custom HTML parser.
@ -52,12 +61,13 @@ export interface ParserConfig {
const config: ParserConfig = {
textTags: ['br', 'b', 'i', 'u', 'a', 'ul', 'ol'],
parserCss: null,
parserCss: undefined,
parserHtml: null,
optionsHtml: {
htmlType: 'text/html',
allowScripts: 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 { CssRuleProperties } from '../../css_composer/model/CssRule';
import EditorModel from '../../editor/model/Editor';
import { ParserConfig } from '../config/config';
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
* @param {String} str CSS string
@ -11,9 +12,9 @@ export default (config: { parserCss?: any; em?: EditorModel } = {}) => ({
*/
parse(str: string) {
let result: CssRuleProperties[] = [];
const { parserCss, em } = config;
const { parserCss } = config;
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))));
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 { CssRuleProperties } from '../../css_composer/model/CssRule';
import EditorModel from '../../editor/model/Editor';
import { HTMLParserOptions, ParserConfig } from '../config/config';
import BrowserParserHtml from './BrowserParserHtml';
type AnyObject = Record<string, any>;
@ -11,11 +13,10 @@ type HTMLParseResult = {
css: null | CssRuleProperties[];
};
export default (config: AnyObject = {}) => {
let c = config;
const modelAttrStart = 'data-gjs-';
const event = 'parse:html';
const modelAttrStart = 'data-gjs-';
const event = 'parse:html';
export default (em?: EditorModel, config: ParserConfig = {}) => {
return {
compTypes: '',
@ -235,7 +236,7 @@ export default (config: AnyObject = {}) => {
}
// Throw away empty nodes (keep spaces)
if (!config.keepEmptyTextNodes) {
if (!opts.keepEmptyTextNodes) {
const content = node.nodeValue;
if (content != ' ' && !content!.trim()) {
continue;
@ -259,7 +260,7 @@ export default (config: AnyObject = {}) => {
const comp = comps[ci];
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;
break;
}
@ -291,14 +292,13 @@ export default (config: AnyObject = {}) => {
* @param {ParserCss} parserCss In case there is style tags inside HTML
* @return {Object}
*/
parse(str: string, parserCss: any, opts = {}) {
const { em } = c;
const conf = (em && em.get('Config')) || {};
parse(str: string, parserCss: any, opts: HTMLParserOptions = {}) {
const conf = em?.get('Config') || {};
const res: HTMLParseResult = { html: null, css: null };
const cf: AnyObject = { ...config, ...opts };
const options = {
...config.optionsHtml,
// Support previous `configParser.htmlType` option
// @ts-ignore Support previous `configParser.htmlType` option
htmlType: config.optionsHtml?.htmlType || config.htmlType,
...opts,
};
@ -336,7 +336,7 @@ export default (config: AnyObject = {}) => {
em && em.trigger(`${event}:root`, { input: str, root: el });
const result = this.parseNode(el, cf);
// 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;
em && em.trigger(event, { input: str, output: res });

Loading…
Cancel
Save