diff --git a/src/Page.js b/src/Page.js index a8275e5b09..c5a90dc14e 100644 --- a/src/Page.js +++ b/src/Page.js @@ -18,7 +18,6 @@ const logger = require('./util/logger'); const MarkBind = require('./lib/markbind/src/parser'); const md = require('./lib/markbind/src/lib/markdown-it'); const utils = require('./lib/markbind/src/utils'); -const urlUtils = require('./lib/markbind/src/utils/urls'); const CLI_VERSION = require('../package.json').version; @@ -594,7 +593,7 @@ class Page { // Insert content .then(result => njUtil.renderRaw(result, { [LAYOUT_PAGE_BODY_VARIABLE]: pageData, - }, {}, false)); + })); } @@ -708,7 +707,7 @@ class Page { // Add anchor classes and highlight current page's anchor, if any. const currentPageHtmlPath = this.src.replace(/\.(md|mbd)$/, '.html'); - const currentPageRegex = new RegExp(`{{ *baseUrl *}}/${currentPageHtmlPath}`); + const currentPageRegex = new RegExp(`${this.baseUrl}/${currentPageHtmlPath}`); $nav('a[href]').each((i, elem) => { if (currentPageRegex.test($nav(elem).attr('href'))) { $nav(elem).addClass('current'); @@ -850,7 +849,7 @@ class Page { } } - collectHeadFiles(baseUrl, hostBaseUrl) { + collectHeadFiles() { const { head } = this.frontMatter; if (head === FRONT_MATTER_NONE_ATTR) { this.headFileTopContent = ''; @@ -880,16 +879,11 @@ class Page { // Split top and bottom contents const $ = cheerio.load(headFileMappedData, { xmlMode: false }); if ($('head-top').length) { - collectedTopContent.push(njUtil.renderRaw($('head-top').html(), { - baseUrl, - hostBaseUrl, - }).trim().replace(/\n\s*\n/g, '\n').replace(/\n/g, '\n ')); + collectedTopContent.push($('head-top').html().trim().replace(/\n\s*\n/g, '\n') + .replace(/\n/g, '\n ')); $('head-top').remove(); } - collectedBottomContent.push(njUtil.renderRaw($.html(), { - baseUrl, - hostBaseUrl, - }).trim().replace(/\n\s*\n/g, '\n').replace(/\n/g, '\n ')); + collectedBottomContent.push($.html().trim().replace(/\n\s*\n/g, '\n').replace(/\n/g, '\n ')); }); this.headFileTopContent = collectedTopContent.join('\n '); this.headFileBottomContent = collectedBottomContent.join('\n '); @@ -950,6 +944,7 @@ class Page { */ const fileConfig = { baseUrlMap: this.baseUrlMap, + baseUrl: this.baseUrl, rootPath: this.rootPath, headerIdMap: this.headerIdMap, fixedHeader: this.fixedHeader, @@ -969,27 +964,16 @@ class Page { .then(result => this.insertHeaderFile(result, fileConfig)) .then(result => this.insertFooterFile(result)) .then(result => Page.insertTemporaryStyles(result)) - .then(result => markbinder.resolveBaseUrl(result, fileConfig)) .then(result => markbinder.render(result, this.sourcePath, fileConfig)) .then(result => this.postRender(result)) .then(result => this.collectPluginsAssets(result)) - .then(result => markbinder.processDynamicResources(this.sourcePath, result)) + .then(result => MarkBind.processDynamicResources(this.sourcePath, result, fileConfig)) .then(result => MarkBind.unwrapIncludeSrc(result)) .then((result) => { - this.content = result; - - const { relative } = urlUtils.getParentSiteAbsoluteAndRelativePaths(this.sourcePath, this.rootPath, - this.baseUrlMap); - const baseUrl = relative ? `${this.baseUrl}/${utils.ensurePosix(relative)}` : this.baseUrl; - const hostBaseUrl = this.baseUrl; + this.addLayoutScriptsAndStyles(); + this.collectHeadFiles(); - this.addLayoutFiles(); - this.collectHeadFiles(baseUrl, hostBaseUrl); - - this.content = njUtil.renderString(this.content, { - baseUrl, - hostBaseUrl, - }); + this.content = result; this.collectAllPageSections(); this.buildPageNav(); @@ -1077,7 +1061,7 @@ class Page { pageContextSources.forEach((src) => { if (src === undefined || src === '' || utils.isUrl(src)) { return; - } else if (utils.isAbsolutePath(src)) { + } else if (path.isAbsolute(src)) { self.pluginSourceFiles.add(path.resolve(src)); return; } @@ -1109,7 +1093,7 @@ class Page { src = ensurePosix(src); if (src === '' || utils.isUrl(src)) { return; - } else if (utils.isAbsolutePath(src)) { + } else if (path.isAbsolute(src)) { self.pluginSourceFiles.add(path.resolve(src)); return; } @@ -1233,7 +1217,7 @@ class Page { /** * Adds linked layout files to page assets */ - addLayoutFiles() { + addLayoutScriptsAndStyles() { this.asset.layoutScript = path.join(this.layoutsAssetPath, this.frontMatter.layout, 'scripts.js'); this.asset.layoutStyle = path.join(this.layoutsAssetPath, this.frontMatter.layout, 'styles.css'); } @@ -1260,41 +1244,30 @@ class Page { const markbinder = new MarkBind({ variablePreprocessor: this.variablePreprocessor, }); + /** + * @type {FileConfig} + */ + const fileConfig = { + baseUrlMap: this.baseUrlMap, + baseUrl: this.baseUrl, + rootPath: this.rootPath, + headerIdMap: {}, + cwf: file, + }; return fs.readFileAsync(dependency.to, 'utf-8') - .then(result => markbinder.includeFile(dependency.to, result, { - baseUrlMap: this.baseUrlMap, - rootPath: this.rootPath, - cwf: file, - })) + .then(result => markbinder.includeFile(dependency.to, result, fileConfig)) .then(result => Page.removeFrontMatter(result)) .then(result => this.collectPluginSources(result)) .then(result => this.preRender(result)) - .then(result => markbinder.resolveBaseUrl(result, { - baseUrlMap: this.baseUrlMap, - rootPath: this.rootPath, - })) - .then(result => markbinder.render(result, this.sourcePath, { - baseUrlMap: this.baseUrlMap, - rootPath: this.rootPath, - headerIdMap: {}, - })) + .then(result => markbinder.render(result, this.sourcePath, fileConfig)) .then(result => this.postRender(result)) .then(result => this.collectPluginsAssets(result)) - .then(result => markbinder.processDynamicResources(file, result)) + .then(result => MarkBind.processDynamicResources(file, result, fileConfig)) .then(result => MarkBind.unwrapIncludeSrc(result)) .then((result) => { - // resolve the site base url here - const { relative } = urlUtils.getParentSiteAbsoluteAndRelativePaths(file, this.rootPath, - this.baseUrlMap); - const baseUrl = relative ? `${this.baseUrl}/${utils.ensurePosix(relative)}` : this.baseUrl; - const hostBaseUrl = this.baseUrl; - const content = njUtil.renderString(result, { - baseUrl, - hostBaseUrl, - }); const outputContentHTML = this.disableHtmlBeautify - ? content - : htmlBeautify(content, Page.htmlBeautifyOptions); + ? result + : htmlBeautify(result, Page.htmlBeautifyOptions); return fs.outputFileAsync(resultPath, outputContentHTML); }) .then(() => { diff --git a/src/Site.js b/src/Site.js index c39a87dcf2..ee396a6fe6 100644 --- a/src/Site.js +++ b/src/Site.js @@ -11,6 +11,7 @@ const njUtil = require('./lib/markbind/src/utils/nunjuckUtils'); const injectHtmlParser2SpecialTags = require('./lib/markbind/src/patches/htmlparser2'); const injectMarkdownItSpecialTags = require( './lib/markbind/src/lib/markdown-it/markdown-it-escape-special-tags'); +const utils = require('./lib/markbind/src/utils'); const _ = {}; _.difference = require('lodash/difference'); @@ -524,8 +525,6 @@ class Site { * Collects the user defined variables map in the site/subsites */ collectUserDefinedVariablesMap() { - // The key is the base directory of the site/subsites, - // while the value is a mapping of user defined variables this.variablePreprocessor.resetUserDefinedVariablesMap(); this.baseUrlMap.forEach((base) => { @@ -540,13 +539,17 @@ class Site { } /* - This is to prevent the first nunjuck call from converting {{baseUrl}} to an empty string - and let the baseUrl value be injected later. + We retrieve the baseUrl of the (sub)site by appending the relative to the configured base url + i.e. We ignore the configured baseUrl of the sub sites. */ - this.variablePreprocessor.addUserDefinedVariable(base, 'baseUrl', '{{baseUrl}}'); + const siteRelativePathFromRoot = utils.ensurePosix(path.relative(this.rootPath, base)); + const siteBaseUrl = siteRelativePathFromRoot === '' + ? this.siteConfig.baseUrl + : path.posix.join(this.siteConfig.baseUrl || '/', siteRelativePathFromRoot); + this.variablePreprocessor.addUserDefinedVariable(base, 'baseUrl', siteBaseUrl); this.variablePreprocessor.addUserDefinedVariable(base, 'MarkBind', MARKBIND_LINK_HTML); - const $ = cheerio.load(content); + const $ = cheerio.load(content, { decodeEntities: false }); $('variable,span').each((index, element) => { const name = $(element).attr('name') || $(element).attr('id'); const variableSource = $(element).attr('from'); diff --git a/src/lib/markbind/src/constants.js b/src/lib/markbind/src/constants.js index 3ff5b152fc..2d8dd96aa0 100644 --- a/src/lib/markbind/src/constants.js +++ b/src/lib/markbind/src/constants.js @@ -1,6 +1,5 @@ module.exports = { // src/lib/markbind/src/parser.js - ATTRIB_INCLUDE_PATH: 'include-path', ATTRIB_CWF: 'cwf', BOILERPLATE_FOLDER_NAME: '_markbind/boilerplates', diff --git a/src/lib/markbind/src/parser.js b/src/lib/markbind/src/parser.js index 30a83b3303..d1a22391ca 100644 --- a/src/lib/markbind/src/parser.js +++ b/src/lib/markbind/src/parser.js @@ -3,11 +3,9 @@ const htmlparser = require('htmlparser2'); require('./patches/htmlparser2'); const path = require('path'); const Promise = require('bluebird'); const slugify = require('@sindresorhus/slugify'); -const ensurePosix = require('ensure-posix-path'); const componentParser = require('./parsers/componentParser'); const componentPreprocessor = require('./preprocessors/componentPreprocessor'); const logger = require('../../../util/logger'); -const njUtil = require('./utils/nunjuckUtils'); const _ = {}; _.clone = require('lodash/clone'); @@ -18,16 +16,10 @@ _.isEmpty = require('lodash/isEmpty'); const md = require('./lib/markdown-it'); const utils = require('./utils'); -const urlUtils = require('./utils/urls'); cheerio.prototype.options.xmlMode = true; // Enable xml mode for self-closing tag cheerio.prototype.options.decodeEntities = false; // Don't escape HTML entities -const { - ATTRIB_INCLUDE_PATH, - ATTRIB_CWF, -} = require('./constants'); - class Parser { constructor(config) { this.variablePreprocessor = config.variablePreprocessor; @@ -48,20 +40,16 @@ class Parser { return _.clone(this.missingIncludeSrc); } - processDynamicResources(context, html) { - const $ = cheerio.load(html, { - xmlMode: false, - decodeEntities: false, - }); + static processDynamicResources(context, html, config) { + const $ = cheerio.load(html, { xmlMode: 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); + const resolvedResourcePath = path.posix.relative(utils.ensurePosix(config.rootPath), fullResourcePath); + return path.posix.join(config.baseUrl || '/', resolvedResourcePath); } $('img, pic, thumbnail').each(function () { @@ -70,7 +58,7 @@ class Parser { return; } const resourcePath = utils.ensurePosix(elem.attr('src')); - if (utils.isAbsolutePath(resourcePath) || utils.isUrl(resourcePath)) { + if (path.isAbsolute(resourcePath) || utils.isUrl(resourcePath)) { // Do not rewrite. return; } @@ -84,7 +72,7 @@ class Parser { // Found empty href resource in resourcePath return; } - if (utils.isAbsolutePath(resourcePath) || utils.isUrl(resourcePath) || resourcePath.startsWith('#')) { + if (path.isAbsolute(resourcePath) || utils.isUrl(resourcePath) || resourcePath.startsWith('#')) { // Do not rewrite. return; } @@ -95,10 +83,7 @@ class Parser { } static unwrapIncludeSrc(html) { - const $ = cheerio.load(html, { - xmlMode: false, - decodeEntities: false, - }); + const $ = cheerio.load(html, { xmlMode: false }); $('div[data-included-from], span[data-included-from]').each(function () { $(this).replaceWith($(this).contents()); }); @@ -149,22 +134,6 @@ class Parser { node.children = cheerio.parseHTML(md.render(cheerio.html(node.children)), true); cheerio.prototype.options.xmlMode = true; break; - case 'panel': { - if (!_.hasIn(node.attribs, 'src')) { // dynamic panel - break; - } - const fileExists = utils.fileExists(node.attribs.src) - || utils.fileExists(urlUtils.calculateBoilerplateFilePath(node.attribs.boilerplate, - node.attribs.src, config)); - if (fileExists) { - 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')); - node.attribs.src = utils.ensurePosix(fragment ? `${resultPath}#${fragment}` : resultPath); - } - delete node.attribs.boilerplate; - break; - } default: break; } @@ -230,10 +199,7 @@ class Parser { }); resolve(cheerio.html(nodes)); }); - const parser = new htmlparser.Parser(handler, { - xmlMode: true, - decodeEntities: true, - }); + const parser = new htmlparser.Parser(handler, { xmlMode: true }); const renderedContent = this.variablePreprocessor.renderPage(file, content, additionalVariables); @@ -276,10 +242,7 @@ class Parser { resolve(cheerio.html(nodes)); cheerio.prototype.options.xmlMode = true; }); - const parser = new htmlparser.Parser(handler, { - xmlMode: true, - decodeEntities: false, - }); + const parser = new htmlparser.Parser(handler, { xmlMode: true }); const fileExt = utils.getExt(filePath); if (utils.isMarkdownFileExt(fileExt)) { const renderedContent = md.render(content); @@ -293,78 +256,6 @@ class Parser { }); } - resolveBaseUrl(pageData, config) { - const { baseUrlMap, rootPath } = config; - this.baseUrlMap = baseUrlMap; - this.rootPath = rootPath; - - return new Promise((resolve, reject) => { - const handler = new htmlparser.DomHandler((error, dom) => { - if (error) { - reject(error); - return; - } - const nodes = dom.map(d => this._rebaseReference(d)); - cheerio.prototype.options.xmlMode = false; - resolve(cheerio.html(nodes)); - cheerio.prototype.options.xmlMode = true; - }); - const parser = new htmlparser.Parser(handler, { - xmlMode: true, - decodeEntities: true, - }); - parser.parseComplete(pageData); - }); - } - - /** - * Pre-renders the baseUrl of the provided node and its children according to - * the site they belong to, such that the final call to render baseUrl correctly - * resolves baseUrl according to the said site. - */ - _rebaseReference(node) { - if (_.isArray(node)) { - return node.map(el => this._rebaseReference(el)); - } - if (Parser.isText(node)) { - return node; - } - - // Rebase children elements - node.children.forEach(el => this._rebaseReference(el)); - - const includeSourceFile = node.attribs[ATTRIB_CWF]; - const includedSourceFile = node.attribs[ATTRIB_INCLUDE_PATH]; - delete node.attribs[ATTRIB_CWF]; - delete node.attribs[ATTRIB_INCLUDE_PATH]; - - // Skip rebasing for non-includes - if (!includedSourceFile || !node.children) { - return node; - } - - // The site at which the file with the include element was pre-processed - const currentBase = urlUtils.getParentSiteAbsolutePath(includeSourceFile, this.rootPath, this.baseUrlMap); - // The site of the included file - const newBase = urlUtils.getParentSiteAbsoluteAndRelativePaths(includedSourceFile, this.rootPath, - this.baseUrlMap); - - // Only re-render if include src and content are from different sites - if (currentBase !== newBase.absolute) { - cheerio.prototype.options.xmlMode = false; - const rendered = njUtil.renderRaw(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}}/${ensurePosix(newBase.relative)}`, - }, { path: includedSourceFile }); - node.children = cheerio.parseHTML(rendered, true); - cheerio.prototype.options.xmlMode = true; - } - - return node; - } - 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 e4665b59f3..046e91828d 100644 --- a/src/lib/markbind/src/parsers/componentParser.js +++ b/src/lib/markbind/src/parsers/componentParser.js @@ -6,6 +6,10 @@ _.has = require('lodash/has'); const md = require('../lib/markdown-it'); const logger = require('../../../../util/logger'); +const { + ATTRIB_CWF, +} = require('../constants'); + cheerio.prototype.options.xmlMode = true; // Enable xml mode for self-closing tag cheerio.prototype.options.decodeEntities = false; // Don't escape HTML entities @@ -483,6 +487,10 @@ function postParseComponents(node) { } catch (error) { logger.error(error); } + + if (node.attribs) { + delete node.attribs[ATTRIB_CWF]; + } } diff --git a/src/lib/markbind/src/preprocessors/componentPreprocessor.js b/src/lib/markbind/src/preprocessors/componentPreprocessor.js index a4b7a749fc..2a6099830c 100644 --- a/src/lib/markbind/src/preprocessors/componentPreprocessor.js +++ b/src/lib/markbind/src/preprocessors/componentPreprocessor.js @@ -13,7 +13,6 @@ _.has = require('lodash/has'); _.isEmpty = require('lodash/isEmpty'); const { - ATTRIB_INCLUDE_PATH, ATTRIB_CWF, } = require('../constants'); @@ -81,19 +80,17 @@ function _getSrcFlagsAndFilePaths(element, context, config) { // We do this even if the src is not a url to get the hash, if any const includeSrc = url.parse(element.attribs.src); - const baseUrlRegex = new RegExp('^{{\\s*baseUrl\\s*}}[/\\\\]'); - let filePath; if (isUrl) { filePath = element.attribs.src; } else { const includePath = decodeURIComponent(includeSrc.path); - if (baseUrlRegex.test(includePath)) { - // The baseUrl has not been resolved during pre-processing, but we need the source file path - const parentSitePath = urlUtils.getParentSiteAbsolutePath(context.cwf, config.rootPath, - config.baseUrlMap); - filePath = path.resolve(parentSitePath, includePath.replace(baseUrlRegex, '')); + if (path.posix.isAbsolute(includePath)) { + // {{ baseUrl }} (or simply '/...' if baseUrl === '') was used in this src attribute, + // get the relative path from the rootPath followed by its absolute path + const relativePathToFile = path.posix.relative(`${config.baseUrl}/`, includePath); + filePath = path.resolve(config.rootPath, relativePathToFile); } else { filePath = path.resolve(path.dirname(context.cwf), includePath); } @@ -123,16 +120,14 @@ function _getSrcFlagsAndFilePaths(element, context, config) { * and adds the appropriate include. */ function _preProcessPanel(node, context, config, parser) { - const element = node; - - const hasSrc = _.has(element.attribs, 'src'); + const hasSrc = _.has(node.attribs, 'src'); if (!hasSrc) { - if (element.children && element.children.length > 0) { + if (node.children && node.children.length > 0) { // eslint-disable-next-line no-use-before-define - element.children = element.children.map(e => preProcessComponent(e, context, config, parser)); + node.children = node.children.map(e => preProcessComponent(e, context, config, parser)); } - return element; + return node; } const { @@ -140,22 +135,27 @@ function _preProcessPanel(node, context, config, parser) { hash, filePath, actualFilePath, - } = _getSrcFlagsAndFilePaths(element, context, config); + } = _getSrcFlagsAndFilePaths(node, context, config); - const fileExistsNode = _getFileExistsNode(element, context, config, parser, actualFilePath); + const fileExistsNode = _getFileExistsNode(node, context, config, parser, actualFilePath); if (fileExistsNode) { return fileExistsNode; } if (!isUrl && hash) { - element.attribs.fragment = hash.substring(1); + node.attribs.fragment = hash.substring(1); } - element.attribs.src = filePath; + const { fragment } = node.attribs; + const resultPath = utils.setExtension(path.relative(config.rootPath, filePath), '._include_.html'); + const baseUrlPrependedPath = path.posix.join(`${config.baseUrl}/`, utils.ensurePosix(resultPath)); + node.attribs.src = fragment ? `${baseUrlPrependedPath}#${fragment}` : baseUrlPrependedPath; + + delete node.attribs.boilerplate; parser.dynamicIncludeSrc.push({ from: context.cwf, to: actualFilePath, asIfTo: filePath }); - return element; + return node; } @@ -231,7 +231,6 @@ function _preprocessInclude(node, context, config, parser) { const isTrim = _.has(element.attribs, 'trim'); element.name = isInline ? 'span' : 'div'; - element.attribs[ATTRIB_INCLUDE_PATH] = filePath; // No need to process url contents if (isUrl) return element; diff --git a/src/lib/markbind/src/preprocessors/variablePreprocessor.js b/src/lib/markbind/src/preprocessors/variablePreprocessor.js index c260ddeb2f..e77c230ec6 100644 --- a/src/lib/markbind/src/preprocessors/variablePreprocessor.js +++ b/src/lib/markbind/src/preprocessors/variablePreprocessor.js @@ -89,7 +89,7 @@ class VariablePreprocessor { * This is to allow using previously declared site variables in site variables declared later on. */ renderAndAddUserDefinedVariable(site, name, value) { - const renderedVal = njUtil.renderRaw(value, this.userDefinedVariablesMap[site], {}, false); + const renderedVal = njUtil.renderRaw(value, this.userDefinedVariablesMap[site]); this.addUserDefinedVariable(site, name, renderedVal); } diff --git a/src/lib/markbind/src/utils/index.js b/src/lib/markbind/src/utils/index.js index b46bebf05f..506f959498 100644 --- a/src/lib/markbind/src/utils/index.js +++ b/src/lib/markbind/src/utils/index.js @@ -62,12 +62,6 @@ module.exports = { return r.test(filePath); }, - isAbsolutePath(filePath) { - return path.isAbsolute(filePath) - || filePath.includes('{{baseUrl}}') - || filePath.includes('{{hostBaseUrl}}'); - }, - createErrorNode(element, error) { const errorElement = cheerio.parseHTML( `
Test nunjucks raw tags
-{{ code elements should automatically be assigned v-pre }}
-
There should be text between this and the next <hr>
tag, since it is a special tag.
All text should appear in the browser window as a single line,
save for the comment which the browser still interprets. (but will be in the expected output)