Browse Source

Merge pull request #3691 from artf/update-html-parser

Update html parser
pull/3692/head
Artur Arseniev 5 years ago
committed by GitHub
parent
commit
1fc9427118
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      docs/.vuepress/config.js
  2. 2
      docs/api.js
  3. 4
      docs/api/assets.md
  4. 2
      docs/api/component.md
  5. 83
      docs/api/css_rule.md
  6. 193
      docs/api/editor.md
  7. 102
      docs/api/parser.md
  8. 44
      src/css_composer/model/CssRule.js
  9. 3
      src/dom_components/config/config.js
  10. 10
      src/dom_components/model/Component.js
  11. 3
      src/dom_components/model/ComponentFrame.js
  12. 17
      src/dom_components/model/ComponentImage.js
  13. 12
      src/dom_components/model/ComponentLabel.js
  14. 4
      src/dom_components/model/ComponentLink.js
  15. 6
      src/dom_components/model/ComponentMap.js
  16. 3
      src/dom_components/model/ComponentScript.js
  17. 18
      src/dom_components/model/ComponentSvg.js
  18. 9
      src/dom_components/model/ComponentSvgIn.js
  19. 17
      src/dom_components/model/ComponentTable.js
  20. 17
      src/dom_components/model/ComponentTableBody.js
  21. 15
      src/dom_components/model/ComponentTableCell.js
  22. 17
      src/dom_components/model/ComponentTableFoot.js
  23. 17
      src/dom_components/model/ComponentTableHead.js
  24. 7
      src/dom_components/model/ComponentTableRow.js
  25. 24
      src/dom_components/model/ComponentVideo.js
  26. 2
      src/dom_components/view/ComponentScriptView.js
  27. 5
      src/editor/index.js
  28. 14
      src/parser/config/config.js
  29. 99
      src/parser/index.js
  30. 80
      src/parser/model/BrowserParserHtml.js
  31. 40
      src/parser/model/ParserHtml.js
  32. 4
      src/utils/index.js
  33. 2
      src/utils/mixins.js
  34. 9
      test/specs/dom_components/model/Component.js
  35. 106
      test/specs/dom_components/model/ComponentTypes.js

2
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'],
],
'/': [
'',

2
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(' && ');

4
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

2
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).

83
docs/api/css_rule.md

@ -0,0 +1,83 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## 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

193
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]\<CssRule>)** 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]\<Component>**
Returns **[Array][7]\<Component>**
## 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]\<CssRule>** Array of created CssRule instances
Returns **[Array][7]\<CssRule>** 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 = '<b>Hello</b>';
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

102
docs/api/parser.md

@ -0,0 +1,102 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## 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(`<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
```
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

44
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 = '';

3
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',

10
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 => {

3
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
}
);

17
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'
}
);

12
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
}
);

4
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

6
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;

3
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) {

18
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
}
);

9
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
}
);

17
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
}
);

17
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
}
);

15
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
}
);

17
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
}
);

17
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
}
);

7
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
}
);

24
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;

2
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;

5
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

14
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
};

99
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<Object>} [config.blocks=[]] Default blocks
* @return {this}
* @example
* ...
* {
* blocks: [
* {id:'h1-block' label: 'Heading', content:'<h1>...</h1>'},
* ...
* ],
* }
* ...
*/
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(`<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(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<Object>}
* 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(str) {
return pCss.parse(str);
parseCss(input) {
return pCss.parse(input);
},
destroy() {

80
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 : `<div>${str}</div>`;
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(`<div class="mycls" data-test>Hello</div><span>World <b>example</b></span>`)
* // 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(`<div class="mycls" data-test>Hello</div><span>World <b>example</b></span>`)
* // 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;
};
*/

40
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<Object>}
*/
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;
}

4
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 }
};
};

2
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 ||

9
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);
});
});

106
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('<img> is correctly recognized', () => {
expectedType('<img src="img.png" attr-test="value"/>', 'image');
});
test('<label> is correctly recognized', () => {
expectedType('<label attr-test="value">Hello</label>', 'label');
});
test('<a> is correctly recognized', () => {
expectedType('<a href="/link">link</a>', 'link');
});
test('<table> is correctly recognized', () => {
expectedType('<table></table>', 'table', { skipHtml: 1 });
});
test('<thead> is correctly recognized', () => {
expectedType('<table><thead> </thead></table>', 'thead', { getType: 1 });
});
test('<tbody> is correctly recognized', () => {
expectedType('<table><tbody> </tbody></table>', 'tbody', { getType: 1 });
});
test('<tr> is correctly recognized', () => {
expectedType('<table><tbody><tr> </tr></tbody></table>', 'row', {
getType: 1
});
});
test('<video> is correctly recognized', () => {
expectedType('<video></video>', 'video', { skipHtml: 1 });
});
test('<td> & <th> are correctly recognized', () => {
expectedType('<table><tbody><tr><td></td></tr></tbody></table>', 'cell', {
getType: 1
});
expectedType('<table><tbody><tr><th></th></tr></tbody></table>', 'cell', {
total: 2,
getType: 1
});
});
test('<tfoot> is correctly recognized', () => {
expectedType('<table><tfoot> </tfoot></table>', 'tfoot', { getType: 1 });
});
test('<script> is correctly recognized', () => {
// const scr = 'console.log("Inline script");'; // issues with jsdom parser
const scr = ``;
expectedType(`<script attr-test="value">${scr}</script>`, 'script');
});
test('<iframe> is correctly recognized', () => {
expectedType(
`<iframe frameborder="0" src="/somewhere" attr-test="value"></iframe>`,
'iframe'
);
});
test('<svg> is correctly recognized', () => {
const cmp = wrapper.append(`<svg viewBox="0 0 24 24" height="30px">
<path d="M19 4h-3.5l-1-1h-5l-1 1H5v2h14M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12z"></path></svg>
`)[0];
expect(wrapper.components().length).toBe(1);
expect(cmp.is('svg')).toBe(true);
expect(
cmp
.components()
.at(0)
.is('svg-in')
).toBe(true);
});
});
Loading…
Cancel
Save