grapesjs
Version:
Free and Open Source Web Builder Framework
149 lines (129 loc) • 4.14 kB
JavaScript
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) {
var add = [];
var result = [];
var 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.
if (/^(\.{1}[\w\-]+)+(:{1,2}[\w\-()]+)?$/ig.test(sel)) {
var cls = sel.split('.').filter(Boolean);
result.push(cls);
} else {
add.push(sel);
}
}
return {
result,
add,
};
},
/**
* Fetch data from node
* @param {StyleSheet|CSSMediaRule} el
* @return {Array<Object>}
*/
parseNode(el) {
var result = [];
var nodes = el.cssRules;
for (var i = 0, len = nodes.length; i < len; i++) {
var node = nodes[i];
var sels = node.selectorText;
var selsAdd = [];
// It's a CSSMediaRule
if(node.cssRules) {
var subRules = this.parseNode(node);
var mediaText = node.media.mediaText;
for( var s = 0, lens = subRules.length; s < lens; s++) {
var subRule = subRules[s];
subRule.mediaText = mediaText ? mediaText.trim() : '';
}
result = result.concat(subRules);
}
if(!sels)
continue;
var selsParsed = this.parseSelector(sels);
sels = selsParsed.result;
selsAdd = selsParsed.add;
// Create style object from the big one
var stl = node.style;
var style = {};
for (var j = 0, len2 = stl.length; j < len2; j++) {
const propName = stl[j];
const propValue = stl.getPropertyValue(propName);
const important = stl.getPropertyPriority(propName);
style[propName] = `${propValue}${important ? ` !${important}` : ''}`
}
var lastRule = '';
// For each group of selectors
for (var k = 0, len3 = sels.length; k < len3; k++) {
var selArr = sels[k];
var model = {};
//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
// created will push them there, otherwise will create a new rule
if (selsAdd.length) {
var selsAddStr = selsAdd.join(', ');
if (lastRule) {
lastRule.selectorsAdd = selsAddStr;
} else {
result.push({
selectors: [],
selectorsAdd: selsAddStr,
style,
});
}
}
}
return result;
},
/**
* Parse CSS string to a desired model object
* @param {string} str HTML string
* @return {Object|Array<Object>}
*/
parse(str) {
var el = document.createElement('style');
/*
el.innerHTML = ".cssClass {border: 2px solid black; background-color: blue;} " +
".red, .red2 {color:red; padding:5px} .test1.red {color:black} .red:hover{color: blue} " +
"@media screen and (min-width: 480px){ .red{color:white} }";
*/
el.innerHTML = str;
// There is no .sheet without adding it to the <head>
document.head.appendChild(el);
var sheet = el.sheet;
document.head.removeChild(el);
var result = this.parseNode(sheet);
if(result.length == 1)
result = result[0];
return result;
}
});