UNPKG

grapesjs_codeapps

Version:

Free and Open Source Web Builder Framework/SC Modification

252 lines (216 loc) 7.53 kB
module.exports = config => { var TEXT_NODE = 'span'; var c = config; var modelAttrStart = 'data-gjs-'; 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(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 .slice(1) .join(':') .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(str) { const result = []; const cls = str.split(' '); for (let i = 0, len = cls.length; i < len; i++) { const cl = cls[i].trim(); if (!cl) continue; result.push(cl); } return result; }, /** * Get data from the node element * @param {HTMLElement} el DOM element to traverse * @return {Array<Object>} */ parseNode(el) { const result = []; const nodes = el.childNodes; for (var i = 0, len = nodes.length; i < len; i++) { const node = nodes[i]; const attrs = node.attributes || []; const attrsLen = attrs.length; const nodePrev = result[result.length - 1]; const nodeChild = node.childNodes.length; const ct = this.compTypes; let model = {}; // Start with understanding what kind of component it is if (ct) { let obj = ''; let type = node.getAttribute && node.getAttribute(`${modelAttrStart}type`); // If the type is already defined, use it if (type) { model = { type }; } else { // Iterate over all available Component Types and // the first with a valid result will be that component for (let it = 0; it < ct.length; it++) { obj = ct[it].model.isComponent(node); if (obj) break; } model = obj; } } // Set tag name if not yet done if (!model.tagName) { model.tagName = node.tagName ? node.tagName.toLowerCase() : ''; } if (attrsLen) { model.attributes = {}; } // Parse attributes for (let j = 0; j < attrsLen; j++) { const nodeName = attrs[j].nodeName; let nodeValue = attrs[j].nodeValue; // Isolate attributes if (nodeName == 'style') { model.style = this.parseStyle(nodeValue); } else if (nodeName == 'class') { model.classes = this.parseClass(nodeValue); } else if (nodeName == 'contenteditable') { continue; } else if (nodeName.indexOf(modelAttrStart) === 0) { const modelAttr = nodeName.replace(modelAttrStart, ''); const valueLen = nodeValue.length; const firstChar = nodeValue && nodeValue.substr(0, 1); const lastChar = nodeValue && nodeValue.substr(valueLen - 1); nodeValue = nodeValue === 'true' ? true : nodeValue; nodeValue = nodeValue === 'false' ? false : nodeValue; // Try to parse JSON where it's possible // I can get false positive here (eg. a selector '[data-attr]') // so put it under try/catch and let fail silently try { nodeValue = (firstChar == '{' && lastChar == '}') || (firstChar == '[' && lastChar == ']') ? JSON.parse(nodeValue) : nodeValue; } catch (e) {} model[modelAttr] = nodeValue; } else { model.attributes[nodeName] = nodeValue; } } // Check for nested elements but avoid it if already provided if (nodeChild && !model.components) { // Avoid infinite nested text nodes const firstChild = node.childNodes[0]; // If there is only one child and it's a TEXTNODE // just make it content of the current node if (nodeChild === 1 && firstChild.nodeType === 3) { !model.type && (model.type = 'text'); model.content = firstChild.nodeValue; } else { model.components = this.parseNode(node); } } // Check if it's a text node and if could be moved to the prevous model if (model.type == 'textnode') { if (nodePrev && nodePrev.type == 'textnode') { nodePrev.content += model.content; continue; } // Throw away empty nodes (keep spaces) if (!config.keepEmptyTextNodes) { const 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 const comps = model.components; if (!model.type && comps) { let allTxt = 1; let foundTextNode = 0; for (let ci = 0; ci < comps.length; ci++) { const comp = comps[ci]; const cType = comp.type; if ( ['text', 'textnode'].indexOf(cType) < 0 && c.textTags.indexOf(comp.tagName) < 0 ) { allTxt = 0; break; } if (cType == '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(str, parserCss) { var config = (c.em && c.em.get('Config')) || {}; var res = { html: '', css: '' }; var el = document.createElement('div'); el.innerHTML = str; var scripts = el.querySelectorAll('script'); var i = scripts.length; // Remove all scripts if (!config.allowScripts) { 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; } }; };