diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 160dc7cab..e4bb0c66b 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -73,10 +73,12 @@ module.exports = { ['/api/device_manager', 'Device Manager'], ['/api/selector_manager', 'Selector Manager'], ['/api/css_composer', 'CSS Composer'], + ['/api/css_rule', `${subDivider}CssRule`], ['/api/modal_dialog', 'Modal'], ['/api/rich_text_editor', 'Rich Text Editor'], ['/api/keymaps', 'Keymaps'], ['/api/undo_manager', 'Undo Manager'], + ['/api/parser', 'Parser'], ], '/': [ '', diff --git a/docs/api.js b/docs/api.js index 15b9a5b74..ab7c48df5 100644 --- a/docs/api.js +++ b/docs/api.js @@ -17,6 +17,7 @@ const cmds = [ ['device_manager/index.js', 'device_manager.md'], ['selector_manager/index.js', 'selector_manager.md'], ['css_composer/index.js', 'css_composer.md'], + ['css_composer/model/CssRule.js', 'css_rule.md'], ['modal_dialog/index.js', 'modal_dialog.md'], ['rich_text_editor/index.js', 'rich_text_editor.md'], ['keymaps/index.js', 'keymaps.md'], @@ -25,6 +26,7 @@ const cmds = [ ['i18n/index.js', 'i18n.md'], ['pages/index.js', 'pages.md'], ['pages/model/Page.js', 'page.md'], + ['parser/index.js', 'parser.md'], ].map(entry => `${binRoot}documentation build ${srcRoot}/${entry[0]} -o ${docRoot}/api/${entry[1]} -f md --shallow --markdown-toc false`) .join(' && '); diff --git a/docs/api/assets.md b/docs/api/assets.md index 0f5e41b0d..f18d14d62 100644 --- a/docs/api/assets.md +++ b/docs/api/assets.md @@ -160,8 +160,8 @@ Render assets ### Parameters -* `assets` **[array][16]** Assets to render, without the argument will render - all global assets +* `assts` +* `assets` **[array][16]** Assets to render, without the argument will render all global assets ### Examples diff --git a/docs/api/component.md b/docs/api/component.md index 8f89c97ae..3412301d1 100644 --- a/docs/api/component.md +++ b/docs/api/component.md @@ -2,6 +2,8 @@ ## Component +**Extends Model.extend(Styleable)** + The Component object represents a single node of our template structure, so when you update its properties the changes are immediately reflected on the canvas and in the code to export (indeed, when you ask to export the code we just go through all the tree of nodes). diff --git a/docs/api/css_rule.md b/docs/api/css_rule.md new file mode 100644 index 000000000..d7ca0daf4 --- /dev/null +++ b/docs/api/css_rule.md @@ -0,0 +1,83 @@ + + +## getAtRule + +Return the at-rule statement when exists, eg. '@media (...)', '@keyframes' + +### Examples + +```javascript +const cssRule = editor.Css.setRule('.class1', { color: 'red' }, { + atRuleType: 'media', + atRuleParams: '(min-width: 500px)' +}); +cssRule.getAtRule(); // "@media (min-width: 500px)" +``` + +Returns **[String][1]** + +## selectorsToString + +Return selectors of the rule as a string + +### Parameters + +* `opts` **[Object][2]?** Options (optional, default `{}`) + + * `opts.skipState` **[Boolean][3]?** Skip state from the result + +### Examples + +```javascript +const cssRule = editor.Css.setRule('.class1:hover', { color: 'red' }); +cssRule.selectorsToString(); // ".class1:hover" +cssRule.selectorsToString({ skipState: true }); // ".class1" +``` + +Returns **[String][1]** + +## getDeclaration + +Get declaration block (without the at-rule statement) + +### Parameters + +* `opts` **[Object][2]** Options (same as in `selectorsToString`) (optional, default `{}`) + +### Examples + +```javascript +const cssRule = editor.Css.setRule('.class1', { color: 'red' }, { + atRuleType: 'media', + atRuleParams: '(min-width: 500px)' +}); +cssRule.getDeclaration() // ".class1{color:red;}" +``` + +Returns **[String][1]** + +## toCSS + +Return the CSS string of the rule + +### Parameters + +* `opts` **[Object][2]** Options (same as in `getDeclaration`) (optional, default `{}`) + +### Examples + +```javascript +const cssRule = editor.Css.setRule('.class1', { color: 'red' }, { + atRuleType: 'media', + atRuleParams: '(min-width: 500px)' +}); +cssRule.toCSS() // "@media (min-width: 500px){.class1{color:red;}}" +``` + +Returns **[String][1]** CSS string + +[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String + +[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object + +[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean diff --git a/docs/api/editor.md b/docs/api/editor.md index d28f43d32..75da2bfef 100644 --- a/docs/api/editor.md +++ b/docs/api/editor.md @@ -113,11 +113,6 @@ By changing `result.content` you're able to customize what is dropped * `modal:open` - Modal is opened * `modal:close` - Modal is closed -### Parser - -* `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 - ### Commands * `run:{commandName}` - Triggered when some command is called to run (eg. editor.runCommand('preview')) @@ -128,9 +123,13 @@ By changing `result.content` you're able to customize what is dropped * `run` - Triggered on run of any command. The id and the result are passed as arguments to the callback * `stop` - Triggered on stop of any command. The id and the result are passed as arguments to the callback +### Parser + +Check the [Parser][2] module. + ### Pages -Check the [Pages][2] module. +Check the [Pages][3] module. ### General @@ -146,7 +145,7 @@ Returns configuration object ### Parameters -* `prop` **[string][3]?** Property name +* `prop` **[string][4]?** Property name Returns **any** Returns the configuration object or the value of the specified property @@ -157,11 +156,12 @@ Returns HTML built inside canvas ### Parameters -* `opts` **[Object][4]** Options (optional, default `{}`) +* `opts` **[Object][5]** Options (optional, default `{}`) - * `opts.cleanId` **[Boolean][5]** Remove unnecessary IDs (eg. those created automatically) (optional, default `false`) + * `opts.component` **Component?** Return the HTML of a specific Component + * `opts.cleanId` **[Boolean][6]** Remove unnecessary IDs (eg. those created automatically) (optional, default `false`) -Returns **[string][3]** HTML string +Returns **[string][4]** HTML string ## getCss @@ -169,11 +169,13 @@ Returns CSS built inside canvas ### Parameters -* `opts` **[Object][4]** Options (optional, default `{}`) +* `opts` **[Object][5]** Options (optional, default `{}`) - * `opts.avoidProtected` **[Boolean][5]** Don't include protected CSS (optional, default `false`) + * `opts.component` **Component?** Return the CSS of a specific Component + * `opts.json` **[Boolean][6]** Return an array of CssRules instead of the CSS string (optional, default `false`) + * `opts.avoidProtected` **[Boolean][6]** Don't include protected CSS (optional, default `false`) -Returns **[string][3]** CSS string +Returns **([String][4] | [Array][7]\)** CSS string or array of CssRules ## getJs @@ -181,11 +183,11 @@ Returns JS of all components ### Parameters -* `opts` **[Object][4]** Options (optional, default `{}`) +* `opts` **[Object][5]** Options (optional, default `{}`) - * `opts.component` **Component?** Get the JS of a particular component + * `opts.component` **Component?** Get the JS of a specific component -Returns **[string][3]** JS string +Returns **[String][4]** JS string ## getComponents @@ -205,8 +207,8 @@ Set components inside editor's canvas. This method overrides actual components ### Parameters -* `components` **([Array][6]<[Object][4]> | [Object][4] | [string][3])** HTML string or components model -* `opt` **[Object][4]** the options object to be used by the \[setComponents][em#setComponents][7] method (optional, default `{}`) +* `components` **([Array][7]<[Object][5]> | [Object][5] | [string][4])** HTML string or components model +* `opt` **[Object][5]** the options object to be used by the \[setComponents][em#setComponents][8] method (optional, default `{}`) ### Examples @@ -228,10 +230,10 @@ Add components ### Parameters -* `components` **([Array][6]<[Object][4]> | [Object][4] | [string][3])** HTML string or components model -* `opts` **[Object][4]** Options +* `components` **([Array][7]<[Object][5]> | [Object][5] | [string][4])** HTML string or components model +* `opts` **[Object][5]** Options - * `opts.avoidUpdateStyle` **[Boolean][5]** If the HTML string contains styles, + * `opts.avoidUpdateStyle` **[Boolean][6]** If the HTML string contains styles, by default, they will be created and, if already exist, updated. When this option is true, styles already created will not be updated. (optional, default `false`) @@ -247,13 +249,13 @@ editor.addComponents({ }); ``` -Returns **[Array][6]\** +Returns **[Array][7]\** ## getStyle Returns style in JSON format object -Returns **[Object][4]** +Returns **[Object][5]** ## setStyle @@ -261,7 +263,7 @@ Set style inside editor's canvas. This method overrides actual style ### Parameters -* `style` **([Array][6]<[Object][4]> | [Object][4] | [string][3])** CSS string or style model +* `style` **([Array][7]<[Object][5]> | [Object][5] | [string][4])** CSS string or style model * `opt` (optional, default `{}`) ### Examples @@ -283,7 +285,7 @@ Add styles to the editor ### Parameters -* `style` **([Array][6]<[Object][4]> | [Object][4] | [string][3])** CSS string or style model +* `style` **([Array][7]<[Object][5]> | [Object][5] | [string][4])** CSS string or style model * `opts` (optional, default `{}`) ### Examples @@ -292,7 +294,7 @@ Add styles to the editor editor.addStyle('.cls{color: red}'); ``` -Returns **[Array][6]\** Array of created CssRule instances +Returns **[Array][7]\** Array of created CssRule instances ## getSelected @@ -304,7 +306,7 @@ Returns **Model** Returns an array of all selected components -Returns **[Array][6]** +Returns **[Array][7]** ## getSelectedToStyle @@ -322,10 +324,10 @@ Select a component ### Parameters -* `el` **(Component | [HTMLElement][8])** Component to select -* `opts` **[Object][4]?** Options +* `el` **(Component | [HTMLElement][9])** Component to select +* `opts` **[Object][5]?** Options - * `opts.scroll` **[Boolean][5]?** Scroll canvas to the selected element + * `opts.scroll` **[Boolean][6]?** Scroll canvas to the selected element ### Examples @@ -344,7 +346,7 @@ Add component to selection ### Parameters -* `el` **(Component | [HTMLElement][8] | [Array][6])** Component to select +* `el` **(Component | [HTMLElement][9] | [Array][7])** Component to select ### Examples @@ -360,7 +362,7 @@ Remove component from selection ### Parameters -* `el` **(Component | [HTMLElement][8] | [Array][6])** Component to select +* `el` **(Component | [HTMLElement][9] | [Array][7])** Component to select ### Examples @@ -376,7 +378,7 @@ Toggle component selection ### Parameters -* `el` **(Component | [HTMLElement][8] | [Array][6])** Component to select +* `el` **(Component | [HTMLElement][9] | [Array][7])** Component to select ### Examples @@ -408,7 +410,7 @@ change the canvas to the proper width ### Parameters -* `name` **[string][3]** Name of the device +* `name` **[string][4]** Name of the device ### Examples @@ -430,7 +432,7 @@ console.log(device); // 'Tablet' ``` -Returns **[string][3]** Device name +Returns **[string][4]** Device name ## runCommand @@ -438,8 +440,8 @@ Execute command ### Parameters -* `id` **[string][3]** Command ID -* `options` **[Object][4]** Custom options (optional, default `{}`) +* `id` **[string][4]** Command ID +* `options` **[Object][5]** Custom options (optional, default `{}`) ### Examples @@ -455,8 +457,8 @@ Stop the command if stop method was provided ### Parameters -* `id` **[string][3]** Command ID -* `options` **[Object][4]** Custom options (optional, default `{}`) +* `id` **[string][4]** Command ID +* `options` **[Object][5]** Custom options (optional, default `{}`) ### Examples @@ -472,9 +474,22 @@ Store data to the current storage ### Parameters -* `clb` **[Function][9]** Callback function +* `clb` **[Function][10]** Callback function + +Returns **[Object][5]** Stored data + +## storeData + +Get the JSON data object, which could be stored and loaded back with `editor.loadData(json)` + +### Examples + +```javascript +console.log(editor.storeData()); +// { pages: [...], styles: [...], ... } +``` -Returns **[Object][4]** Stored data +Returns **[Object][5]** ## load @@ -482,23 +497,39 @@ Load data from the current storage ### Parameters -* `clb` **[Function][9]** Callback function +* `clb` **[Function][10]** Callback function + +Returns **[Object][5]** Stored data -Returns **[Object][4]** Stored data +## loadData + +Load data from the JSON data object + +### Parameters + +* `data` **[Object][5]** Data to load + +### Examples + +```javascript +editor.loadData({ pages: [...], styles: [...], ... }) +``` + +Returns **[Object][5]** Loaded object ## getContainer Returns container element. The one which was indicated as 'container' on init method -Returns **[HTMLElement][8]** +Returns **[HTMLElement][9]** ## getDirtyCount Return the count of changes made to the content and not yet stored. This count resets at any `store()` -Returns **[number][10]** +Returns **[number][11]** ## refresh @@ -511,9 +542,9 @@ refresh you'll get misleading position of tools ### Parameters * `opts` -* `options` **[Object][4]?** Options +* `options` **[Object][5]?** Options - * `options.tools` **[Boolean][5]** Update the position of tools (eg. rich text editor, component highlighter, etc.) (optional, default `false`) + * `options.tools` **[Boolean][6]** Update the position of tools (eg. rich text editor, component highlighter, etc.) (optional, default `false`) ## setCustomRte @@ -521,7 +552,7 @@ Replace the built-in Rich Text Editor with a custom one. ### Parameters -* `obj` **[Object][4]** Custom RTE Interface +* `obj` **[Object][5]** Custom RTE Interface ### Examples @@ -561,7 +592,7 @@ custom parser, pass `null` as the argument ### Parameters -* `parser` **([Function][9] | null)** Parser function +* `parser` **([Function][10] | null)** Parser function ### Examples @@ -583,11 +614,11 @@ Returns **this** ## setDragMode Change the global drag mode of components. -To get more about this feature read: [https://github.com/artf/grapesjs/issues/1936][11] +To get more about this feature read: [https://github.com/artf/grapesjs/issues/1936][12] ### Parameters -* `value` **[String][3]** Drag mode, options: 'absolute' | 'translate' +* `value` **[String][4]** Drag mode, options: 'absolute' | 'translate' Returns **this** @@ -598,10 +629,10 @@ Trigger event log message ### Parameters * `msg` **any** Message to log -* `opts` **[Object][4]** Custom options (optional, default `{}`) +* `opts` **[Object][5]** Custom options (optional, default `{}`) - * `opts.ns` **[String][3]** Namespace of the log (eg. to use in plugins) (optional, default `''`) - * `opts.level` **[String][3]** Level of the log, `debug`, `info`, `warning`, `error` (optional, default `'debug'`) + * `opts.ns` **[String][4]** Namespace of the log (eg. to use in plugins) (optional, default `''`) + * `opts.level` **[String][4]** Level of the log, `debug`, `info`, `warning`, `error` (optional, default `'debug'`) ### Examples @@ -623,11 +654,11 @@ Translate label ### Parameters * `args` **...any** -* `key` **[String][3]** Label to translate -* `opts` **[Object][4]?** Options for the translation +* `key` **[String][4]** Label to translate +* `opts` **[Object][5]?** Options for the translation - * `opts.params` **[Object][4]?** Params for the translation - * `opts.noWarn` **[Boolean][5]?** Avoid warnings in case of missing resources + * `opts.params` **[Object][5]?** Params for the translation + * `opts.noWarn` **[Boolean][6]?** Avoid warnings in case of missing resources ### Examples @@ -639,7 +670,7 @@ editor.t('msg2', { params: { test: 'hello' } }); editor.t('msg2', { params: { test: 'hello' }, l: 'it' }); ``` -Returns **[String][3]** +Returns **[String][4]** ## on @@ -647,8 +678,8 @@ Attach event ### Parameters -* `event` **[string][3]** Event name -* `callback` **[Function][9]** Callback function +* `event` **[string][4]** Event name +* `callback` **[Function][10]** Callback function Returns **this** @@ -658,8 +689,8 @@ Attach event and detach it after the first run ### Parameters -* `event` **[string][3]** Event name -* `callback` **[Function][9]** Callback function +* `event` **[string][4]** Event name +* `callback` **[Function][10]** Callback function Returns **this** @@ -669,8 +700,8 @@ Detach event ### Parameters -* `event` **[string][3]** Event name -* `callback` **[Function][9]** Callback function +* `event` **[string][4]** Event name +* `callback` **[Function][10]** Callback function Returns **this** @@ -680,7 +711,7 @@ Trigger event ### Parameters -* `event` **[string][3]** Event to trigger +* `event` **[string][4]** Event to trigger Returns **this** @@ -692,7 +723,7 @@ Destroy the editor Render editor -Returns **[HTMLElement][8]** +Returns **[HTMLElement][9]** ## html @@ -700,8 +731,8 @@ Print safe HTML by using ES6 tagged template strings. ### Parameters -* `literals` **[Array][6]<[String][3]>** -* `substs` **[Array][6]<[String][3]>** +* `literals` **[Array][7]<[String][4]>** +* `substs` **[Array][7]<[String][4]>** ### Examples @@ -712,26 +743,28 @@ const safeStr = 'Hello'; const strHtml = editor.html`Escaped ${unsafeStr}, unescaped $${safeStr}`; ``` -Returns **[String][3]** +Returns **[String][4]** [1]: https://github.com/artf/grapesjs/blob/master/src/editor/config/config.js -[2]: /api/pages.html +[2]: /api/parser.html + +[3]: /api/pages.html -[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String +[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String -[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object +[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object -[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean +[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean -[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array +[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array -[7]: em#setComponents +[8]: em#setComponents -[8]: https://developer.mozilla.org/docs/Web/HTML/Element +[9]: https://developer.mozilla.org/docs/Web/HTML/Element -[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function +[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function -[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number +[11]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number -[11]: https://github.com/artf/grapesjs/issues/1936 +[12]: https://github.com/artf/grapesjs/issues/1936 diff --git a/docs/api/parser.md b/docs/api/parser.md new file mode 100644 index 000000000..20ef335d1 --- /dev/null +++ b/docs/api/parser.md @@ -0,0 +1,102 @@ + + +## Parser + +You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object][1] + +```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][2] +* [parseHtml][3] +* [parseCss][4] + +## getConfig + +Get the configuration object + +### Examples + +```javascript +console.log(Parser.getConfig()) +``` + +Returns **[Object][5]** Configuration object + +## parseHtml + +Parse HTML string and return the object containing the Component Definition + +### Parameters + +* `input` **[String][6]** HTML string to parse +* `options` **[Object][5]?** Options (optional, default `{}`) + + * `options.htmlType` **[String][6]?** [HTML mime type][7] to parse + +### Examples + +```javascript +const resHtml = Parser.parseHtml(`
Hi
`, { + 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 `
Hi
` +const resXml = Parser.parseHtml(`
Hi
`, { + htmlType: 'application/xml', +}); +// This will preserve the original format as, from the XML point of view, is a valid format +``` + +Returns **[Object][5]** Object containing the result `{ html: ..., css: ... }` + +## parseCss + +Parse CSS string and return an array of valid definition objects for CSSRules + +### Parameters + +* `input` **[String][6]** CSS string to parse + +### Examples + +```javascript +const res = Parser.parseCss('.cls { color: red }'); +// [{ ... }] +``` + +Returns **[Array][8]<[Object][5]>** Array containing the result + +[1]: https://github.com/artf/grapesjs/blob/master/src/parser/config/config.js + +[2]: #getconfig + +[3]: #parsehtml + +[4]: #parsecss + +[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object + +[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String + +[7]: https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString#Argument02 + +[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array diff --git a/src/css_composer/model/CssRule.js b/src/css_composer/model/CssRule.js index 1607dad77..f1f331c3f 100644 --- a/src/css_composer/model/CssRule.js +++ b/src/css_composer/model/CssRule.js @@ -88,8 +88,14 @@ export default class CssRule extends Model.extend(Styleable) { } /** - * Returns an at-rule statement if possible, eg. '@media (...)', '@keyframes' - * @return {string} + * Return the at-rule statement when exists, eg. '@media (...)', '@keyframes' + * @returns {String} + * @example + * const cssRule = editor.Css.setRule('.class1', { color: 'red' }, { + * atRuleType: 'media', + * atRuleParams: '(min-width: 500px)' + * }); + * cssRule.getAtRule(); // "@media (min-width: 500px)" */ getAtRule() { const type = this.get('atRuleType'); @@ -101,8 +107,14 @@ export default class CssRule extends Model.extend(Styleable) { } /** - * Return selectors fo the rule as a string - * @return {string} + * Return selectors of the rule as a string + * @param {Object} [opts] Options + * @param {Boolean} [opts.skipState] Skip state from the result + * @returns {String} + * @example + * const cssRule = editor.Css.setRule('.class1:hover', { color: 'red' }); + * cssRule.selectorsToString(); // ".class1:hover" + * cssRule.selectorsToString({ skipState: true }); // ".class1" */ selectorsToString(opts = {}) { const result = []; @@ -123,9 +135,15 @@ export default class CssRule extends Model.extend(Styleable) { } /** - * Get declaration block - * @param {Object} [opts={}] Options - * @return {string} + * Get declaration block (without the at-rule statement) + * @param {Object} [opts={}] Options (same as in `selectorsToString`) + * @returns {String} + * @example + * const cssRule = editor.Css.setRule('.class1', { color: 'red' }, { + * atRuleType: 'media', + * atRuleParams: '(min-width: 500px)' + * }); + * cssRule.getDeclaration() // ".class1{color:red;}" */ getDeclaration(opts = {}) { let result = ''; @@ -141,9 +159,15 @@ export default class CssRule extends Model.extend(Styleable) { } /** - * Returns CSS string of the rule - * @param {Object} [opts={}] Options - * @return {string} + * Return the CSS string of the rule + * @param {Object} [opts={}] Options (same as in `getDeclaration`) + * @return {String} CSS string + * @example + * const cssRule = editor.Css.setRule('.class1', { color: 'red' }, { + * atRuleType: 'media', + * atRuleParams: '(min-width: 500px)' + * }); + * cssRule.toCSS() // "@media (min-width: 500px){.class1{color:red;}}" */ toCSS(opts = {}) { let result = ''; diff --git a/src/dom_components/config/config.js b/src/dom_components/config/config.js index 9aa765508..a14506818 100644 --- a/src/dom_components/config/config.js +++ b/src/dom_components/config/config.js @@ -35,7 +35,8 @@ export default { */ processor: 0, - // List of void elements + // List of HTML void elements + // https://www.w3.org/TR/2011/WD-html-markup-20110113/syntax.html#void-elements voidElements: [ 'area', 'base', diff --git a/src/dom_components/model/Component.js b/src/dom_components/model/Component.js index fc1ce726f..67720dfa7 100644 --- a/src/dom_components/model/Component.js +++ b/src/dom_components/model/Component.js @@ -11,7 +11,13 @@ import { bindAll, keys } from 'underscore'; -import { shallowDiff, capitalize, isEmptyObj, isObject } from 'utils/mixins'; +import { + shallowDiff, + capitalize, + isEmptyObj, + isObject, + toLowerCase +} from 'utils/mixins'; import Styleable from 'domain_abstract/model/Styleable'; import { Model } from 'backbone'; import Components from './Components'; @@ -1736,7 +1742,7 @@ export default class Component extends Model.extend(Styleable) { * @private */ Component.isComponent = el => { - return { tagName: el.tagName ? el.tagName.toLowerCase() : '' }; + return { tagName: toLowerCase(el.tagName) }; }; Component.ensureInList = model => { diff --git a/src/dom_components/model/ComponentFrame.js b/src/dom_components/model/ComponentFrame.js index da06b0259..b7098bbcb 100644 --- a/src/dom_components/model/ComponentFrame.js +++ b/src/dom_components/model/ComponentFrame.js @@ -1,4 +1,5 @@ import Component from './Component'; +import { toLowerCase } from 'utils/mixins'; const type = 'iframe'; @@ -17,6 +18,6 @@ export default Component.extend( } }, { - isComponent: el => el.tagName === 'IFRAME' + isComponent: el => toLowerCase(el.tagName) === type } ); diff --git a/src/dom_components/model/ComponentImage.js b/src/dom_components/model/ComponentImage.js index 5f6cdfe71..9596272eb 100644 --- a/src/dom_components/model/ComponentImage.js +++ b/src/dom_components/model/ComponentImage.js @@ -1,5 +1,6 @@ import { result } from 'underscore'; import Component from './Component'; +import { toLowerCase } from 'utils/mixins'; const svgAttrs = 'xmlns="http://www.w3.org/2000/svg" width="100" viewBox="0 0 24 24" style="fill: rgba(0,0,0,0.15); transform: scale(0.75)"'; @@ -138,20 +139,6 @@ export default Component.extend( } }, { - /** - * Detect if the passed element is a valid component. - * In case the element is valid an object abstracted - * from the element will be returned - * @param {HTMLElement} - * @return {Object} - * @private - */ - isComponent(el) { - var result = ''; - if (el.tagName == 'IMG') { - result = { type: 'image' }; - } - return result; - } + isComponent: el => toLowerCase(el.tagName) === 'img' } ); diff --git a/src/dom_components/model/ComponentLabel.js b/src/dom_components/model/ComponentLabel.js index f51095ed0..7df2cd773 100644 --- a/src/dom_components/model/ComponentLabel.js +++ b/src/dom_components/model/ComponentLabel.js @@ -1,18 +1,18 @@ import Component from './ComponentText'; +import { toLowerCase } from 'utils/mixins'; + +const type = 'label'; export default Component.extend( { defaults: { ...Component.prototype.defaults, - tagName: 'label', + type, + tagName: type, traits: ['id', 'title', 'for'] } }, { - isComponent(el) { - if (el.tagName == 'LABEL') { - return { type: 'label' }; - } - } + isComponent: el => toLowerCase(el.tagName) === type } ); diff --git a/src/dom_components/model/ComponentLink.js b/src/dom_components/model/ComponentLink.js index e9e559610..3933b670e 100644 --- a/src/dom_components/model/ComponentLink.js +++ b/src/dom_components/model/ComponentLink.js @@ -1,4 +1,5 @@ import Component from './ComponentText'; +import { toLowerCase } from 'utils/mixins'; export default Component.extend( { @@ -23,9 +24,8 @@ export default Component.extend( { isComponent(el) { let result; - let avoidEdit; - if (el.tagName == 'A') { + if (toLowerCase(el.tagName) === 'a') { result = { type: 'link', editable: 0 diff --git a/src/dom_components/model/ComponentMap.js b/src/dom_components/model/ComponentMap.js index 6dd1c59de..b597d049a 100644 --- a/src/dom_components/model/ComponentMap.js +++ b/src/dom_components/model/ComponentMap.js @@ -1,5 +1,6 @@ import Component from './ComponentImage'; import OComponent from './Component'; +import { toLowerCase } from 'utils/mixins'; export default Component.extend( { @@ -100,7 +101,10 @@ export default Component.extend( */ isComponent(el) { var result = ''; - if (el.tagName == 'IFRAME' && /maps\.google\.com/.test(el.src)) { + if ( + toLowerCase(el.tagName) == 'iframe' && + /maps\.google\.com/.test(el.src) + ) { result = { type: 'map', src: el.src }; } return result; diff --git a/src/dom_components/model/ComponentScript.js b/src/dom_components/model/ComponentScript.js index 8eb17b238..483a2dab0 100644 --- a/src/dom_components/model/ComponentScript.js +++ b/src/dom_components/model/ComponentScript.js @@ -1,4 +1,5 @@ import Component from './Component'; +import { toLowerCase } from 'utils/mixins'; const type = 'script'; @@ -15,7 +16,7 @@ export default Component.extend( }, { isComponent(el) { - if (el.tagName == 'SCRIPT') { + if (toLowerCase(el.tagName) == type) { const result = { type }; if (el.src) { diff --git a/src/dom_components/model/ComponentSvg.js b/src/dom_components/model/ComponentSvg.js index 6f1bdd988..1d7714c2b 100644 --- a/src/dom_components/model/ComponentSvg.js +++ b/src/dom_components/model/ComponentSvg.js @@ -1,11 +1,16 @@ import Component from './Component'; +import { toLowerCase } from 'utils/mixins'; + +const type = 'svg'; export default Component.extend( { defaults: { ...Component.prototype.defaults, - resizable: { ratioDefault: 1 }, - highlightable: 0 + type, + tagName: type, + highlightable: 0, + resizable: { ratioDefault: 1 } }, getName() { @@ -16,13 +21,6 @@ export default Component.extend( } }, { - isComponent(el) { - if (SVGElement && el instanceof SVGElement) { - return { - tagName: el.tagName, - type: 'svg' - }; - } - } + isComponent: el => toLowerCase(el.tagName) === type } ); diff --git a/src/dom_components/model/ComponentSvgIn.js b/src/dom_components/model/ComponentSvgIn.js index dad3385f8..f10a40bd7 100644 --- a/src/dom_components/model/ComponentSvgIn.js +++ b/src/dom_components/model/ComponentSvgIn.js @@ -13,13 +13,6 @@ export default Component.extend( } }, { - isComponent(el) { - if (Component.isComponent(el) && el.tagName.toLowerCase() !== 'svg') { - return { - tagName: el.tagName, - type: 'svg-in' - }; - } - } + isComponent: (el, opts = {}) => !!opts.inSvg } ); diff --git a/src/dom_components/model/ComponentTable.js b/src/dom_components/model/ComponentTable.js index 79573e56d..220d13f2c 100644 --- a/src/dom_components/model/ComponentTable.js +++ b/src/dom_components/model/ComponentTable.js @@ -1,11 +1,14 @@ import Component from './Component'; +import { toLowerCase } from 'utils/mixins'; + +const type = 'table'; export default Component.extend( { defaults: { ...Component.prototype.defaults, - type: 'table', - tagName: 'table', + type, + tagName: type, droppable: ['tbody', 'thead', 'tfoot'] }, @@ -16,14 +19,6 @@ export default Component.extend( } }, { - isComponent(el) { - let result = ''; - - if (el.tagName == 'TABLE') { - result = { type: 'table' }; - } - - return result; - } + isComponent: el => toLowerCase(el.tagName) === type } ); diff --git a/src/dom_components/model/ComponentTableBody.js b/src/dom_components/model/ComponentTableBody.js index dfb77e658..f3cd41103 100644 --- a/src/dom_components/model/ComponentTableBody.js +++ b/src/dom_components/model/ComponentTableBody.js @@ -1,11 +1,14 @@ import Component from './Component'; +import { toLowerCase } from 'utils/mixins'; + +const type = 'tbody'; export default Component.extend( { defaults: { ...Component.prototype.defaults, - type: 'tbody', - tagName: 'tbody', + type, + tagName: type, draggable: ['table'], droppable: ['tr'], columns: 1, @@ -45,14 +48,6 @@ export default Component.extend( } }, { - isComponent(el) { - let result = ''; - - if (el.tagName == 'TBODY') { - result = { type: 'tbody' }; - } - - return result; - } + isComponent: el => toLowerCase(el.tagName) === type } ); diff --git a/src/dom_components/model/ComponentTableCell.js b/src/dom_components/model/ComponentTableCell.js index 9fa1024bf..3fe597e89 100644 --- a/src/dom_components/model/ComponentTableCell.js +++ b/src/dom_components/model/ComponentTableCell.js @@ -1,4 +1,5 @@ import Component from './Component'; +import { toLowerCase } from 'utils/mixins'; export default Component.extend( { @@ -10,18 +11,6 @@ export default Component.extend( } }, { - isComponent(el) { - let result = ''; - const tag = el.tagName; - - if (tag == 'TD' || tag == 'TH') { - result = { - type: 'cell', - tagName: tag.toLowerCase() - }; - } - - return result; - } + isComponent: el => ['td', 'th'].indexOf(toLowerCase(el.tagName)) >= 0 } ); diff --git a/src/dom_components/model/ComponentTableFoot.js b/src/dom_components/model/ComponentTableFoot.js index e7ae0a9f1..861224a7b 100644 --- a/src/dom_components/model/ComponentTableFoot.js +++ b/src/dom_components/model/ComponentTableFoot.js @@ -1,22 +1,17 @@ import ComponentTableBody from './ComponentTableBody'; +import { toLowerCase } from 'utils/mixins'; + +const type = 'tfoot'; export default ComponentTableBody.extend( { defaults: { ...ComponentTableBody.prototype.defaults, - type: 'tfoot', - tagName: 'tfoot' + type, + tagName: type } }, { - isComponent(el) { - let result = ''; - - if (el.tagName == 'TFOOT') { - result = { type: 'tfoot' }; - } - - return result; - } + isComponent: el => toLowerCase(el.tagName) === type } ); diff --git a/src/dom_components/model/ComponentTableHead.js b/src/dom_components/model/ComponentTableHead.js index f1f82626f..070479da6 100644 --- a/src/dom_components/model/ComponentTableHead.js +++ b/src/dom_components/model/ComponentTableHead.js @@ -1,22 +1,17 @@ import ComponentTableBody from './ComponentTableBody'; +import { toLowerCase } from 'utils/mixins'; + +const type = 'thead'; export default ComponentTableBody.extend( { defaults: { ...ComponentTableBody.prototype.defaults, - type: 'thead', - tagName: 'thead' + type, + tagName: type } }, { - isComponent(el) { - let result = ''; - - if (el.tagName == 'THEAD') { - result = { type: 'thead' }; - } - - return result; - } + isComponent: el => toLowerCase(el.tagName) === type } ); diff --git a/src/dom_components/model/ComponentTableRow.js b/src/dom_components/model/ComponentTableRow.js index d0f25beaa..50c796edc 100644 --- a/src/dom_components/model/ComponentTableRow.js +++ b/src/dom_components/model/ComponentTableRow.js @@ -1,15 +1,18 @@ import Component from './Component'; +import { toLowerCase } from 'utils/mixins'; + +const tagName = 'tr'; export default Component.extend( { defaults: { ...Component.prototype.defaults, - tagName: 'tr', + tagName, draggable: ['thead', 'tbody', 'tfoot'], droppable: ['th', 'td'] } }, { - isComponent: el => el.tagName == 'TR' && true + isComponent: el => toLowerCase(el.tagName) === tagName } ); diff --git a/src/dom_components/model/ComponentVideo.js b/src/dom_components/model/ComponentVideo.js index eaddd4922..7f193e639 100644 --- a/src/dom_components/model/ComponentVideo.js +++ b/src/dom_components/model/ComponentVideo.js @@ -1,5 +1,7 @@ import Component from './ComponentImage'; +import { toLowerCase } from 'utils/mixins'; +const type = 'video'; const yt = 'yt'; const vi = 'vi'; const ytnc = 'ytnc'; @@ -8,8 +10,8 @@ export default Component.extend( { defaults: { ...Component.prototype.defaults, - type: 'video', - tagName: 'video', + type, + tagName: type, videoId: '', void: 0, provider: 'so', // on change of provider, traits are switched @@ -337,14 +339,18 @@ export default Component.extend( * @private */ isComponent(el) { - var result = ''; - var isYtProv = /youtube\.com\/embed/.test(el.src); - var isYtncProv = /youtube-nocookie\.com\/embed/.test(el.src); - var isViProv = /player\.vimeo\.com\/video/.test(el.src); - var isExtProv = isYtProv || isYtncProv || isViProv; - if (el.tagName == 'VIDEO' || (el.tagName == 'IFRAME' && isExtProv)) { + let result = ''; + const { tagName, src } = el; + const isYtProv = /youtube\.com\/embed/.test(src); + const isYtncProv = /youtube-nocookie\.com\/embed/.test(src); + const isViProv = /player\.vimeo\.com\/video/.test(src); + const isExtProv = isYtProv || isYtncProv || isViProv; + if ( + toLowerCase(tagName) == type || + (toLowerCase(tagName) == 'iframe' && isExtProv) + ) { result = { type: 'video' }; - if (el.src) result.src = el.src; + if (src) result.src = src; if (isExtProv) { if (isYtProv) result.provider = yt; else if (isYtncProv) result.provider = ytnc; diff --git a/src/dom_components/view/ComponentScriptView.js b/src/dom_components/view/ComponentScriptView.js index b044bca6e..5291f403c 100644 --- a/src/dom_components/view/ComponentScriptView.js +++ b/src/dom_components/view/ComponentScriptView.js @@ -32,7 +32,7 @@ export default ComponentView.extend({ `; em && em.set('scriptCount', scriptCount + 1); } else { - content = model.get('content'); + content = model.__innerHTML(); } this.el.innerHTML = content; diff --git a/src/editor/index.js b/src/editor/index.js index 9b246eb3e..a0b18bf6a 100644 --- a/src/editor/index.js +++ b/src/editor/index.js @@ -88,9 +88,6 @@ * ### Modal * * `modal:open` - Modal is opened * * `modal:close` - Modal is closed - * ### Parser - * * `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 * ### Commands * * `run:{commandName}` - Triggered when some command is called to run (eg. editor.runCommand('preview')) * * `stop:{commandName}` - Triggered when some command is called to stop (eg. editor.stopCommand('preview')) @@ -99,6 +96,8 @@ * * `abort:{commandName}` - Triggered when the command execution is aborted (`editor.on(`run:preview:before`, opts => opts.abort = 1);`) * * `run` - Triggered on run of any command. The id and the result are passed as arguments to the callback * * `stop` - Triggered on stop of any command. The id and the result are passed as arguments to the callback + * ### Parser + * Check the [Parser](/api/parser.html) module. * ### Pages * Check the [Pages](/api/pages.html) module. * ### General diff --git a/src/parser/config/config.js b/src/parser/config/config.js index d33ad56f8..aa11e24fa 100644 --- a/src/parser/config/config.js +++ b/src/parser/config/config.js @@ -2,8 +2,20 @@ export default { textTags: ['br', 'b', 'i', 'u', 'a', 'ul', 'ol'], // Custom CSS parser + // @see https://grapesjs.com/docs/guides/Custom-CSS-parser.html parserCss: null, // Custom HTML parser - parserHtml: null + // At the moment, the custom HTML parser should rely on DOM Node instance as the result + // @example + // The return should be an instance of an Node as the root to traverse + // https://developer.mozilla.org/en-US/docs/Web/API/Node + // parserHtml: (input, opts = {}) => (new DOMParser()).parseFromString(input, 'text/xml') + // Here the result will be XMLDocument, which extends Node + parserHtml: null, + + // DOMParser mime type (default 'text/html') + // @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString + // If you use the `text/html` parser, it will fix the invalid syntax automatically + htmlType: null }; diff --git a/src/parser/index.js b/src/parser/index.js index 2062375b2..52c3fe4f4 100644 --- a/src/parser/index.js +++ b/src/parser/index.js @@ -1,3 +1,29 @@ +/** + * 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'; @@ -13,36 +39,8 @@ export default () => { parserHtml: null, - /** - * Name of the module - * @type {String} - * @private - */ name: 'Parser', - /** - * Get config object - * @return {Object} - */ - getConfig() { - return conf; - }, - - /** - * Initialize module. Automatically called with a new instance of the editor - * @param {Object} config Configurations - * @param {Array} [config.blocks=[]] Default blocks - * @return {this} - * @example - * ... - * { - * blocks: [ - * {id:'h1-block' label: 'Heading', content:'

...

'}, - * ... - * ], - * } - * ... - */ init(config = {}) { conf = { ...defaults, ...config }; conf.Parser = this; @@ -55,23 +53,48 @@ export default () => { }, /** - * Parse HTML string and return valid model - * @param {string} str HTML string - * @return {Object} + * 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 + * @returns {Object} Object containing the result `{ html: ..., css: ... }` + * @example + * const resHtml = Parser.parseHtml(`
Hi
`, { + * 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 `
Hi
` + * const resXml = Parser.parseHtml(`
Hi
`, { + * htmlType: 'application/xml', + * }); + * // This will preserve the original format as, from the XML point of view, is a valid format */ - parseHtml(str) { + parseHtml(input, options = {}) { const { em, compTypes } = this; pHtml.compTypes = em ? em.get('DomComponents').getTypes() : compTypes; - return pHtml.parse(str, pCss); + return pHtml.parse(input, pCss, options); }, /** - * Parse CSS string and return valid model - * @param {string} str CSS string - * @return {Array} + * Parse CSS string and return an array of valid definition objects for CSSRules + * @param {String} input CSS string to parse + * @returns {Array} Array containing the result + * @example + * const res = Parser.parseCss('.cls { color: red }'); + * // [{ ... }] */ - parseCss(str) { - return pCss.parse(str); + parseCss(input) { + return pCss.parse(input); }, destroy() { diff --git a/src/parser/model/BrowserParserHtml.js b/src/parser/model/BrowserParserHtml.js index 3a338908f..3e3360d96 100644 --- a/src/parser/model/BrowserParserHtml.js +++ b/src/parser/model/BrowserParserHtml.js @@ -1,4 +1,57 @@ -// PoC +import { each } from 'underscore'; + +const htmlType = 'text/html'; +const defaultType = htmlType; // 'application/xml'; + +export default (str, config = {}) => { + const parser = new DOMParser(); + const mimeType = config.htmlType || defaultType; + const toHTML = mimeType === htmlType; + const strF = toHTML ? str : `
${str}
`; + const doc = parser.parseFromString(strF, mimeType); + let res; + + if (toHTML) { + // Replicate the old parser in order to avoid breaking changes + const { head, body } = doc; + // Move all scripts at the bottom of the page + const scripts = head.querySelectorAll('script'); + each(scripts, node => body.appendChild(node)); + // Move inside body all head children + const hEls = []; + each(head.children, n => hEls.push(n)); + each(hEls, (node, i) => body.insertBefore(node, body.children[i])); + res = body; + } else { + res = doc.firstChild; + } + + return res; +}; + +/** + * POC, custom html parser specs + * Parse an HTML string to an array of nodes + * example + * parse(`
Hello
World example`) + * // result + * [ + * { + * tagName: 'div', + * attributes: { class: 'mycls', 'data-test': '' }, + * childNodes: ['Hello'], + * },{ + * tagName: 'span', + * childNodes: [ + * 'World ', + * { + * tagName: 'b', + * childNodes: ['example'], + * } + * ], + * } + * ] + * export const parseNodes = nodes => { const result = []; @@ -44,29 +97,7 @@ export const parseNode = el => { }; }; -/** - * Parse an HTML string to an array of nodes - * @example - * parse(`
Hello
World example`) - * // result - * [ - * { - * tagName: 'div', - * attributes: { class: 'mycls', 'data-test': '' }, - * childNodes: ['Hello'], - * },{ - * tagName: 'span', - * childNodes: [ - * 'World ', - * { - * tagName: 'b', - * childNodes: ['example'], - * } - * ], - * } - * ] - */ -export default (str, config) => { +export default (str, config = {}) => { const result = []; const el = document.createElement('div'); el.innerHTML = str; @@ -79,3 +110,4 @@ export default (str, config) => { return result; }; + */ diff --git a/src/parser/model/ParserHtml.js b/src/parser/model/ParserHtml.js index 654031e73..dee26c945 100644 --- a/src/parser/model/ParserHtml.js +++ b/src/parser/model/ParserHtml.js @@ -1,9 +1,10 @@ -import { each, isString } from 'underscore'; +import { each, isString, isFunction } from 'underscore'; +import BrowserParserHtml from './BrowserParserHtml'; export default config => { - var TEXT_NODE = 'span'; - var c = config; - var modelAttrStart = 'data-gjs-'; + let c = config; + const modelAttrStart = 'data-gjs-'; + const event = 'parse:html'; return { compTypes: '', @@ -101,7 +102,7 @@ export default config => { * @param {HTMLElement} el DOM element to traverse * @return {Array} */ - parseNode(el) { + parseNode(el, opts = {}) { const result = []; const nodes = el.childNodes; @@ -128,7 +129,7 @@ export default config => { // the first with a valid result will be that component for (let it = 0; it < ct.length; it++) { const compType = ct[it]; - obj = compType.model.isComponent(node); + obj = compType.model.isComponent(node, opts); if (obj) { if (typeof obj !== 'object') { @@ -207,7 +208,10 @@ export default config => { content: firstChild.nodeValue }; } else { - model.components = this.parseNode(node); + model.components = this.parseNode(node, { + inSvg: model.type === 'svg', + ...opts + }); } } @@ -227,6 +231,11 @@ export default config => { } } + // Check for custom void elements (valid in XML) + if (!nodeChild && `${node.outerHTML}`.slice(-2) === '/>') { + model.void = true; + } + // If all children are texts and there is some textnode the parent should // be text too otherwise I'm unable to edit texnodes const comps = model.components; @@ -273,17 +282,19 @@ export default config => { * @param {ParserCss} parserCss In case there is style tags inside HTML * @return {Object} */ - parse(str, parserCss) { + parse(str, parserCss, opts = {}) { const { em } = c; - const config = (em && em.get('Config')) || {}; - const res = { html: '', css: '' }; - const el = document.createElement('div'); - el.innerHTML = str; + const conf = (em && em.get('Config')) || {}; + const res = { html: null, css: null }; + const cf = { ...config, ...opts }; + const el = isFunction(cf.parserHtml) + ? cf.parserHtml(str, cf) + : BrowserParserHtml(str, cf); const scripts = el.querySelectorAll('script'); let i = scripts.length; // Remove all scripts - if (!config.allowScripts) { + if (!conf.allowScripts) { while (i--) scripts[i].parentNode.removeChild(scripts[i]); } @@ -301,12 +312,13 @@ export default config => { if (styleStr) res.css = parserCss.parse(styleStr); } + em && em.trigger(`${event}:root`, { input: str, root: el }); const result = this.parseNode(el); // I have to keep it otherwise it breaks the DomComponents.addComponent (returns always array) const resHtml = result.length === 1 && !c.returnArray ? result[0] : result; res.html = resHtml; - em && em.trigger('parse:html', { input: str, output: res }); + em && em.trigger(event, { input: str, output: res }); return res; } diff --git a/src/utils/index.js b/src/utils/index.js index 14fc93262..c1931d98c 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -1,6 +1,7 @@ import Dragger from './Dragger'; import Sorter from './Sorter'; import Resizer from './Resizer'; +import * as mixins from './mixins'; export default () => { return { @@ -22,6 +23,7 @@ export default () => { Sorter, Resizer, - Dragger + Dragger, + helpers: { ...mixins } }; }; diff --git a/src/utils/mixins.js b/src/utils/mixins.js index 1730d1b8f..b2283daa9 100644 --- a/src/utils/mixins.js +++ b/src/utils/mixins.js @@ -2,6 +2,8 @@ import { keys, isUndefined, isElement, isArray } from 'underscore'; export const hasWin = () => typeof window !== 'undefined'; +export const toLowerCase = str => (str || '').toLowerCase(); + const elProt = hasWin() ? window.Element.prototype : {}; const matches = elProt.matches || diff --git a/test/specs/dom_components/model/Component.js b/test/specs/dom_components/model/Component.js index a4ca70a2a..9ce445146 100644 --- a/test/specs/dom_components/model/Component.js +++ b/test/specs/dom_components/model/Component.js @@ -441,21 +441,18 @@ describe('Image Component', () => { test('Refuse not img element', () => { var el = document.createElement('div'); - obj = ComponentImage.isComponent(el); - expect(obj).toEqual(''); + expect(ComponentImage.isComponent(el)).toEqual(false); }); test('Component parse img element', () => { var el = document.createElement('img'); - obj = ComponentImage.isComponent(el); - expect(obj).toEqual({ type: 'image' }); + expect(ComponentImage.isComponent(el)).toEqual(true); }); test('Component parse img element with src', () => { var el = document.createElement('img'); el.src = 'http://localhost/'; - obj = ComponentImage.isComponent(el); - expect(obj).toEqual({ type: 'image' }); + expect(ComponentImage.isComponent(el)).toEqual(true); }); }); diff --git a/test/specs/dom_components/model/ComponentTypes.js b/test/specs/dom_components/model/ComponentTypes.js new file mode 100644 index 000000000..5f433322a --- /dev/null +++ b/test/specs/dom_components/model/ComponentTypes.js @@ -0,0 +1,106 @@ +import Editor from 'editor'; + +describe('Component Types', () => { + let editor; + let wrapper; + + const expectedType = (input, type, opts = {}) => { + const cmp = wrapper.append(input)[0]; + expect(wrapper.components().length).toBe(opts.total || 1); + !opts.skipHtml && expect(cmp.toHTML()).toBe(input); + const res = opts.getType ? wrapper.findType(type)[0] : cmp; + expect(res.is(type)).toBe(true); + }; + + beforeAll(() => { + editor = new Editor({ allowScripts: 1 }); + editor + .getModel() + .get('PageManager') + .onLoad(); + wrapper = editor.getWrapper(); + }); + + afterAll(() => { + editor.destroy(); + }); + + afterEach(() => { + wrapper.components().reset(); + }); + + test(' is correctly recognized', () => { + expectedType('', 'image'); + }); + + test('