mirror of https://github.com/artf/grapesjs.git
nocodeframeworkdrag-and-dropsite-buildersite-generatortemplate-builderui-builderweb-builderweb-builder-frameworkwebsite-builderno-codepage-builder
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
222 lines
6.4 KiB
222 lines
6.4 KiB
define(function(require) {
|
|
|
|
return function(config) {
|
|
|
|
var TEXT_NODE = 'span';
|
|
var c = config;
|
|
|
|
return {
|
|
|
|
compTypes: '',
|
|
|
|
/**
|
|
* Parse style string to object
|
|
* @param {string} str
|
|
* @return {Object}
|
|
* @example
|
|
* var stl = ParserHtml.parseStyle('color:black; width:100px; test:value;');
|
|
* console.log(stl);
|
|
* // {color: 'black', width: '100px', test: 'value'}
|
|
*/
|
|
parseStyle: function(str){
|
|
var result = {};
|
|
var decls = str.split(';');
|
|
for (var i = 0, len = decls.length; i < len; i++) {
|
|
var decl = decls[i].trim();
|
|
if(!decl)
|
|
continue;
|
|
var prop = decl.split(':');
|
|
result[prop[0].trim()] = prop[1].trim();
|
|
}
|
|
return result;
|
|
},
|
|
|
|
/**
|
|
* Parse class string to array
|
|
* @param {string} str
|
|
* @return {Array<string>}
|
|
* @example
|
|
* var res = ParserHtml.parseClass('test1 test2 test3');
|
|
* console.log(res);
|
|
* // ['test1', 'test2', 'test3']
|
|
*/
|
|
parseClass: function(str){
|
|
var result = [];
|
|
var cls = str.split(' ');
|
|
for (var i = 0, len = cls.length; i < len; i++) {
|
|
var cl = cls[i].trim();
|
|
if(!cl)
|
|
continue;
|
|
result.push(cl);
|
|
}
|
|
return result;
|
|
},
|
|
|
|
/**
|
|
* Fetch data from node
|
|
* @param {HTMLElement} el DOM
|
|
* @return {Array<Object>}
|
|
*/
|
|
parseNode: function(el){
|
|
var result = [];
|
|
var nodes = el.childNodes;
|
|
|
|
for (var i = 0, len = nodes.length; i < len; i++) {
|
|
var node = nodes[i];
|
|
var model = {};
|
|
var attrs = node.attributes || [];
|
|
var attrsLen = attrs.length;
|
|
var prevI = result.length - 1;
|
|
var prevSib = result[prevI];
|
|
var ct = this.compTypes;
|
|
|
|
if(ct){
|
|
var obj = '';
|
|
for (var cType in ct) {
|
|
var component = ct[cType].model;
|
|
obj = component.isComponent(node);
|
|
if(obj)
|
|
break;
|
|
}
|
|
model = obj;
|
|
}
|
|
|
|
if(!model.tagName)
|
|
model.tagName = node.tagName ? node.tagName.toLowerCase() : '';
|
|
|
|
if(attrsLen)
|
|
model.attributes = {};
|
|
|
|
// Store attributes
|
|
for (var j = 0; j < attrsLen; j++){
|
|
var nodeName = attrs[j].nodeName;
|
|
var nodeValue = attrs[j].nodeValue;
|
|
|
|
//Isolate few attributes
|
|
if(nodeName == 'style')
|
|
model.style = this.parseStyle(nodeValue);
|
|
else if(nodeName == 'class')
|
|
model.classes = this.parseClass(nodeValue);
|
|
else if (nodeName == 'contenteditable')
|
|
continue;
|
|
else
|
|
model.attributes[nodeName] = nodeValue;
|
|
}
|
|
|
|
// Check for nested elements
|
|
var nodeChild = node.childNodes.length;
|
|
if(nodeChild){
|
|
// Avoid infinite text nodes nesting
|
|
var firstChild = node.childNodes[0];
|
|
if(nodeChild === 1 && firstChild.nodeType === 3){
|
|
if(!model.type){
|
|
model.type = 'text';
|
|
}
|
|
model.content = firstChild.nodeValue;
|
|
}else{
|
|
var parsed = this.parseNode(node);
|
|
// From: <div> <span>TEST</span> </div> <-- span is text type
|
|
// TO: <div> TEST </div> <-- div become text type
|
|
// With 'nodeChild > 1' I know that nodes were merged
|
|
if(parsed.length == 1 && parsed[0].type == 'text' &&
|
|
nodeChild > 1 && parsed[0].tagName == TEXT_NODE){
|
|
model.type = 'text';
|
|
model.content = parsed[0].content;
|
|
}else
|
|
model.components = parsed;
|
|
}
|
|
}
|
|
|
|
// Check if it's a text node and if could be moved to the prevous model
|
|
if(model.type == 'textnode'){
|
|
var prevIsText = prevSib && prevSib.type == 'textnode';
|
|
if(prevIsText){
|
|
prevSib.content += model.content;
|
|
continue;
|
|
}
|
|
// Throw away empty nodes (keep spaces)
|
|
var content = node.nodeValue;
|
|
if(content != ' ' && !content.trim()){
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// If all children are texts and there is some textnode the parent should
|
|
// be text too otherwise I'm unable to edit texnodes
|
|
var comps = model.components;
|
|
if(!model.type && comps){
|
|
var allTxt = 1;
|
|
var foundTextNode = 0;
|
|
for(var ci = 0; ci < comps.length; ci++){
|
|
var comp = comps[ci];
|
|
if(comp.type != 'text' &&
|
|
comp.type != 'textnode' &&
|
|
c.textTags.indexOf(comp.tagName) < 0 ){
|
|
allTxt = 0;
|
|
break;
|
|
}
|
|
if(comp.type == 'textnode')
|
|
foundTextNode = 1;
|
|
}
|
|
if(allTxt && foundTextNode)
|
|
model.type = 'text';
|
|
}
|
|
|
|
// If tagName is still empty and is not a textnode, do not push it
|
|
if(!model.tagName && model.type != 'textnode')
|
|
continue;
|
|
|
|
result.push(model);
|
|
}
|
|
|
|
return result;
|
|
},
|
|
|
|
/**
|
|
* Parse HTML string to a desired model object
|
|
* @param {string} str HTML string
|
|
* @param {ParserCss} parserCss In case there is style tags inside HTML
|
|
* @return {Object}
|
|
*/
|
|
parse: function(str, parserCss){
|
|
var res = { html: '', css: ''};
|
|
var el = document.createElement('div');
|
|
el.innerHTML = str;
|
|
var scripts = el.querySelectorAll('script');
|
|
var i = scripts.length;
|
|
|
|
// Remove all scripts
|
|
while (i--)
|
|
scripts[i].parentNode.removeChild(scripts[i]);
|
|
|
|
// Detach style tags and parse them
|
|
if(parserCss){
|
|
var styleStr = '';
|
|
var styles = el.querySelectorAll('style');
|
|
var j = styles.length;
|
|
|
|
while (j--){
|
|
styleStr = styles[j].innerHTML + styleStr;
|
|
styles[j].parentNode.removeChild(styles[j]);
|
|
}
|
|
|
|
if(styleStr)
|
|
res.css = parserCss.parse(styleStr);
|
|
}
|
|
|
|
var result = this.parseNode(el);
|
|
|
|
if(result.length == 1)
|
|
result = result[0];
|
|
|
|
res.html = result;
|
|
|
|
return res;
|
|
|
|
},
|
|
|
|
};
|
|
|
|
};
|
|
|
|
});
|
|
|