mirror of https://github.com/artf/grapesjs.git
committed by
GitHub
52 changed files with 1922 additions and 2102 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 205 KiB |
@ -0,0 +1,247 @@ |
|||||
|
--- |
||||
|
title: Use Custom CSS Parser |
||||
|
--- |
||||
|
# Use Custom CSS Parser |
||||
|
|
||||
|
If you just use GrapesJS for building templates from scratch, so you start from an empty canvas and for editing you strictly rely on the generated JSON (final HTML/CSS only for end-users) then, probably, you might skip this guide. On the other hand, if you import templates from already defined HTML/CSS or let the user embed custom codes (eg. using the [grapesjs-custom-code](https://github.com/artf/grapesjs-custom-code) plugin), then you have to know that you might face strange behaviors. |
||||
|
|
||||
|
::: warning |
||||
|
This guide requires GrapesJS v0.14.33 or higher |
||||
|
::: |
||||
|
|
||||
|
[[toc]] |
||||
|
|
||||
|
|
||||
|
## Import HTML/CSS |
||||
|
|
||||
|
Importing already defined HTML/CSS is a really good feature as it lets you start editing immediately any kind of template and obviously, GrapesJS itself promotes this kind of approach |
||||
|
|
||||
|
```html |
||||
|
<div id="gjs"> |
||||
|
<div class="txt-red">Hello world!</div> |
||||
|
<style>.txt-red{color: red}</style> |
||||
|
</div> |
||||
|
|
||||
|
<script type="text/javascript"> |
||||
|
const editor = grapesjs.init({ |
||||
|
container : '#gjs', |
||||
|
fromElement: true, |
||||
|
}); |
||||
|
</script> |
||||
|
``` |
||||
|
|
||||
|
To work fast and easier GrapesJS needs to compile a simple string (HTML/CSS) into structured nodes (nested JS objects). Fortunately, most of the hard work (parsing) is already done by the browser itself which translates that string into its own objects ([DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model)/[CSSOM](https://developer.mozilla.org/en-US/docs/Web/API/CSS_Object_Model)) and so we just rely on those, by traversing them and creating our nodes (unfortunately browser's objects are not enough). The fact we're able to parse our strings just by using the browser itself it's very cool, we can enable the import feature without requiring any third-party library, so... where is the problem? Well, while the generated DOM is performing quite well, as we're able to extract what we need, unfortunately, it's not the same for the CSSOM, so let's see in the next paragraph what is wrong with it. |
||||
|
|
||||
|
## CSSOM results are inconsistent |
||||
|
|
||||
|
Unfortunately, we have discovered that the CSSOM generated by browsers are highly inconsistent from what we ask to parse. To demonstrate it, we gonna create a simple example by using the built-in parser and we'll check its result. |
||||
|
So, for our case we just take in account a simple rule, we'll parse it and print the CSSOM result on screen. |
||||
|
|
||||
|
```html |
||||
|
<h1>To parse</h1> |
||||
|
<pre id="css-to-parse"> |
||||
|
.simple-class { |
||||
|
background-image:url("https://image1.png"), url("https://image2.jpg"); |
||||
|
background-attachment: fixed, scroll; |
||||
|
background-position:left top, center center; |
||||
|
background-repeat:repeat-y, no-repeat; |
||||
|
background-size: contain, cover; |
||||
|
box-shadow: 0 0 5px #9d7aa5, 0 0 10px #e6c3ee; |
||||
|
border: 2px solid #FF0000; |
||||
|
} |
||||
|
</pre> |
||||
|
|
||||
|
<h1>Result</h1> |
||||
|
<pre id="result"></pre> |
||||
|
|
||||
|
<script> |
||||
|
// We use ES5 just to make it more cross-browser, without the need of being compiled |
||||
|
|
||||
|
function parse(str) { |
||||
|
var result = []; |
||||
|
// Create the element which will contain the style to parse |
||||
|
var el = document.createElement('style'); |
||||
|
el.innerHTML = str; |
||||
|
// We have to append the style to get its CSSOM |
||||
|
document.head.appendChild(el); |
||||
|
var sheet = el.sheet; |
||||
|
// Now we can remove it |
||||
|
document.head.removeChild(el); |
||||
|
|
||||
|
return sheet; |
||||
|
} |
||||
|
|
||||
|
function CSSOMToString(root) { |
||||
|
// For the sake of brevity we just print what we need |
||||
|
var styleStr = ''; |
||||
|
var rule = root.cssRules[0]; |
||||
|
var style = rule.style; |
||||
|
// The only way we have to iterate over CSSStyleDeclaration |
||||
|
for (var i = 0, len = style.length; i < len; i++) { |
||||
|
var property = style[i]; |
||||
|
var value = style.getPropertyValue(property); |
||||
|
styleStr += "\t" + property + ': ' + value + ";\n"; |
||||
|
} |
||||
|
var result = document.getElementById('result'); |
||||
|
result.innerHTML = rule.selectorText + " {\n" + styleStr + "}"; |
||||
|
} |
||||
|
|
||||
|
var css = document.getElementById('css-to-parse').innerText; |
||||
|
CSSOMToString(parse(css)); |
||||
|
</script> |
||||
|
``` |
||||
|
|
||||
|
### Results |
||||
|
Here some results (using latest versions + IE11) |
||||
|
|
||||
|
<img :src="$withBase('/cssom-result.jpg')"> |
||||
|
|
||||
|
As you see, this is what we get for asking only 7 properties, who adds more or less, someone converts colors to rgba functions and someone else changes the order of our values (eg. `box-shadow`). Webkit-based browsers attach also properties they self don't understand |
||||
|
|
||||
|
<img :src="$withBase('/cssom-devtools.png')"> |
||||
|
|
||||
|
So it's clear that we can't rely on CSSOM objects, that's why we added the possibility to set custom CSS parser via `editor.setCustomParserCss` method or `config.Parser.parserCss` option to use on initialization. Let's see in detail how it's expected to work |
||||
|
|
||||
|
## Set CSS parser |
||||
|
|
||||
|
The custom parser you have to use it's just a function receiving 2 arguments: `css`, as the CSS string to parse, and `editor`, the instance of the current editor. As the result, you should return an array containing valid rule objects, the syntax of those objects are explained below. This is how you can set the custom parser |
||||
|
|
||||
|
```js |
||||
|
const parserCss = (css, editor) => { |
||||
|
const result = []; |
||||
|
// ... parse the CSS string |
||||
|
result.push({ |
||||
|
selectors: '.someclass, div .otherclass', |
||||
|
style: { color: 'red' } |
||||
|
}) |
||||
|
// ... |
||||
|
return result; // Result should be ALWAYS an array |
||||
|
}; |
||||
|
|
||||
|
// On initialization |
||||
|
// This is the recommended way, as you gonna use the parser from the beginning |
||||
|
const editor = grapesjs.init({ |
||||
|
//... |
||||
|
parser: { |
||||
|
parserCss, |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// Or later, via editor API |
||||
|
editor.setCustomParserCss(parserCss); |
||||
|
``` |
||||
|
|
||||
|
## Rule Objects |
||||
|
|
||||
|
The syntax of rule objects is pretty straightforward, each object might contain following keys |
||||
|
|
||||
|
| Key | Description | Example | |
||||
|
|-|-|- |
||||
|
| `selectors` | Selectors of the rule. <br> **REQUIRED** return an empty string in case the rule has no selectors | `.class1, div > #someid` | |
||||
|
| `style` | Style declarations as an object | `{ color: 'red' }` | |
||||
|
| `atRule` | At-rule name | `media` | |
||||
|
| `params` | Parameters of the at-rule | `screen and (min-width: 480px)` | |
||||
|
|
||||
|
To make it more clear let's see a few examples |
||||
|
|
||||
|
```js |
||||
|
// Input |
||||
|
` |
||||
|
@font-face { |
||||
|
font-family: "Font Name"; |
||||
|
src: url("https://font-url.eot"); |
||||
|
} |
||||
|
` |
||||
|
// Output |
||||
|
[ |
||||
|
{ |
||||
|
selectors: '', |
||||
|
atRule: 'font-face', |
||||
|
style: { |
||||
|
'font-family': '"Font Name"', |
||||
|
src: 'url("https://font-url.eot")', |
||||
|
}, |
||||
|
} |
||||
|
] |
||||
|
|
||||
|
// Input |
||||
|
` |
||||
|
@keyframes keyframe-name { |
||||
|
from { opacity: 0; } |
||||
|
to { opacity: 1; } |
||||
|
} |
||||
|
` |
||||
|
// Output |
||||
|
[ |
||||
|
{ |
||||
|
params: 'keyframe-name', |
||||
|
selectors: 'from', |
||||
|
atRule: 'keyframes', |
||||
|
style: { |
||||
|
opacity: '0', |
||||
|
}, |
||||
|
}, { |
||||
|
params: 'keyframe-name', |
||||
|
selectors: 'to', |
||||
|
atRule: 'keyframes', |
||||
|
style: { |
||||
|
opacity: '1', |
||||
|
}, |
||||
|
} |
||||
|
] |
||||
|
|
||||
|
// Input |
||||
|
` |
||||
|
@media screen and (min-width: 480px) { |
||||
|
body { |
||||
|
background-color: lightgreen; |
||||
|
} |
||||
|
|
||||
|
.class-test, .class-test2:hover { |
||||
|
color: blue !important; |
||||
|
} |
||||
|
} |
||||
|
` |
||||
|
// Output |
||||
|
[ |
||||
|
{ |
||||
|
params: 'screen and (min-width: 480px)', |
||||
|
selectors: 'body', |
||||
|
atRule: 'media', |
||||
|
style: { |
||||
|
'background-color': 'lightgreen', |
||||
|
}, |
||||
|
}, { |
||||
|
params: 'screen and (min-width: 480px)', |
||||
|
selectors: '.class-test, .class-test2:hover', |
||||
|
atRule: 'media', |
||||
|
style: { |
||||
|
color: 'blue !important', |
||||
|
}, |
||||
|
} |
||||
|
] |
||||
|
|
||||
|
// Input |
||||
|
` |
||||
|
:root { |
||||
|
--some-color: red; |
||||
|
--some-width: 55px; |
||||
|
} |
||||
|
` |
||||
|
// Output |
||||
|
[ |
||||
|
{ |
||||
|
selectors: ':root', |
||||
|
style: { |
||||
|
'--some-color': 'red', |
||||
|
'--some-width': '55px', |
||||
|
}, |
||||
|
}, |
||||
|
] |
||||
|
``` |
||||
|
|
||||
|
## Plugins |
||||
|
|
||||
|
Below the list of current available CSS parsers as plugins, if you need to create your own we highly suggest to explore their sources |
||||
|
|
||||
|
* [grapesjs-parser-postcss](https://github.com/artf/grapesjs-parser-postcss) - Using [PostCSS](https://github.com/postcss/postcss) parser |
||||
File diff suppressed because it is too large
@ -0,0 +1,11 @@ |
|||||
|
// We need this one just to identify better the wrapper type
|
||||
|
import Component from './Component'; |
||||
|
|
||||
|
module.exports = Component.extend( |
||||
|
{}, |
||||
|
{ |
||||
|
isComponent() { |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
); |
||||
@ -1,3 +1,9 @@ |
|||||
module.exports = { |
module.exports = { |
||||
textTags: ['br', 'b', 'i', 'u', 'a', 'ul', 'ol'] |
textTags: ['br', 'b', 'i', 'u', 'a', 'ul', 'ol'], |
||||
|
|
||||
|
// Custom CSS parser
|
||||
|
parserCss: null, |
||||
|
|
||||
|
// Custom HTML parser
|
||||
|
parserHtml: null |
||||
}; |
}; |
||||
|
|||||
@ -0,0 +1,221 @@ |
|||||
|
import { keys } from 'underscore'; |
||||
|
|
||||
|
// At-rules
|
||||
|
// https://developer.mozilla.org/it/docs/Web/API/CSSRule#Type_constants
|
||||
|
const atRules = { |
||||
|
4: 'media', |
||||
|
5: 'font-face', |
||||
|
6: 'page', |
||||
|
7: 'keyframes', |
||||
|
11: 'counter-style', |
||||
|
12: 'supports', |
||||
|
13: 'document', |
||||
|
14: 'font-feature-values', |
||||
|
15: 'viewport' |
||||
|
}; |
||||
|
const atRuleKeys = keys(atRules); |
||||
|
const singleAtRules = ['5', '6', '11', '15']; |
||||
|
const singleAtRulesNames = ['font-face', 'page', 'counter-style', 'viewport']; |
||||
|
|
||||
|
/** |
||||
|
* Parse selector string to array. |
||||
|
* Only classe based are valid as CSS rules inside editor, not valid |
||||
|
* selectors will be dropped as additional |
||||
|
* It's ok with the last part of the string as state (:hover, :active) |
||||
|
* @param {string} str Selectors string |
||||
|
* @return {Object} |
||||
|
* @example |
||||
|
* var res = parseSelector('.test1, .test1.test2, .test2 .test3'); |
||||
|
* console.log(res); |
||||
|
* // {
|
||||
|
* //result: [['test1'], ['test1', 'test2']],
|
||||
|
* //add: ['.test2 .test3']
|
||||
|
* //}
|
||||
|
*/ |
||||
|
export const parseSelector = (str = '') => { |
||||
|
const add = []; |
||||
|
const result = []; |
||||
|
const sels = str.split(','); |
||||
|
|
||||
|
for (var i = 0, len = sels.length; i < len; i++) { |
||||
|
var sel = sels[i].trim(); |
||||
|
|
||||
|
// Will accept only concatenated classes and last
|
||||
|
// class might be with state (eg. :hover), nothing else.
|
||||
|
// Can also accept SINGLE ID selectors, eg. `#myid`, `#myid:hover`
|
||||
|
// Composed are not valid: `#myid.some-class`, `#myid.some-class:hover`
|
||||
|
if ( |
||||
|
/^(\.{1}[\w\-]+)+(:{1,2}[\w\-()]+)?$/gi.test(sel) || |
||||
|
/^(#{1}[\w\-]+){1}(:{1,2}[\w\-()]+)?$/gi.test(sel) |
||||
|
) { |
||||
|
var cls = sel.split('.').filter(Boolean); |
||||
|
result.push(cls); |
||||
|
} else { |
||||
|
add.push(sel); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
result, |
||||
|
add |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Parse style declarations of the node |
||||
|
* @param {CSSRule} node |
||||
|
* @return {Object} |
||||
|
*/ |
||||
|
export const parseStyle = node => { |
||||
|
const stl = node.style; |
||||
|
const style = {}; |
||||
|
|
||||
|
for (var i = 0, len = stl.length; i < len; i++) { |
||||
|
const propName = stl[i]; |
||||
|
const propValue = stl.getPropertyValue(propName); |
||||
|
const important = stl.getPropertyPriority(propName); |
||||
|
style[propName] = `${propValue}${important ? ` !${important}` : ''}`; |
||||
|
} |
||||
|
|
||||
|
return style; |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Get the condition when possible |
||||
|
* @param {CSSRule} node |
||||
|
* @return {string} |
||||
|
*/ |
||||
|
export const parseCondition = node => { |
||||
|
const condition = |
||||
|
node.conditionText || |
||||
|
(node.media && node.media.mediaText) || |
||||
|
node.name || |
||||
|
node.selectorText || |
||||
|
''; |
||||
|
return condition.trim(); |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Create node for the editor |
||||
|
* @param {Array<String>} selectors Array containing strings of classes |
||||
|
* @param {Object} style Key-value object of style declarations |
||||
|
* @return {Object} |
||||
|
*/ |
||||
|
export const createNode = (selectors, style, opts = {}) => { |
||||
|
const node = {}; |
||||
|
const selLen = selectors.length; |
||||
|
const lastClass = selectors[selLen - 1]; |
||||
|
const stateArr = lastClass ? lastClass.split(/:(.+)/) : []; |
||||
|
const state = stateArr[1]; |
||||
|
const { atRule, selectorsAdd, mediaText } = opts; |
||||
|
const singleAtRule = singleAtRulesNames.indexOf(atRule) >= 0; |
||||
|
singleAtRule && (node.singleAtRule = 1); |
||||
|
atRule && (node.atRuleType = atRule); |
||||
|
selectorsAdd && (node.selectorsAdd = selectorsAdd); |
||||
|
mediaText && (node.mediaText = mediaText); |
||||
|
|
||||
|
// Isolate the state from selectors
|
||||
|
if (state) { |
||||
|
selectors[selLen - 1] = stateArr[0]; |
||||
|
node.state = state; |
||||
|
stateArr.splice(stateArr.length - 1, 1); |
||||
|
} |
||||
|
|
||||
|
node.selectors = selectors; |
||||
|
node.style = style; |
||||
|
|
||||
|
return node; |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Fetch data from node |
||||
|
* @param {StyleSheet|CSSRule} el |
||||
|
* @return {Array<Object>} |
||||
|
*/ |
||||
|
export const parseNode = el => { |
||||
|
var result = []; |
||||
|
var nodes = el.cssRules || []; |
||||
|
|
||||
|
for (var i = 0, len = nodes.length; i < len; i++) { |
||||
|
const node = nodes[i]; |
||||
|
const type = node.type.toString(); |
||||
|
let singleAtRule = 0; |
||||
|
let atRuleType = ''; |
||||
|
let condition = ''; |
||||
|
// keyText is for CSSKeyframeRule
|
||||
|
let sels = node.selectorText || node.keyText; |
||||
|
const isSingleAtRule = singleAtRules.indexOf(type) >= 0; |
||||
|
|
||||
|
// Check if the node is an at-rule
|
||||
|
if (isSingleAtRule) { |
||||
|
singleAtRule = 1; |
||||
|
atRuleType = atRules[type]; |
||||
|
condition = parseCondition(node); |
||||
|
} else if (atRuleKeys.indexOf(type) >= 0) { |
||||
|
var subRules = parseNode(node); |
||||
|
condition = parseCondition(node); |
||||
|
|
||||
|
for (var s = 0, lens = subRules.length; s < lens; s++) { |
||||
|
var subRule = subRules[s]; |
||||
|
condition && (subRule.mediaText = condition); |
||||
|
subRule.atRuleType = atRules[type]; |
||||
|
} |
||||
|
result = result.concat(subRules); |
||||
|
} |
||||
|
|
||||
|
if (!sels && !isSingleAtRule) continue; |
||||
|
const style = parseStyle(node); |
||||
|
const selsParsed = parseSelector(sels); |
||||
|
const selsAdd = selsParsed.add; |
||||
|
sels = selsParsed.result; |
||||
|
|
||||
|
let lastRule; |
||||
|
// For each group of selectors
|
||||
|
for (var k = 0, len3 = sels.length; k < len3; k++) { |
||||
|
const model = createNode(sels[k], style, { |
||||
|
atRule: atRules[type] |
||||
|
}); |
||||
|
result.push(model); |
||||
|
lastRule = model; |
||||
|
} |
||||
|
|
||||
|
// Need to push somewhere not class-based selectors, if some rule was
|
||||
|
// created will push them there, otherwise will create a new rule
|
||||
|
if (selsAdd.length) { |
||||
|
var selsAddStr = selsAdd.join(', '); |
||||
|
if (lastRule) { |
||||
|
lastRule.selectorsAdd = selsAddStr; |
||||
|
} else { |
||||
|
const model = { |
||||
|
selectors: [], |
||||
|
selectorsAdd: selsAddStr, |
||||
|
style |
||||
|
}; |
||||
|
singleAtRule && (model.singleAtRule = singleAtRule); |
||||
|
atRuleType && (model.atRuleType = atRuleType); |
||||
|
condition && (model.mediaText = condition); |
||||
|
result.push(model); |
||||
|
} |
||||
|
} |
||||
|
// console.log('LAST PUSH', result[result.length - 1]);
|
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Parse CSS string and return the array of objects |
||||
|
* @param {String} str CSS string |
||||
|
* @return {Array<Object>} Array of objects for the definition of CSSRules |
||||
|
*/ |
||||
|
export default str => { |
||||
|
const el = document.createElement('style'); |
||||
|
el.innerHTML = str; |
||||
|
|
||||
|
// There is no .sheet before adding it to the <head>
|
||||
|
document.head.appendChild(el); |
||||
|
const sheet = el.sheet; |
||||
|
document.head.removeChild(el); |
||||
|
|
||||
|
return parseNode(sheet); |
||||
|
}; |
||||
@ -1,203 +1,59 @@ |
|||||
import { keys } from 'underscore'; |
import { isString } from 'underscore'; |
||||
|
import BrowserCssParser, { |
||||
// At-rules
|
parseSelector, |
||||
// https://developer.mozilla.org/it/docs/Web/API/CSSRule#Type_constants
|
createNode |
||||
const atRules = { |
} from './BrowserParserCss'; |
||||
4: 'media', |
|
||||
5: 'font-face', |
|
||||
6: 'page', |
|
||||
7: 'keyframes', |
|
||||
11: 'counter-style', |
|
||||
12: 'supports', |
|
||||
13: 'document', |
|
||||
14: 'font-feature-values', |
|
||||
15: 'viewport' |
|
||||
}; |
|
||||
const atRuleKeys = keys(atRules); |
|
||||
const singleAtRules = ['5', '6', '11', '15']; |
|
||||
|
|
||||
module.exports = config => ({ |
|
||||
/** |
|
||||
* Parse selector string to array. |
|
||||
* Only classe based are valid as CSS rules inside editor, not valid |
|
||||
* selectors will be dropped as additional |
|
||||
* It's ok with the last part of the string as state (:hover, :active) |
|
||||
* @param {string} str Selectors string |
|
||||
* @return {Object} |
|
||||
* @example |
|
||||
* var res = ParserCss.parseSelector('.test1, .test1.test2, .test2 .test3'); |
|
||||
* console.log(res); |
|
||||
* // {
|
|
||||
* //result: [['test1'], ['test1', 'test2']],
|
|
||||
* //add: ['.test2 .test3']
|
|
||||
* //}
|
|
||||
*/ |
|
||||
parseSelector(str = '') { |
|
||||
const add = []; |
|
||||
const result = []; |
|
||||
const sels = str.split(','); |
|
||||
|
|
||||
for (var i = 0, len = sels.length; i < len; i++) { |
|
||||
var sel = sels[i].trim(); |
|
||||
|
|
||||
// Will accept only concatenated classes and last
|
|
||||
// class might be with state (eg. :hover), nothing else.
|
|
||||
// Can also accept SINGLE ID selectors, eg. `#myid`, `#myid:hover`
|
|
||||
// Composed are not valid: `#myid.some-class`, `#myid.some-class:hover`
|
|
||||
if ( |
|
||||
/^(\.{1}[\w\-]+)+(:{1,2}[\w\-()]+)?$/gi.test(sel) || |
|
||||
/^(#{1}[\w\-]+){1}(:{1,2}[\w\-()]+)?$/gi.test(sel) |
|
||||
) { |
|
||||
var cls = sel.split('.').filter(Boolean); |
|
||||
result.push(cls); |
|
||||
} else { |
|
||||
add.push(sel); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return { |
|
||||
result, |
|
||||
add |
|
||||
}; |
|
||||
}, |
|
||||
|
|
||||
|
module.exports = (config = {}) => ({ |
||||
/** |
/** |
||||
* Parse style declarations of the node |
* Parse CSS string to a desired model object |
||||
* @param {CSSRule} node |
* @param {String} str CSS string |
||||
* @return {Object} |
* @return {Array<Object>} |
||||
*/ |
*/ |
||||
parseStyle(node) { |
parse(str) { |
||||
const stl = node.style; |
let result = []; |
||||
const style = {}; |
const { parserCss, em = {} } = config; |
||||
|
const editor = em && em.get && em.get('Editor'); |
||||
for (var i = 0, len = stl.length; i < len; i++) { |
const nodes = parserCss ? parserCss(str, editor) : BrowserCssParser(str); |
||||
const propName = stl[i]; |
nodes.forEach(node => (result = result.concat(this.checkNode(node)))); |
||||
const propValue = stl.getPropertyValue(propName); |
|
||||
const important = stl.getPropertyPriority(propName); |
|
||||
style[propName] = `${propValue}${important ? ` !${important}` : ''}`; |
|
||||
} |
|
||||
|
|
||||
return style; |
|
||||
}, |
|
||||
|
|
||||
/** |
return result; |
||||
* Get the condition when possible |
|
||||
* @param {CSSRule} node |
|
||||
* @return {string} |
|
||||
*/ |
|
||||
parseCondition(node) { |
|
||||
const condition = |
|
||||
node.conditionText || |
|
||||
(node.media && node.media.mediaText) || |
|
||||
node.name || |
|
||||
node.selectorText || |
|
||||
''; |
|
||||
return condition.trim(); |
|
||||
}, |
}, |
||||
|
|
||||
/** |
/** |
||||
* Fetch data from node |
* Check the returned node from a custom parser and transforms it to |
||||
* @param {StyleSheet|CSSRule} el |
* a valid object for the CSS composer |
||||
* @return {Array<Object>} |
* @return {[type]} |
||||
*/ |
*/ |
||||
parseNode(el) { |
checkNode(node) { |
||||
var result = []; |
const { selectors, style } = node; |
||||
var nodes = el.cssRules || []; |
|
||||
|
if (isString(selectors)) { |
||||
for (var i = 0, len = nodes.length; i < len; i++) { |
const nodes = []; |
||||
const node = nodes[i]; |
const selsParsed = parseSelector(selectors); |
||||
const type = node.type.toString(); |
const classSets = selsParsed.result; |
||||
let singleAtRule = 0; |
const selectorsAdd = selsParsed.add.join(', '); |
||||
let atRuleType = ''; |
const opts = { |
||||
let condition = ''; |
atRule: node.atRule, |
||||
// keyText is for CSSKeyframeRule
|
mediaText: node.params |
||||
let sels = node.selectorText || node.keyText; |
}; |
||||
const isSingleAtRule = singleAtRules.indexOf(type) >= 0; |
|
||||
|
if (classSets.length) { |
||||
// Check if the node is an at-rule
|
classSets.forEach(classSet => { |
||||
if (isSingleAtRule) { |
nodes.push(createNode(classSet, style, opts)); |
||||
singleAtRule = 1; |
}); |
||||
atRuleType = atRules[type]; |
} else { |
||||
condition = this.parseCondition(node); |
nodes.push(createNode([], style, opts)); |
||||
} else if (atRuleKeys.indexOf(type) >= 0) { |
|
||||
var subRules = this.parseNode(node); |
|
||||
condition = this.parseCondition(node); |
|
||||
|
|
||||
for (var s = 0, lens = subRules.length; s < lens; s++) { |
|
||||
var subRule = subRules[s]; |
|
||||
condition && (subRule.mediaText = condition); |
|
||||
subRule.atRuleType = atRules[type]; |
|
||||
} |
|
||||
result = result.concat(subRules); |
|
||||
} |
} |
||||
|
|
||||
if (!sels && !isSingleAtRule) continue; |
if (selectorsAdd) { |
||||
const style = this.parseStyle(node); |
const lastNode = nodes[nodes.length - 1]; |
||||
const selsParsed = this.parseSelector(sels); |
lastNode.selectorsAdd = selectorsAdd; |
||||
const selsAdd = selsParsed.add; |
|
||||
sels = selsParsed.result; |
|
||||
|
|
||||
let lastRule; |
|
||||
// For each group of selectors
|
|
||||
for (var k = 0, len3 = sels.length; k < len3; k++) { |
|
||||
var selArr = sels[k]; |
|
||||
var model = {}; |
|
||||
singleAtRule && (model.singleAtRule = singleAtRule); |
|
||||
atRuleType && (model.atRuleType = atRuleType); |
|
||||
|
|
||||
//Isolate state from selector
|
|
||||
var stateArr = selArr[selArr.length - 1].split(/:(.+)/); |
|
||||
if (stateArr[1]) { |
|
||||
selArr[selArr.length - 1] = stateArr[0]; |
|
||||
model.state = stateArr[1]; |
|
||||
stateArr.splice(stateArr.length - 1, 1); |
|
||||
} |
|
||||
|
|
||||
model.selectors = selArr; |
|
||||
model.style = style; |
|
||||
lastRule = model; |
|
||||
result.push(model); |
|
||||
} |
} |
||||
|
|
||||
// Need to push somewhere not class-based selectors, if some rule was
|
node = nodes; |
||||
// created will push them there, otherwise will create a new rule
|
|
||||
if (selsAdd.length) { |
|
||||
var selsAddStr = selsAdd.join(', '); |
|
||||
if (lastRule) { |
|
||||
lastRule.selectorsAdd = selsAddStr; |
|
||||
} else { |
|
||||
const model = { |
|
||||
selectors: [], |
|
||||
selectorsAdd: selsAddStr, |
|
||||
style |
|
||||
}; |
|
||||
singleAtRule && (model.singleAtRule = singleAtRule); |
|
||||
atRuleType && (model.atRuleType = atRuleType); |
|
||||
condition && (model.mediaText = condition); |
|
||||
result.push(model); |
|
||||
} |
|
||||
} |
|
||||
// console.log('LAST PUSH', result[result.length - 1]);
|
|
||||
} |
} |
||||
|
|
||||
return result; |
return node; |
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Parse CSS string to a desired model object |
|
||||
* @param {string} str HTML string |
|
||||
* @return {Object|Array<Object>} |
|
||||
*/ |
|
||||
parse(str) { |
|
||||
const el = document.createElement('style'); |
|
||||
el.innerHTML = str; |
|
||||
|
|
||||
// There is no .sheet before adding it to the <head>
|
|
||||
document.head.appendChild(el); |
|
||||
const sheet = el.sheet; |
|
||||
document.head.removeChild(el); |
|
||||
const result = this.parseNode(sheet); |
|
||||
|
|
||||
return result.length == 1 ? result[0] : result; |
|
||||
} |
} |
||||
}); |
}); |
||||
|
|||||
Loading…
Reference in new issue