diff --git a/docs/userGuide/reusingContents.md b/docs/userGuide/reusingContents.md index c76e4c8534..3af298db0e 100644 --- a/docs/userGuide/reusingContents.md +++ b/docs/userGuide/reusingContents.md @@ -103,6 +103,16 @@ As mentioned in _User Guide: MarkBind Syntax_, you can use built-in variables to

+##### Variables: Defaults + +You can specify a default value for a variable, which is displayed when the variable is not specified in `variables.md` and by any of the [includes]({{ baseUrl }}/userGuide/reusingContents.html#the-include-tag) of the page. This is done by adding `or defaultValue` within the curly braces. + +

+ +{{ icon_example }} If `name` is not declared in `variables.md`:
+My name is {{ name or "Anonymous" }}. {{ icon_arrow_right }} My name is Anonymous. +
+ ##### Variables: Tips and Tricks **Variables can refer to other variables** that are declared earlier, including built-in variables. @@ -259,6 +269,33 @@ In other words, **`` interprets the reused code relative to the origina
+##### Specifying Variables in an `` + +**It is possible to include variables in an ``.** + +
+ +{{ icon_example }} Specifying `title` and `author` variables in an `` tag: + +```html + + My Title + John Doe + +``` + +In `article.md`: + + +# {{ title }}
+Author: {{ author }} +
+
+ +These variables work the same way as variables in `_markbind/variables.md`, except that they only apply to the included file. They allow the included file to be reused as a template, for different source files using different variable values. + +If the same variable is defined in a chain of ``s (e.g. `a.md` includes `b.md` includes `c.md`...), variables defined in the top-most `` will take precedence. Global variables (`_markbind/variables.md`) will take precedence over any `` variables. + ## Using Boilerplate Files **If you find duplicating a _boilerplate code_ fragment in multiple places of your code base, you can use a `boilerplate` file to avoid such duplication.** Note that you cannot use a normal `` in this case because the code included using a normal `` stays relative to the original location while boilerplate code needs to be interpreted relative to the location it is being used. diff --git a/src/lib/markbind/src/parser.js b/src/lib/markbind/src/parser.js index b627688961..fabdddd74b 100644 --- a/src/lib/markbind/src/parser.js +++ b/src/lib/markbind/src/parser.js @@ -88,6 +88,32 @@ function Parser(options) { this.missingIncludeSrc = []; } +/** + * Extract variables from an include element + * @param includeElement include element to extract variables from + * @param contextVariables local variables defined by parent pages + */ +function extractVariables(includeElement, contextVariables) { + const includedVariables = { ...contextVariables }; + if (includeElement.children) { + includeElement.children.forEach((child) => { + if (child.name !== 'span') { + return; + } + if (!child.attribs.id) { + // eslint-disable-next-line no-console + console.warn(`Missing reference in ${includeElement.attribs[ATTRIB_CWF]}\n` + + `Missing 'id' in variable for ${includeElement.attribs.src} include.`); + return; + } + if (!includedVariables[child.attribs.id]) { + includedVariables[child.attribs.id] = cheerio.html(child.children); + } + }); + } + return includedVariables; +} + Parser.prototype.getDynamicIncludeSrc = function () { return _.clone(this.dynamicIncludeSrc); }; @@ -205,7 +231,11 @@ Parser.prototype._preprocess = function (node, context, config) { let fileContent = self._fileCache[actualFilePath]; // cache the file contents to save some I/O const { parent, relative } = calculateNewBaseUrls(filePath, config.rootPath, config.baseUrlMap); const userDefinedVariables = config.userDefinedVariablesMap[path.resolve(parent, relative)]; - fileContent = nunjucks.renderString(fileContent, userDefinedVariables); + + // process variables declared within the include + const includedVariables = extractVariables(element, context.includedVariables); + + fileContent = nunjucks.renderString(fileContent, { ...includedVariables, ...userDefinedVariables }); delete element.attribs.boilerplate; delete element.attribs.src; delete element.attribs.inline; @@ -265,6 +295,7 @@ Parser.prototype._preprocess = function (node, context, config) { const childContext = _.cloneDeep(context); childContext.cwf = filePath; childContext.source = isIncludeSrcMd ? 'md' : 'html'; + childContext.includedVariables = includedVariables; if (element.children && element.children.length > 0) { element.children = element.children.map(e => self._preprocess(e, childContext, config)); } diff --git a/test/test_site/_markbind/variables.md b/test/test_site/_markbind/variables.md index e8c5806bbe..da1cb897c8 100644 --- a/test/test_site/_markbind/variables.md +++ b/test/test_site/_markbind/variables.md @@ -7,3 +7,6 @@ {{reference_level_1}} {{reference_level_2}} {{reference_level_3}} + +Global variable overriding included variable +Global variable diff --git a/test/test_site/expected/index.html b/test/test_site/expected/index.html index ebca9c40ac..2cdaa0d839 100644 --- a/test/test_site/expected/index.html +++ b/test/test_site/expected/index.html @@ -69,6 +69,18 @@ + Include with custom variables‎ + Test included variable‎ + Test included variable with markdown‎ + Test included variable as attribute‎ + Test included variable as html element‎ + Test included variable overridden by variables.md‎ + Test included variables in included file‎ + Inner included variables should not leak into other files‎ + Test included variable with global variable‎ + Test included variable overridden by set‎ + Test missing variable with default‎ + Included variables should not leak into other files‎ Panel without src‎