From e403193e8436c49855ae0c574029c58120a9ad16 Mon Sep 17 00:00:00 2001 From: openorclose Date: Sun, 1 Mar 2020 22:22:49 +0800 Subject: [PATCH 1/3] Allow changing parameter properties --- .eslintrc.js | 1 + src/lib/markbind/src/parser.js | 301 +++++++++--------- .../markbind/src/parsers/componentParser.js | 42 +-- 3 files changed, 164 insertions(+), 180 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index e5049bf1bf..b7a079c568 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -29,6 +29,7 @@ module.exports = { "lodash/prefer-lodash-method": [0], "lodash/prefer-noop": [0], "max-len": ["error", { "code": 110 }], + "no-param-reassign": ["error", { "props": false }], "operator-linebreak": ["error", "before"], // override airbnb-base dev dependencies, latest version does not white list __mocks__ "import/no-extraneous-dependencies": [ diff --git a/src/lib/markbind/src/parser.js b/src/lib/markbind/src/parser.js index 7a8fc3a11c..8abf0282bf 100644 --- a/src/lib/markbind/src/parser.js +++ b/src/lib/markbind/src/parser.js @@ -129,19 +129,19 @@ class Parser { return _.clone(this.missingIncludeSrc); } - static _preprocessThumbnails(element) { - const isImage = _.hasIn(element.attribs, 'src') && element.attribs.src !== ''; + static _preprocessThumbnails(node) { + const isImage = _.hasIn(node.attribs, 'src') && node.attribs.src !== ''; if (isImage) { - return element; + return node; } - const text = _.hasIn(element.attribs, 'text') ? element.attribs.text : ''; + const text = _.hasIn(node.attribs, 'text') ? node.attribs.text : ''; if (text === '') { - return element; + return node; } const renderedText = md.renderInline(text); // eslint-disable-next-line no-param-reassign - element.children = cheerio.parseHTML(renderedText); - return element; + node.children = cheerio.parseHTML(renderedText); + return node; } _renderIncludeFile(filePath, element, context, config, asIfAt = filePath) { @@ -209,86 +209,84 @@ class Parser { } _preprocess(node, context, config) { - const element = node; - const self = this; - element.attribs = element.attribs || {}; - element.attribs[ATTRIB_CWF] = path.resolve(context.cwf); - if (element.name === 'thumbnail') { - return Parser._preprocessThumbnails(element); + node.attribs = node.attribs || {}; + node.attribs[ATTRIB_CWF] = path.resolve(context.cwf); + if (node.name === 'thumbnail') { + return Parser._preprocessThumbnails(node); } - const requiresSrc = ['include'].includes(element.name); - if (requiresSrc && _.isEmpty(element.attribs.src)) { - const error = new Error(`Empty src attribute in ${element.name} in: ${element.attribs[ATTRIB_CWF]}`); + const requiresSrc = ['include'].includes(node.name); + if (requiresSrc && _.isEmpty(node.attribs.src)) { + const error = new Error(`Empty src attribute in ${node.name} in: ${node.attribs[ATTRIB_CWF]}`); this._onError(error); - return Parser.createErrorNode(element, error); + return Parser.createErrorNode(node, error); } - const shouldProcessSrc = ['include', 'panel'].includes(element.name); - const hasSrc = _.hasIn(element.attribs, 'src'); + const shouldProcessSrc = ['include', 'panel'].includes(node.name); + const hasSrc = _.hasIn(node.attribs, 'src'); let isUrl; let includeSrc; let filePath; let actualFilePath; if (hasSrc && shouldProcessSrc) { - isUrl = utils.isUrl(element.attribs.src); - includeSrc = url.parse(element.attribs.src); + isUrl = utils.isUrl(node.attribs.src); + includeSrc = url.parse(node.attribs.src); filePath = isUrl - ? element.attribs.src + ? node.attribs.src : path.resolve(path.dirname(context.cwf), decodeURIComponent(includeSrc.path)); actualFilePath = filePath; - const isBoilerplate = _.hasIn(element.attribs, 'boilerplate'); + const isBoilerplate = _.hasIn(node.attribs, 'boilerplate'); if (isBoilerplate) { - element.attribs.boilerplate = element.attribs.boilerplate || path.basename(filePath); + node.attribs.boilerplate = node.attribs.boilerplate || path.basename(filePath); actualFilePath - = Parser.calculateBoilerplateFilePath(element.attribs.boilerplate, filePath, config); + = Parser.calculateBoilerplateFilePath(node.attribs.boilerplate, filePath, config); this.boilerplateIncludeSrc.push({ from: context.cwf, to: actualFilePath }); } - const isOptional = element.name === 'include' && _.hasIn(element.attribs, 'optional'); + const isOptional = node.name === 'include' && _.hasIn(node.attribs, 'optional'); if (!utils.fileExists(actualFilePath)) { if (isOptional) { return Parser.createEmptyNode(); } this.missingIncludeSrc.push({ from: context.cwf, to: actualFilePath }); const error - = new Error(`No such file: ${actualFilePath}\nMissing reference in ${element.attribs[ATTRIB_CWF]}`); + = new Error(`No such file: ${actualFilePath}\nMissing reference in ${node.attribs[ATTRIB_CWF]}`); this._onError(error); - return Parser.createErrorNode(element, error); + return Parser.createErrorNode(node, error); } } - if (element.name === 'include') { - const isInline = _.hasIn(element.attribs, 'inline'); - const isDynamic = _.hasIn(element.attribs, 'dynamic'); - const isOptional = _.hasIn(element.attribs, 'optional'); - const isTrim = _.hasIn(element.attribs, 'trim'); - element.name = isInline ? 'span' : 'div'; - element.attribs[ATTRIB_INCLUDE_PATH] = filePath; + if (node.name === 'include') { + const isInline = _.hasIn(node.attribs, 'inline'); + const isDynamic = _.hasIn(node.attribs, 'dynamic'); + const isOptional = _.hasIn(node.attribs, 'optional'); + const isTrim = _.hasIn(node.attribs, 'trim'); + node.name = isInline ? 'span' : 'div'; + node.attribs[ATTRIB_INCLUDE_PATH] = filePath; if (isOptional && !includeSrc.hash) { // optional includes of whole files have been handled, but segments still need to be processed - delete element.attribs.optional; + delete node.attribs.optional; } if (isDynamic) { - element.name = 'panel'; - element.attribs.src = filePath; - element.attribs['no-close'] = true; - element.attribs['no-switch'] = true; + node.name = 'panel'; + node.attribs.src = filePath; + node.attribs['no-close'] = true; + node.attribs['no-switch'] = true; if (includeSrc.hash) { - element.attribs.fragment = includeSrc.hash.substring(1); + node.attribs.fragment = includeSrc.hash.substring(1); } - element.attribs.header = element.attribs.name || ''; - delete element.attribs.dynamic; - this.dynamicIncludeSrc.push({ from: context.cwf, to: actualFilePath, asIfTo: element.attribs.src }); - return element; + node.attribs.header = node.attribs.name || ''; + delete node.attribs.dynamic; + this.dynamicIncludeSrc.push({ from: context.cwf, to: actualFilePath, asIfTo: node.attribs.src }); + return node; } if (isUrl) { - return element; // only keep url path for dynamic + return node; // only keep url path for dynamic } this.staticIncludeSrc.push({ from: context.cwf, to: actualFilePath }); const isIncludeSrcMd = utils.isMarkdownFileExt(utils.getExt(filePath)); if (isIncludeSrcMd && context.source === 'html') { // HTML include markdown, use special tag to indicate markdown code. - element.name = 'markdown'; + node.name = 'markdown'; } const { content, childContext, userDefinedVariables } - = this._renderIncludeFile(actualFilePath, element, context, config, filePath); + = this._renderIncludeFile(actualFilePath, node, context, config, filePath); childContext.source = isIncludeSrcMd ? 'md' : 'html'; childContext.callStack.push(context.cwf); if (!Parser.PROCESSED_INNER_VARIABLES.has(filePath)) { @@ -298,15 +296,15 @@ class Parser { const innerVariables = this.getImportedVariableMap(filePath); const fileContent = nunjucks.renderString(content, { ...userDefinedVariables, ...innerVariables }); // Delete variable attributes in include - Object.keys(element.attribs).forEach((attribute) => { + Object.keys(node.attribs).forEach((attribute) => { if (attribute.startsWith('var-')) { - delete element.attribs[attribute]; + delete node.attribs[attribute]; } }); - delete element.attribs.boilerplate; - delete element.attribs.src; - delete element.attribs.inline; - delete element.attribs.trim; + delete node.attribs.boilerplate; + delete node.attribs.src; + delete node.attribs.inline; + delete node.attribs.trim; if (includeSrc.hash) { // directly get segment from the src const segmentSrc = cheerio.parseHTML(fileContent, true); @@ -320,14 +318,14 @@ class Parser { } else { const error = new Error(`No such segment '${includeSrc.hash.substring(1)}' in file: ${actualFilePath}` - + `\nMissing reference in ${element.attribs[ATTRIB_CWF]}`); + + `\nMissing reference in ${node.attribs[ATTRIB_CWF]}`); this._onError(error); - return Parser.createErrorNode(element, error); + return Parser.createErrorNode(node, error); } } if (isOptional) { // optional includes of segments have now been handled, so delete the attribute - delete element.attribs.optional; + delete node.attribs.optional; } if (isIncludeSrcMd) { if (context.mode === 'include') { @@ -335,10 +333,10 @@ class Parser { } else { actualContent = md.render(actualContent); } - actualContent = Parser._rebaseReferenceForStaticIncludes(actualContent, element, config); + actualContent = Parser._rebaseReferenceForStaticIncludes(actualContent, node, config); } const wrapperType = isInline ? 'span' : 'div'; - element.children + node.children = cheerio.parseHTML( `<${wrapperType} data-included-from="${filePath}">${actualContent}`, true); @@ -352,52 +350,58 @@ class Parser { } } const wrapperType = isInline ? 'span' : 'div'; - element.children + node.children = cheerio.parseHTML( `<${wrapperType} data-included-from="${filePath}">${actualContent}`, true); } - if (element.children && element.children.length > 0) { + if (node.children && node.children.length > 0) { if (childContext.callStack.length > CyclicReferenceError.MAX_RECURSIVE_DEPTH) { const error = new CyclicReferenceError(childContext.callStack); this._onError(error); - return Parser.createErrorNode(element, error); + return Parser.createErrorNode(node, error); } - element.children = element.children.map(e => self._preprocess(e, childContext, config)); + node.children = node.children.map(e => this._preprocess(e, childContext, config)); } - } else if ((element.name === 'panel') && hasSrc) { + } else if ((node.name === 'panel') && hasSrc) { if (!isUrl && includeSrc.hash) { - element.attribs.fragment = includeSrc.hash.substring(1); // save hash to fragment attribute + node.attribs.fragment = includeSrc.hash.substring(1); // save hash to fragment attribute } - element.attribs.src = filePath; + node.attribs.src = filePath; this.dynamicIncludeSrc.push({ from: context.cwf, to: actualFilePath, asIfTo: filePath }); - return element; - } else if (element.name === 'variable' || element.name === 'import') { + return node; + } else if (node.name === 'variable' || node.name === 'import') { return Parser.createEmptyNode(); } else { - if (element.name === 'body') { + if (node.name === 'body') { // eslint-disable-next-line no-console - console.warn(` tag found in ${element.attribs[ATTRIB_CWF]}. This may cause formatting errors.`); + console.warn(` tag found in ${node.attribs[ATTRIB_CWF]}. This may cause formatting errors.`); } - if (element.children && element.children.length > 0) { - element.children = element.children.map(e => self._preprocess(e, context, config)); + if (node.children && node.children.length > 0) { + node.children = node.children.map(e => this._preprocess(e, context, config)); } } - return element; + return node; } processDynamicResources(context, html) { - const self = this; const $ = cheerio.load(html, { xmlMode: false, decodeEntities: false, }); + + const { rootPath } = this; + function getAbsoluteResourcePath(elem, relativeResourcePath) { + const firstParent = elem.closest('div[data-included-from], span[data-included-from]'); + const originalSrc = utils.ensurePosix(firstParent.attr('data-included-from') || context); + const originalSrcFolder = path.posix.dirname(originalSrc); + const fullResourcePath = path.posix.join(originalSrcFolder, relativeResourcePath); + const resolvedResourcePath = path.posix.relative(utils.ensurePosix(rootPath), fullResourcePath); + return path.posix.join('{{hostBaseUrl}}', resolvedResourcePath); + } + $('img, pic, thumbnail').each(function () { const elem = $(this); - if (elem[0].name === 'thumbnail' && elem.attr('src') === undefined) { - // Thumbnail tag without src - return; - } const resourcePath = utils.ensurePosix(elem.attr('src')); if (resourcePath === undefined || resourcePath === '') { // Found empty img/pic resource in resourcePath @@ -407,12 +411,7 @@ class Parser { // Do not rewrite. return; } - const firstParent = elem.closest('div[data-included-from], span[data-included-from]'); - const originalSrc = utils.ensurePosix(firstParent.attr('data-included-from') || context); - const originalSrcFolder = path.posix.dirname(originalSrc); - const fullResourcePath = path.posix.join(originalSrcFolder, resourcePath); - const resolvedResourcePath = path.posix.relative(utils.ensurePosix(self.rootPath), fullResourcePath); - const absoluteResourcePath = path.posix.join('{{hostBaseUrl}}', resolvedResourcePath); + const absoluteResourcePath = getAbsoluteResourcePath(elem, resourcePath); $(this).attr('src', absoluteResourcePath); }); $('a, link').each(function () { @@ -426,12 +425,7 @@ class Parser { // Do not rewrite. return; } - const firstParent = elem.closest('div[data-included-from], span[data-included-from]'); - const originalSrc = utils.ensurePosix(firstParent.attr('data-included-from') || context); - const originalSrcFolder = path.posix.dirname(originalSrc); - const fullResourcePath = path.posix.join(originalSrcFolder, resourcePath); - const resolvedResourcePath = path.posix.relative(utils.ensurePosix(self.rootPath), fullResourcePath); - const absoluteResourcePath = path.posix.join('{{hostBaseUrl}}', resolvedResourcePath); + const absoluteResourcePath = getAbsoluteResourcePath(elem, resourcePath); $(this).attr('href', absoluteResourcePath); }); return $.html(); @@ -449,20 +443,18 @@ class Parser { } _parse(node, context, config) { - const element = node; - const self = this; - if (_.isArray(element)) { - return element.map(el => self._parse(el, context, config)); + if (_.isArray(node)) { + return node.map(el => this._parse(el, context, config)); } - if (Parser.isText(element)) { - return element; + if (Parser.isText(node)) { + return node; } - if (element.name) { - element.name = element.name.toLowerCase(); + if (node.name) { + node.name = node.name.toLowerCase(); } - if ((/^h[1-6]$/).test(element.name) && !element.attribs.id) { - const textContent = utils.getTextContent(element); + if ((/^h[1-6]$/).test(node.name) && !node.attribs.id) { + const textContent = utils.getTextContent(node); const slugifiedHeading = slugify(textContent, { decamelize: false }); let headerId = slugifiedHeading; @@ -474,82 +466,82 @@ class Parser { headerIdMap[slugifiedHeading] = 2; } - element.attribs.id = headerId; + node.attribs.id = headerId; } - switch (element.name) { + switch (node.name) { case 'md': - element.name = 'span'; + node.name = 'span'; cheerio.prototype.options.xmlMode = false; - element.children = cheerio.parseHTML(md.renderInline(cheerio.html(element.children)), true); + node.children = cheerio.parseHTML(md.renderInline(cheerio.html(node.children)), true); cheerio.prototype.options.xmlMode = true; break; case 'markdown': - element.name = 'div'; + node.name = 'div'; cheerio.prototype.options.xmlMode = false; - element.children = cheerio.parseHTML(md.render(cheerio.html(element.children)), true); + node.children = cheerio.parseHTML(md.render(cheerio.html(node.children)), true); cheerio.prototype.options.xmlMode = true; break; case 'panel': { - if (!_.hasIn(element.attribs, 'src')) { // dynamic panel + if (!_.hasIn(node.attribs, 'src')) { // dynamic panel break; } - const fileExists = utils.fileExists(element.attribs.src) + const fileExists = utils.fileExists(node.attribs.src) || utils.fileExists( Parser.calculateBoilerplateFilePath( - element.attribs.boilerplate, - element.attribs.src, config)); + node.attribs.boilerplate, + node.attribs.src, config)); if (fileExists) { - const { src, fragment } = element.attribs; + const { src, fragment } = node.attribs; const resultDir = path.dirname(path.join('{{hostBaseUrl}}', path.relative(config.rootPath, src))); const resultPath = path.join(resultDir, utils.setExtension(path.basename(src), '._include_.html')); - element.attribs.src = utils.ensurePosix(fragment ? `${resultPath}#${fragment}` : resultPath); + node.attribs.src = utils.ensurePosix(fragment ? `${resultPath}#${fragment}` : resultPath); } - delete element.attribs.boilerplate; + delete node.attribs.boilerplate; break; } default: break; } - componentParser.parseComponents(element, this._onError); + componentParser.parseComponents(node, this._onError); - if (element.children) { - element.children.forEach((child) => { - self._parse(child, context, config); + if (node.children) { + node.children.forEach((child) => { + this._parse(child, context, config); }); } - componentParser.postParseComponents(element, this._onError); + componentParser.postParseComponents(node, this._onError); - return element; + return node; } _trimNodes(node) { - const self = this; if (node.name === 'pre' || node.name === 'code') { return; } if (node.children) { - /* eslint-disable no-plusplus */ - for (let n = 0; n < node.children.length; n++) { - const child = node.children[n]; - if (child.type === 'comment' - || (child.type === 'text' && n === node.children.length - 1 && !/\S/.test(child.data))) { - node.children.splice(n, 1); - n--; + node.children = node.children.filter((child) => { + if (child.type === 'comment') { + return false; } else if (child.type === 'tag') { - self._trimNodes(child); + this._trimNodes(child); } + return true; + }); + + // IF the last child is an empty text node, pop it out. + const lastChild = node.children[node.children.length - 1]; + if (lastChild && lastChild.type === 'text' && !/\S/.test(lastChild.data)) { + node.children.pop(); } - /* eslint-enable no-plusplus */ } } preprocess(file, pageData, context, config) { - const currentContext = context; - currentContext.mode = 'include'; - currentContext.callStack = []; + context.mode = 'include'; + context.callStack = []; return new Promise((resolve, reject) => { const handler = new htmlparser.DomHandler((error, dom) => { @@ -560,7 +552,7 @@ class Parser { const nodes = dom.map((d) => { let processed; try { - processed = this._preprocess(d, currentContext, config); + processed = this._preprocess(d, context, config); } catch (err) { err.message += `\nError while preprocessing '${file}'`; this._onError(err); @@ -588,8 +580,8 @@ class Parser { ...additionalVariables, }, { path: file }); - this._extractInnerVariables(fileContent, currentContext, config); - const innerVariables = this.getImportedVariableMap(currentContext.cwf); + this._extractInnerVariables(fileContent, context, config); + const innerVariables = this.getImportedVariableMap(context.cwf); fileContent = nunjucks.renderString(fileContent, { ...userDefinedVariables, ...additionalVariables, @@ -597,10 +589,10 @@ class Parser { }); const fileExt = utils.getExt(file); if (utils.isMarkdownFileExt(fileExt)) { - currentContext.source = 'md'; + context.source = 'md'; parser.parseComplete(fileContent.toString()); } else if (fileExt === 'html') { - currentContext.source = 'html'; + context.source = 'html'; parser.parseComplete(fileContent); } else { const error = new Error(`Unsupported File Extension: '${fileExt}'`); @@ -789,21 +781,20 @@ class Parser { } _rebaseReference(node, foundBase) { - const element = node; - if (_.isArray(element)) { - return element.map(el => this._rebaseReference(el, foundBase)); + if (_.isArray(node)) { + return node.map(el => this._rebaseReference(el, foundBase)); } - if (Parser.isText(element)) { - return element; + if (Parser.isText(node)) { + return node; } // Rebase children element const childrenBase = {}; - element.children.forEach((el) => { + node.children.forEach((el) => { this._rebaseReference(el, childrenBase); }); // rebase current element - if (element.attribs[ATTRIB_INCLUDE_PATH]) { - const filePath = element.attribs[ATTRIB_INCLUDE_PATH]; + if (node.attribs[ATTRIB_INCLUDE_PATH]) { + const filePath = node.attribs[ATTRIB_INCLUDE_PATH]; let newBase = Parser.calculateNewBaseUrls(filePath, this.rootPath, this.baseUrlMap); if (newBase) { const { relative, parent } = newBase; @@ -815,10 +806,10 @@ class Parser { if (bases.length !== 0) { // need to rebase newBase = combinedBases[bases[0]]; - const { children } = element; + const { children } = node; if (children) { const currentBase - = Parser.calculateNewBaseUrls(element.attribs[ATTRIB_CWF], this.rootPath, this.baseUrlMap); + = Parser.calculateNewBaseUrls(node.attribs[ATTRIB_CWF], this.rootPath, this.baseUrlMap); if (currentBase) { if (currentBase.relative !== newBase) { cheerio.prototype.options.xmlMode = false; @@ -829,16 +820,16 @@ class Parser { hostBaseUrl: '{{hostBaseUrl}}', baseUrl: newBaseUrl, }, { path: filePath }); - element.children = cheerio.parseHTML(rendered, true); + node.children = cheerio.parseHTML(rendered, true); cheerio.prototype.options.xmlMode = true; } } } } - delete element.attribs[ATTRIB_INCLUDE_PATH]; + delete node.attribs[ATTRIB_INCLUDE_PATH]; } - delete element.attribs[ATTRIB_CWF]; - return element; + delete node.attribs[ATTRIB_CWF]; + return node; } static _rebaseReferenceForStaticIncludes(pageData, element, config) { @@ -900,9 +891,9 @@ class Parser { return path.resolve(parent, relative, BOILERPLATE_FOLDER_NAME, pathInBoilerplates); } - static createErrorNode(element, error) { + static createErrorNode(node, error) { const errorElement = cheerio.parseHTML(utils.createErrorElement(error), true)[0]; - return Object.assign(element, _.pick(errorElement, ['name', 'attribs', 'children'])); + return Object.assign(node, _.pick(errorElement, ['name', 'attribs', 'children'])); } static createEmptyNode() { @@ -910,8 +901,8 @@ class Parser { return emptyElement; } - static isText(element) { - return element.type === 'text' || element.type === 'comment'; + static isText(node) { + return node.type === 'text' || node.type === 'comment'; } /** diff --git a/src/lib/markbind/src/parsers/componentParser.js b/src/lib/markbind/src/parsers/componentParser.js index 982fc2b596..b687bf45ff 100644 --- a/src/lib/markbind/src/parsers/componentParser.js +++ b/src/lib/markbind/src/parsers/componentParser.js @@ -21,25 +21,24 @@ cheerio.prototype.options.decodeEntities = false; // Don't escape HTML entities * @param slotName Name attribute of the element to insert, which defaults to the attribute name */ function _parseAttributeWithoutOverride(element, attribute, isInline, slotName = attribute) { - const el = element; + const hasAttributeSlot = element.children + && element.children.some(child => _.has(child.attribs, 'slot') && child.attribs.slot === slotName); - const hasAttributeSlot = el.children - && el.children.some(child => _.has(child.attribs, 'slot') && child.attribs.slot === slotName); - - if (!hasAttributeSlot && _.has(el.attribs, attribute)) { + if (!hasAttributeSlot && _.has(element.attribs, attribute)) { let rendered; if (isInline) { - rendered = vueAttrRenderer.renderInline(el.attribs[attribute]); + rendered = vueAttrRenderer.renderInline(element.attribs[attribute]); } else { - rendered = vueAttrRenderer.render(el.attribs[attribute]); + rendered = vueAttrRenderer.render(element.attribs[attribute]); } const attributeSlotElement = cheerio.parseHTML( ``, true); - el.children = el.children ? attributeSlotElement.concat(el.children) : attributeSlotElement; + element.children + = element.children ? attributeSlotElement.concat(element.children) : attributeSlotElement; } - delete el.attribs[attribute]; + delete element.attribs[attribute]; } /* @@ -47,17 +46,15 @@ function _parseAttributeWithoutOverride(element, attribute, isInline, slotName = */ function _parsePanelAttributes(element) { - const el = element; - _parseAttributeWithoutOverride(element, 'alt', false, '_alt'); - const slotChildren = el.children && el.children.filter(child => _.has(child.attribs, 'slot')); + const slotChildren = element.children && element.children.filter(child => _.has(child.attribs, 'slot')); const hasAltSlot = slotChildren && slotChildren.some(child => child.attribs.slot === '_alt'); const hasHeaderSlot = slotChildren && slotChildren.some(child => child.attribs.slot === 'header'); // If both are present, the header attribute has no effect, and we can simply remove it. if (hasAltSlot && hasHeaderSlot) { - delete el.attribs.header; + delete element.attribs.header; return; } @@ -97,9 +94,7 @@ function _findHeaderElement(element) { * @param element The root panel element */ function _assignPanelId(element) { - const el = element; - - const slotChildren = el.children && el.children.filter(child => _.has(child.attribs, 'slot')); + const slotChildren = element.children && element.children.filter(child => _.has(child.attribs, 'slot')); const headerSlot = slotChildren.find(child => child.attribs.slot === 'header'); const headerAttributeSlot = slotChildren.find(child => child.attribs.slot === '_header'); @@ -115,7 +110,7 @@ function _assignPanelId(element) { + 'Please report this to the MarkBind developers. Thank you!'); } - el.attribs.id = header.attribs.id; + element.attribs.id = header.attribs.id; } } @@ -145,9 +140,7 @@ function _parseTooltipAttributes(element) { function _renameSlot(element, originalName, newName) { if (element.children) { - element.children.forEach((c) => { - const child = c; - + element.children.forEach((child) => { if (_.has(child.attribs, 'slot') && child.attribs.slot === originalName) { child.attribs.slot = newName; } @@ -192,22 +185,21 @@ function _parseBoxAttributes(element) { */ function _parseDropdownAttributes(element) { - const el = element; - const slotChildren = el.children && el.children.filter(child => _.has(child.attribs, 'slot')); + const slotChildren = element.children && element.children.filter(child => _.has(child.attribs, 'slot')); const hasHeaderSlot = slotChildren && slotChildren.some(child => child.attribs.slot === 'header'); // If header slot is present, the header attribute has no effect, and we can simply remove it. if (hasHeaderSlot) { - delete el.attribs.header; + delete element.attribs.header; // TODO deprecate text attribute of dropdown - delete el.attribs.text; + delete element.attribs.text; return; } // header attribute takes priority over text attribute if (_.has(element.attribs, 'header')) { _parseAttributeWithoutOverride(element, 'header', true, '_header'); - delete el.attribs.text; + delete element.attribs.text; } else { // TODO deprecate text attribute of dropdown _parseAttributeWithoutOverride(element, 'text', true, '_header'); From ceb072a5862b7559c878ffa24e5f82587c2e8a86 Mon Sep 17 00:00:00 2001 From: openorclose Date: Wed, 4 Mar 2020 19:48:09 +0800 Subject: [PATCH 2/3] Rename element to node --- src/lib/markbind/src/parser.js | 34 ++--- .../markbind/src/parsers/componentParser.js | 124 +++++++++--------- 2 files changed, 79 insertions(+), 79 deletions(-) diff --git a/src/lib/markbind/src/parser.js b/src/lib/markbind/src/parser.js index 8abf0282bf..7d4677faf4 100644 --- a/src/lib/markbind/src/parser.js +++ b/src/lib/markbind/src/parser.js @@ -76,15 +76,15 @@ class Parser { * ed variables have not been processed yet, we replace such variables with itself first. */ const importedVariables = {}; - $('import[from]').each((index, element) => { - const variableNames = Object.keys(element.attribs) + $('import[from]').each((index, node) => { + const variableNames = Object.keys(node.attribs) .filter(name => name !== 'from' && name !== 'as'); // If no namespace is provided, we use the smallest name as one... const largestName = variableNames.sort()[0]; // ... and prepend it with $__MARKBIND__ to reduce collisions. const generatedAlias = IMPORTED_VARIABLE_PREFIX + largestName; - const hasAlias = _.hasIn(element.attribs, 'as'); - const alias = hasAlias ? element.attribs.as : generatedAlias; + const hasAlias = _.hasIn(node.attribs, 'as'); + const alias = hasAlias ? node.attribs.as : generatedAlias; importedVariables[alias] = new Proxy({}, { get(obj, prop) { return `{{${alias}.${prop}}}`; @@ -144,22 +144,22 @@ class Parser { return node; } - _renderIncludeFile(filePath, element, context, config, asIfAt = filePath) { + _renderIncludeFile(filePath, node, context, config, asIfAt = filePath) { try { this._fileCache[filePath] = this._fileCache[filePath] ? this._fileCache[filePath] : fs.readFileSync(filePath, 'utf8'); } catch (e) { // Read file fail - const missingReferenceErrorMessage = `Missing reference in: ${element.attribs[ATTRIB_CWF]}`; + const missingReferenceErrorMessage = `Missing reference in: ${node.attribs[ATTRIB_CWF]}`; e.message += `\n${missingReferenceErrorMessage}`; this._onError(e); - return Parser.createErrorNode(element, e); + return Parser.createErrorNode(node, e); } const fileContent = this._fileCache[filePath]; // cache the file contents to save some I/O const { parent, relative } = Parser.calculateNewBaseUrls(asIfAt, config.rootPath, config.baseUrlMap); const userDefinedVariables = config.userDefinedVariablesMap[path.resolve(parent, relative)]; // Extract included variables from the PARENT file - const includeVariables = Parser.extractIncludeVariables(element, context.variables); + const includeVariables = Parser.extractIncludeVariables(node, context.variables); // Extract page variables from the CHILD file const pageVariables = this.extractPageVariables(asIfAt, fileContent, userDefinedVariables, includeVariables); @@ -181,22 +181,22 @@ class Parser { }); const aliases = new Map(); Parser.FILE_ALIASES.set(cwf, aliases); - $('import[from]').each((index, element) => { - const filePath = path.resolve(path.dirname(cwf), element.attribs.from); - const variableNames = Object.keys(element.attribs) + $('import[from]').each((index, node) => { + const filePath = path.resolve(path.dirname(cwf), node.attribs.from); + const variableNames = Object.keys(node.attribs) .filter(name => name !== 'from' && name !== 'as'); // If no namespace is provided, we use the smallest name as one const largestName = variableNames.sort()[0]; // ... and prepend it with $__MARKBIND__ to reduce collisions. const generatedAlias = IMPORTED_VARIABLE_PREFIX + largestName; - const alias = _.hasIn(element.attribs, 'as') - ? element.attribs.as + const alias = _.hasIn(node.attribs, 'as') + ? node.attribs.as : generatedAlias; aliases.set(alias, filePath); this.staticIncludeSrc.push({ from: context.cwf, to: filePath }); // Render inner file content const { content: renderedContent, childContext, userDefinedVariables } - = this._renderIncludeFile(filePath, element, context, config); + = this._renderIncludeFile(filePath, node, context, config); if (!Parser.PROCESSED_INNER_VARIABLES.has(filePath)) { Parser.PROCESSED_INNER_VARIABLES.add(filePath); this._extractInnerVariables(renderedContent, childContext, config); @@ -832,19 +832,19 @@ class Parser { return node; } - static _rebaseReferenceForStaticIncludes(pageData, element, config) { + static _rebaseReferenceForStaticIncludes(pageData, node, config) { if (!config) { return pageData; } if (!pageData.includes('{{baseUrl}}')) { return pageData; } - const filePath = element.attribs[ATTRIB_INCLUDE_PATH]; + const filePath = node.attribs[ATTRIB_INCLUDE_PATH]; const fileBase = Parser.calculateNewBaseUrls(filePath, config.rootPath, config.baseUrlMap); if (!fileBase.relative) { return pageData; } - const currentPath = element.attribs[ATTRIB_CWF]; + const currentPath = node.attribs[ATTRIB_CWF]; const currentBase = Parser.calculateNewBaseUrls(currentPath, config.rootPath, config.baseUrlMap); if (currentBase.relative === fileBase.relative) { return pageData; diff --git a/src/lib/markbind/src/parsers/componentParser.js b/src/lib/markbind/src/parsers/componentParser.js index b687bf45ff..f8285bea9b 100644 --- a/src/lib/markbind/src/parsers/componentParser.js +++ b/src/lib/markbind/src/parsers/componentParser.js @@ -15,59 +15,59 @@ cheerio.prototype.options.decodeEntities = false; // Don't escape HTML entities /** * Parses the markdown attribute of the provided element, inserting the corresponding child * if there is no pre-existing slot child with the name of the attribute present. - * @param element Element to parse + * @param node Element to parse * @param attribute Attribute name to parse * @param isInline Whether to parse the attribute with only inline markdown-it rules * @param slotName Name attribute of the element to insert, which defaults to the attribute name */ -function _parseAttributeWithoutOverride(element, attribute, isInline, slotName = attribute) { - const hasAttributeSlot = element.children - && element.children.some(child => _.has(child.attribs, 'slot') && child.attribs.slot === slotName); +function _parseAttributeWithoutOverride(node, attribute, isInline, slotName = attribute) { + const hasAttributeSlot = node.children + && node.children.some(child => _.has(child.attribs, 'slot') && child.attribs.slot === slotName); - if (!hasAttributeSlot && _.has(element.attribs, attribute)) { + if (!hasAttributeSlot && _.has(node.attribs, attribute)) { let rendered; if (isInline) { - rendered = vueAttrRenderer.renderInline(element.attribs[attribute]); + rendered = vueAttrRenderer.renderInline(node.attribs[attribute]); } else { - rendered = vueAttrRenderer.render(element.attribs[attribute]); + rendered = vueAttrRenderer.render(node.attribs[attribute]); } const attributeSlotElement = cheerio.parseHTML( ``, true); - element.children - = element.children ? attributeSlotElement.concat(element.children) : attributeSlotElement; + node.children + = node.children ? attributeSlotElement.concat(node.children) : attributeSlotElement; } - delete element.attribs[attribute]; + delete node.attribs[attribute]; } /* * Panels */ -function _parsePanelAttributes(element) { - _parseAttributeWithoutOverride(element, 'alt', false, '_alt'); +function _parsePanelAttributes(node) { + _parseAttributeWithoutOverride(node, 'alt', false, '_alt'); - const slotChildren = element.children && element.children.filter(child => _.has(child.attribs, 'slot')); + const slotChildren = node.children && node.children.filter(child => _.has(child.attribs, 'slot')); const hasAltSlot = slotChildren && slotChildren.some(child => child.attribs.slot === '_alt'); const hasHeaderSlot = slotChildren && slotChildren.some(child => child.attribs.slot === 'header'); // If both are present, the header attribute has no effect, and we can simply remove it. if (hasAltSlot && hasHeaderSlot) { - delete element.attribs.header; + delete node.attribs.header; return; } - _parseAttributeWithoutOverride(element, 'header', false, '_header'); + _parseAttributeWithoutOverride(node, 'header', false, '_header'); } /** * Traverses the dom breadth-first from the specified element to find a html heading child. - * @param element Root element to search from + * @param node Root element to search from * @returns {undefined|*} The header element, or undefined if none is found. */ -function _findHeaderElement(element) { - const elements = element.children; +function _findHeaderElement(node) { + const elements = node.children; if (!elements || !elements.length) { return undefined; } @@ -91,10 +91,10 @@ function _findHeaderElement(element) { * Assigns an id to the root element of a panel component using the heading specified in the * panel's header slot or attribute (if any), with the header slot having priority if present. * This is to ensure anchors still work when panels are in their minimized form. - * @param element The root panel element + * @param node The root panel element */ -function _assignPanelId(element) { - const slotChildren = element.children && element.children.filter(child => _.has(child.attribs, 'slot')); +function _assignPanelId(node) { + const slotChildren = node.children && node.children.filter(child => _.has(child.attribs, 'slot')); const headerSlot = slotChildren.find(child => child.attribs.slot === 'header'); const headerAttributeSlot = slotChildren.find(child => child.attribs.slot === '_header'); @@ -110,7 +110,7 @@ function _assignPanelId(element) { + 'Please report this to the MarkBind developers. Thank you!'); } - element.attribs.id = header.attribs.id; + node.attribs.id = header.attribs.id; } } @@ -119,28 +119,28 @@ function _assignPanelId(element) { * Popovers */ -function _parsePopoverAttributes(element) { - _parseAttributeWithoutOverride(element, 'content', true); - _parseAttributeWithoutOverride(element, 'header', true); +function _parsePopoverAttributes(node) { + _parseAttributeWithoutOverride(node, 'content', true); + _parseAttributeWithoutOverride(node, 'header', true); // TODO deprecate title attribute for popovers - _parseAttributeWithoutOverride(element, 'title', true, 'header'); + _parseAttributeWithoutOverride(node, 'title', true, 'header'); } /* * Tooltips */ -function _parseTooltipAttributes(element) { - _parseAttributeWithoutOverride(element, 'content', true, '_content'); +function _parseTooltipAttributes(node) { + _parseAttributeWithoutOverride(node, 'content', true, '_content'); } /* * Modals */ -function _renameSlot(element, originalName, newName) { - if (element.children) { - element.children.forEach((child) => { +function _renameSlot(node, originalName, newName) { + if (node.children) { + node.children.forEach((child) => { if (_.has(child.attribs, 'slot') && child.attribs.slot === originalName) { child.attribs.slot = newName; } @@ -148,34 +148,34 @@ function _renameSlot(element, originalName, newName) { } } -function _parseModalAttributes(element) { - _parseAttributeWithoutOverride(element, 'header', true, '_header'); +function _parseModalAttributes(node) { + _parseAttributeWithoutOverride(node, 'header', true, '_header'); // TODO deprecate title attribute for modals - _parseAttributeWithoutOverride(element, 'title', true, '_header'); + _parseAttributeWithoutOverride(node, 'title', true, '_header'); // TODO deprecate modal-header, modal-footer attributes for modals - _renameSlot(element, 'modal-header', 'header'); - _renameSlot(element, 'modal-footer', 'footer'); + _renameSlot(node, 'modal-header', 'header'); + _renameSlot(node, 'modal-footer', 'footer'); } /* * Tabs */ -function _parseTabAttributes(element) { - _parseAttributeWithoutOverride(element, 'header', true, '_header'); +function _parseTabAttributes(node) { + _parseAttributeWithoutOverride(node, 'header', true, '_header'); } /* * Tip boxes */ -function _parseBoxAttributes(element) { - _parseAttributeWithoutOverride(element, 'icon', true, '_icon'); - _parseAttributeWithoutOverride(element, 'header', false, '_header'); +function _parseBoxAttributes(node) { + _parseAttributeWithoutOverride(node, 'icon', true, '_icon'); + _parseAttributeWithoutOverride(node, 'header', false, '_header'); // TODO deprecate heading attribute for box - _parseAttributeWithoutOverride(element, 'heading', false, '_header'); + _parseAttributeWithoutOverride(node, 'heading', false, '_header'); // TODO warn when light and seamless attributes are both present } @@ -184,25 +184,25 @@ function _parseBoxAttributes(element) { * Dropdowns */ -function _parseDropdownAttributes(element) { - const slotChildren = element.children && element.children.filter(child => _.has(child.attribs, 'slot')); +function _parseDropdownAttributes(node) { + const slotChildren = node.children && node.children.filter(child => _.has(child.attribs, 'slot')); const hasHeaderSlot = slotChildren && slotChildren.some(child => child.attribs.slot === 'header'); // If header slot is present, the header attribute has no effect, and we can simply remove it. if (hasHeaderSlot) { - delete element.attribs.header; + delete node.attribs.header; // TODO deprecate text attribute of dropdown - delete element.attribs.text; + delete node.attribs.text; return; } // header attribute takes priority over text attribute - if (_.has(element.attribs, 'header')) { - _parseAttributeWithoutOverride(element, 'header', true, '_header'); - delete element.attribs.text; + if (_.has(node.attribs, 'header')) { + _parseAttributeWithoutOverride(node, 'header', true, '_header'); + delete node.attribs.text; } else { // TODO deprecate text attribute of dropdown - _parseAttributeWithoutOverride(element, 'text', true, '_header'); + _parseAttributeWithoutOverride(node, 'text', true, '_header'); } } @@ -210,30 +210,30 @@ function _parseDropdownAttributes(element) { * API */ -function parseComponents(element, errorHandler) { +function parseComponents(node, errorHandler) { try { - switch (element.name) { + switch (node.name) { case 'panel': - _parsePanelAttributes(element); + _parsePanelAttributes(node); break; case 'popover': - _parsePopoverAttributes(element); + _parsePopoverAttributes(node); break; case 'tooltip': - _parseTooltipAttributes(element); + _parseTooltipAttributes(node); break; case 'modal': - _parseModalAttributes(element); + _parseModalAttributes(node); break; case 'tab': case 'tab-group': - _parseTabAttributes(element); + _parseTabAttributes(node); break; case 'box': - _parseBoxAttributes(element); + _parseBoxAttributes(node); break; case 'dropdown': - _parseDropdownAttributes(element); + _parseDropdownAttributes(node); break; default: break; @@ -248,11 +248,11 @@ function parseComponents(element, errorHandler) { } } -function postParseComponents(element, errorHandler) { +function postParseComponents(node, errorHandler) { try { - switch (element.name) { + switch (node.name) { case 'panel': - _assignPanelId(element); + _assignPanelId(node); break; default: break; From 0f110ad3302333458f2a3e6342549bc17aff2e79 Mon Sep 17 00:00:00 2001 From: openorclose Date: Wed, 4 Mar 2020 20:05:39 +0800 Subject: [PATCH 3/3] Rename element to node --- src/lib/markbind/src/parser.js | 142 +++++++++--------- .../markbind/src/parsers/componentParser.js | 127 ++++++++-------- 2 files changed, 126 insertions(+), 143 deletions(-) diff --git a/src/lib/markbind/src/parser.js b/src/lib/markbind/src/parser.js index 7ba665129d..fa4d061776 100644 --- a/src/lib/markbind/src/parser.js +++ b/src/lib/markbind/src/parser.js @@ -126,22 +126,22 @@ class Parser { return _.clone(this.missingIncludeSrc); } - _renderIncludeFile(filePath, element, context, config, asIfAt = filePath) { + _renderIncludeFile(filePath, node, context, config, asIfAt = filePath) { try { this._fileCache[filePath] = this._fileCache[filePath] ? this._fileCache[filePath] : fs.readFileSync(filePath, 'utf8'); } catch (e) { // Read file fail - const missingReferenceErrorMessage = `Missing reference in: ${element.attribs[ATTRIB_CWF]}`; + const missingReferenceErrorMessage = `Missing reference in: ${node.attribs[ATTRIB_CWF]}`; e.message += `\n${missingReferenceErrorMessage}`; this._onError(e); - return utils.createErrorNode(element, e); + return utils.createErrorNode(node, e); } const fileContent = this._fileCache[filePath]; // cache the file contents to save some I/O const { parent, relative } = urlUtils.calculateNewBaseUrls(asIfAt, config.rootPath, config.baseUrlMap); const userDefinedVariables = config.userDefinedVariablesMap[path.resolve(parent, relative)]; // Extract included variables from the PARENT file - const includeVariables = Parser.extractIncludeVariables(element, context.variables); + const includeVariables = Parser.extractIncludeVariables(node, context.variables); // Extract page variables from the CHILD file const pageVariables = this.extractPageVariables(asIfAt, fileContent, userDefinedVariables, includeVariables); @@ -162,22 +162,22 @@ class Parser { }); const aliases = new Map(); Parser.FILE_ALIASES.set(cwf, aliases); - $('import[from]').each((index, element) => { - const filePath = path.resolve(path.dirname(cwf), element.attribs.from); - const variableNames = Object.keys(element.attribs) + $('import[from]').each((index, node) => { + const filePath = path.resolve(path.dirname(cwf), node.attribs.from); + const variableNames = Object.keys(node.attribs) .filter(name => name !== 'from' && name !== 'as'); // If no namespace is provided, we use the smallest name as one const largestName = variableNames.sort()[0]; // ... and prepend it with $__MARKBIND__ to reduce collisions. const generatedAlias = IMPORTED_VARIABLE_PREFIX + largestName; - const alias = _.hasIn(element.attribs, 'as') - ? element.attribs.as + const alias = _.hasIn(node.attribs, 'as') + ? node.attribs.as : generatedAlias; aliases.set(alias, filePath); this.staticIncludeSrc.push({ from: context.cwf, to: filePath }); // Render inner file content const { content: renderedContent, childContext, userDefinedVariables } - = this._renderIncludeFile(filePath, element, context, config); + = this._renderIncludeFile(filePath, node, context, config); this.extractInnerVariablesIfNotProcessed(renderedContent, childContext, config, filePath); const innerVariables = this.getImportedVariableMap(filePath); Parser.VARIABLE_LOOKUP.get(filePath).forEach((value, variableName, map) => { @@ -194,17 +194,23 @@ class Parser { } processDynamicResources(context, html) { - const self = this; const $ = cheerio.load(html, { xmlMode: false, decodeEntities: false, }); + + const { rootPath } = this; + function getAbsoluteResourcePath(elem, relativeResourcePath) { + const firstParent = elem.closest('div[data-included-from], span[data-included-from]'); + const originalSrc = utils.ensurePosix(firstParent.attr('data-included-from') || context); + const originalSrcFolder = path.posix.dirname(originalSrc); + const fullResourcePath = path.posix.join(originalSrcFolder, relativeResourcePath); + const resolvedResourcePath = path.posix.relative(utils.ensurePosix(rootPath), fullResourcePath); + return path.posix.join('{{hostBaseUrl}}', resolvedResourcePath); + } + $('img, pic, thumbnail').each(function () { const elem = $(this); - if (elem[0].name === 'thumbnail' && elem.attr('src') === undefined) { - // Thumbnail tag without src - return; - } const resourcePath = utils.ensurePosix(elem.attr('src')); if (resourcePath === undefined || resourcePath === '') { // Found empty img/pic resource in resourcePath @@ -214,12 +220,7 @@ class Parser { // Do not rewrite. return; } - const firstParent = elem.closest('div[data-included-from], span[data-included-from]'); - const originalSrc = utils.ensurePosix(firstParent.attr('data-included-from') || context); - const originalSrcFolder = path.posix.dirname(originalSrc); - const fullResourcePath = path.posix.join(originalSrcFolder, resourcePath); - const resolvedResourcePath = path.posix.relative(utils.ensurePosix(self.rootPath), fullResourcePath); - const absoluteResourcePath = path.posix.join('{{hostBaseUrl}}', resolvedResourcePath); + const absoluteResourcePath = getAbsoluteResourcePath(elem, resourcePath); $(this).attr('src', absoluteResourcePath); }); $('a, link').each(function () { @@ -233,12 +234,7 @@ class Parser { // Do not rewrite. return; } - const firstParent = elem.closest('div[data-included-from], span[data-included-from]'); - const originalSrc = utils.ensurePosix(firstParent.attr('data-included-from') || context); - const originalSrcFolder = path.posix.dirname(originalSrc); - const fullResourcePath = path.posix.join(originalSrcFolder, resourcePath); - const resolvedResourcePath = path.posix.relative(utils.ensurePosix(self.rootPath), fullResourcePath); - const absoluteResourcePath = path.posix.join('{{hostBaseUrl}}', resolvedResourcePath); + const absoluteResourcePath = getAbsoluteResourcePath(elem, resourcePath); $(this).attr('href', absoluteResourcePath); }); return $.html(); @@ -256,20 +252,18 @@ class Parser { } _parse(node, context, config) { - const element = node; - const self = this; - if (_.isArray(element)) { - return element.map(el => self._parse(el, context, config)); + if (_.isArray(node)) { + return node.map(el => this._parse(el, context, config)); } - if (Parser.isText(element)) { - return element; + if (Parser.isText(node)) { + return node; } - if (element.name) { - element.name = element.name.toLowerCase(); + if (node.name) { + node.name = node.name.toLowerCase(); } - if ((/^h[1-6]$/).test(element.name) && !element.attribs.id) { - const textContent = utils.getTextContent(element); + if ((/^h[1-6]$/).test(node.name) && !node.attribs.id) { + const textContent = utils.getTextContent(node); const slugifiedHeading = slugify(textContent, { decamelize: false }); let headerId = slugifiedHeading; @@ -281,59 +275,58 @@ class Parser { headerIdMap[slugifiedHeading] = 2; } - element.attribs.id = headerId; + node.attribs.id = headerId; } - switch (element.name) { + switch (node.name) { case 'md': - element.name = 'span'; + node.name = 'span'; cheerio.prototype.options.xmlMode = false; - element.children = cheerio.parseHTML(md.renderInline(cheerio.html(element.children)), true); + node.children = cheerio.parseHTML(md.renderInline(cheerio.html(node.children)), true); cheerio.prototype.options.xmlMode = true; break; case 'markdown': - element.name = 'div'; + node.name = 'div'; cheerio.prototype.options.xmlMode = false; - element.children = cheerio.parseHTML(md.render(cheerio.html(element.children)), true); + node.children = cheerio.parseHTML(md.render(cheerio.html(node.children)), true); cheerio.prototype.options.xmlMode = true; break; case 'panel': { - if (!_.hasIn(element.attribs, 'src')) { // dynamic panel + if (!_.hasIn(node.attribs, 'src')) { // dynamic panel break; } - const fileExists = utils.fileExists(element.attribs.src) + const fileExists = utils.fileExists(node.attribs.src) || utils.fileExists( urlUtils.calculateBoilerplateFilePath( - element.attribs.boilerplate, - element.attribs.src, config)); + node.attribs.boilerplate, + node.attribs.src, config)); if (fileExists) { - const { src, fragment } = element.attribs; + const { src, fragment } = node.attribs; const resultDir = path.dirname(path.join('{{hostBaseUrl}}', path.relative(config.rootPath, src))); const resultPath = path.join(resultDir, utils.setExtension(path.basename(src), '._include_.html')); - element.attribs.src = utils.ensurePosix(fragment ? `${resultPath}#${fragment}` : resultPath); + node.attribs.src = utils.ensurePosix(fragment ? `${resultPath}#${fragment}` : resultPath); } - delete element.attribs.boilerplate; + delete node.attribs.boilerplate; break; } default: break; } - componentParser.parseComponents(element, this._onError); + componentParser.parseComponents(node, this._onError); - if (element.children) { - element.children.forEach((child) => { - self._parse(child, context, config); + if (node.children) { + node.children.forEach((child) => { + this._parse(child, context, config); }); } - componentParser.postParseComponents(element, this._onError); + componentParser.postParseComponents(node, this._onError); - return element; + return node; } _trimNodes(node) { - const self = this; if (node.name === 'pre' || node.name === 'code') { return; } @@ -346,7 +339,7 @@ class Parser { node.children.splice(n, 1); n--; } else if (child.type === 'tag') { - self._trimNodes(child); + this._trimNodes(child); } } /* eslint-enable no-plusplus */ @@ -589,21 +582,20 @@ class Parser { } _rebaseReference(node, foundBase) { - const element = node; - if (_.isArray(element)) { - return element.map(el => this._rebaseReference(el, foundBase)); + if (_.isArray(node)) { + return node.map(el => this._rebaseReference(el, foundBase)); } - if (Parser.isText(element)) { - return element; + if (Parser.isText(node)) { + return node; } // Rebase children element const childrenBase = {}; - element.children.forEach((el) => { + node.children.forEach((el) => { this._rebaseReference(el, childrenBase); }); // rebase current element - if (element.attribs[ATTRIB_INCLUDE_PATH]) { - const filePath = element.attribs[ATTRIB_INCLUDE_PATH]; + if (node.attribs[ATTRIB_INCLUDE_PATH]) { + const filePath = node.attribs[ATTRIB_INCLUDE_PATH]; let newBaseUrl = urlUtils.calculateNewBaseUrls(filePath, this.rootPath, this.baseUrlMap); if (newBaseUrl) { const { relative, parent } = newBaseUrl; @@ -616,27 +608,27 @@ class Parser { if (bases.length !== 0) { // need to rebase newBaseUrl = combinedBases[bases[0]]; - if (element.children) { + if (node.children) { // ATTRIB_CWF is where the element was preprocessed - const currentBase = urlUtils.calculateNewBaseUrls(element.attribs[ATTRIB_CWF], + const currentBase = urlUtils.calculateNewBaseUrls(node.attribs[ATTRIB_CWF], this.rootPath, this.baseUrlMap); if (currentBase && currentBase.relative !== newBaseUrl) { cheerio.prototype.options.xmlMode = false; - const rendered = nunjucks.renderString(cheerio.html(element.children), { + const rendered = nunjucks.renderString(cheerio.html(node.children), { // This is to prevent the nunjuck call from converting {{hostBaseUrl}} to an empty string // and let the hostBaseUrl value be injected later. hostBaseUrl: '{{hostBaseUrl}}', baseUrl: `{{hostBaseUrl}}/${newBaseUrl}`, }, { path: filePath }); - element.children = cheerio.parseHTML(rendered, true); + node.children = cheerio.parseHTML(rendered, true); cheerio.prototype.options.xmlMode = true; } } } - delete element.attribs[ATTRIB_INCLUDE_PATH]; + delete node.attribs[ATTRIB_INCLUDE_PATH]; } - delete element.attribs[ATTRIB_CWF]; - return element; + delete node.attribs[ATTRIB_CWF]; + return node; } static resetVariables() { @@ -645,8 +637,8 @@ class Parser { Parser.PROCESSED_INNER_VARIABLES.clear(); } - static isText(element) { - return element.type === 'text' || element.type === 'comment'; + static isText(node) { + return node.type === 'text' || node.type === 'comment'; } /** diff --git a/src/lib/markbind/src/parsers/componentParser.js b/src/lib/markbind/src/parsers/componentParser.js index 4c8160879f..7b7ef582cb 100644 --- a/src/lib/markbind/src/parsers/componentParser.js +++ b/src/lib/markbind/src/parsers/componentParser.js @@ -44,20 +44,19 @@ function _parseAttributeWithoutOverride(node, attribute, isInline, slotName = at /** * Takes an element, looks for direct elements with slots and transforms to avoid Vue parsing. * This is so that we can use bootstrap-vue popovers, tooltips, and modals. - * @param element Element to transform + * @param node Element to transform */ -function _transformSlottedComponents(element) { - element.children.forEach((child) => { - const c = child; - const slot = c.attribs && c.attribs.slot; +function _transformSlottedComponents(node) { + node.children.forEach((child) => { + const slot = child.attribs && child.attribs.slot; if (slot) { // Turns
... into
... - c.attribs['data-mb-html-for'] = slot; - delete c.attribs.slot; + child.attribs['data-mb-html-for'] = slot; + delete child.attribs.slot; } // similarly, need to transform templates to avoid Vue parsing - if (c.name === 'template') { - c.name = 'span'; + if (child.name === 'template') { + child.name = 'span'; } }); } @@ -150,18 +149,17 @@ function _assignPanelId(node) { * For modals, we make it attempt to show the modal if it exists. */ -function _parseTrigger(element) { - const el = element; - el.name = 'span'; - const trigger = el.attribs.trigger || 'hover'; - const placement = el.attribs.placement || 'top'; - el.attribs[`v-b-popover.${trigger}.${placement}.html`] +function _parseTrigger(node) { + node.name = 'span'; + const trigger = node.attribs.trigger || 'hover'; + const placement = node.attribs.placement || 'top'; + node.attribs[`v-b-popover.${trigger}.${placement}.html`] = 'popoverGenerator'; - el.attribs[`v-b-tooltip.${trigger}.${placement}.html`] + node.attribs[`v-b-tooltip.${trigger}.${placement}.html`] = 'tooltipContentGetter'; const convertedTrigger = trigger === 'hover' ? 'mouseover' : trigger; - el.attribs[`v-on:${convertedTrigger}`] = `$refs['${el.attribs.for}'].show()`; - el.attribs.class = el.attribs.class ? `${el.attribs.class} trigger` : 'trigger'; + node.attribs[`v-on:${convertedTrigger}`] = `$refs['${node.attribs.for}'].show()`; + node.attribs.class = node.attribs.class ? `${node.attribs.class} trigger` : 'trigger'; } /* @@ -172,22 +170,20 @@ function _parseTrigger(element) { * Then, we add in a trigger for this popover. */ -function _parsePopover(element) { - const el = element; - _parseAttributeWithoutOverride(el, 'content', true); - _parseAttributeWithoutOverride(el, 'header', true); +function _parsePopover(node) { + _parseAttributeWithoutOverride(node, 'content', true); + _parseAttributeWithoutOverride(node, 'header', true); // TODO deprecate title attribute for popovers _parseAttributeWithoutOverride(node, 'title', true, 'header'); - _parseAttributeWithoutOverride(el, 'title', true, 'header'); - el.name = 'span'; - const trigger = el.attribs.trigger || 'hover'; - const placement = el.attribs.placement || 'top'; - el.attribs['data-mb-component-type'] = 'popover'; - el.attribs[`v-b-popover.${trigger}.${placement}.html`] + node.name = 'span'; + const trigger = node.attribs.trigger || 'hover'; + const placement = node.attribs.placement || 'top'; + node.attribs['data-mb-component-type'] = 'popover'; + node.attribs[`v-b-popover.${trigger}.${placement}.html`] = 'popoverInnerGenerator'; - el.attribs.class = el.attribs.class ? `${el.attribs.class} trigger` : 'trigger'; - _transformSlottedComponents(el); + node.attribs.class = node.attribs.class ? `${node.attribs.class} trigger` : 'trigger'; + _transformSlottedComponents(node); } /* @@ -196,18 +192,17 @@ function _parsePopover(element) { * Similar to popovers. */ -function _parseTooltip(element) { - const el = element; - _parseAttributeWithoutOverride(el, 'content', true, '_content'); +function _parseTooltip(node) { + _parseAttributeWithoutOverride(node, 'content', true, '_content'); - el.name = 'span'; - const trigger = el.attribs.trigger || 'hover'; - const placement = el.attribs.placement || 'top'; - el.attribs['data-mb-component-type'] = 'tooltip'; - el.attribs[`v-b-tooltip.${trigger}.${placement}.html`] + node.name = 'span'; + const trigger = node.attribs.trigger || 'hover'; + const placement = node.attribs.placement || 'top'; + node.attribs['data-mb-component-type'] = 'tooltip'; + node.attribs[`v-b-tooltip.${trigger}.${placement}.html`] = 'tooltipInnerContentGetter'; - el.attribs.class = el.attribs.class ? `${el.attribs.class} trigger` : 'trigger'; - _transformSlottedComponents(el); + node.attribs.class = node.attribs.class ? `${node.attribs.class} trigger` : 'trigger'; + _transformSlottedComponents(node); } function _renameSlot(node, originalName, newName) { @@ -220,11 +215,10 @@ function _renameSlot(node, originalName, newName) { } } -function _renameAttribute(element, originalAttribute, newAttribute) { - const el = element; - if (_.has(el.attribs, originalAttribute)) { - el.attribs[newAttribute] = el.attribs[originalAttribute]; - delete el.attribs[originalAttribute]; +function _renameAttribute(node, originalAttribute, newAttribute) { + if (_.has(node.attribs, originalAttribute)) { + node.attribs[newAttribute] = node.attribs[originalAttribute]; + delete node.attribs[originalAttribute]; } } @@ -236,45 +230,42 @@ function _renameAttribute(element, originalAttribute, newAttribute) { */ function _parseModalAttributes(node) { - _parseAttributeWithoutOverride(node, 'header', true, '_header'); + _parseAttributeWithoutOverride(node, 'header', true, 'modal-title'); // TODO deprecate title attribute for modals - _parseAttributeWithoutOverride(node, 'title', true, '_header'); - _parseAttributeWithoutOverride(el, 'title', true, 'modal-title'); + _parseAttributeWithoutOverride(node, 'title', true, 'modal-title'); // TODO deprecate modal-header, modal-footer attributes for modals - _renameSlot(node, 'modal-header', 'header'); - _renameSlot(node, 'modal-footer', 'footer'); - _renameSlot(el, 'header', 'modal-header'); - _renameSlot(el, 'footer', 'modal-footer'); + _renameSlot(node, 'header', 'modal-header'); + _renameSlot(node, 'footer', 'modal-footer'); - el.name = 'b-modal'; + node.name = 'b-modal'; - _renameAttribute(el, 'ok-text', 'ok-title'); - _renameAttribute(el, 'center', 'centered'); + _renameAttribute(node, 'ok-text', 'ok-title'); + _renameAttribute(node, 'center', 'centered'); - el.attribs['ok-only'] = ''; // only show OK button + node.attribs['ok-only'] = ''; // only show OK button - if (el.attribs.backdrop === 'false') { - el.attribs['no-close-on-backdrop'] = ''; + if (node.attribs.backdrop === 'false') { + node.attribs['no-close-on-backdrop'] = ''; } - delete el.attribs.backdrop; + delete node.attribs.backdrop; let size = ''; - if (_.has(el.attribs, 'large')) { + if (_.has(node.attribs, 'large')) { size = 'lg'; - delete el.attribs.large; - } else if (_.has(el.attribs, 'small')) { + delete node.attribs.large; + } else if (_.has(node.attribs, 'small')) { size = 'sm'; - delete el.attribs.small; + delete node.attribs.small; } - el.attribs.size = size; + node.attribs.size = size; // default for markbind is zoom, default for bootstrap-vue is fade - const effect = el.attribs.effect === 'fade' ? '' : 'mb-zoom'; - el.attribs['modal-class'] = effect; + const effect = node.attribs.effect === 'fade' ? '' : 'mb-zoom'; + node.attribs['modal-class'] = effect; - if (_.has(el.attribs, 'id')) { - el.attribs.ref = el.attribs.id; + if (_.has(node.attribs, 'id')) { + node.attribs.ref = node.attribs.id; } }