Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added docs/userGuide/syntax/extra/stats.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
55 changes: 55 additions & 0 deletions docs/userGuide/syntax/pageLayouts.mbdf
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
<variable name="mainContentBody">
<code>{<span></span>{ MAIN_CONTENT_BODY }}</code>
</variable>

## Page Layouts

**A _layout_ is a set of page-tweaks that can be applied to a page (or group of pages) in one go.**
Expand All @@ -7,6 +11,7 @@ A layout consists of the following files:
1. A footer (filename: `footer.md`)
1. Tweaks to the `<head>` (`head.md`)
1. A site navigation menu (`navigation.md`)
1. An expressive layout template to embed your content (`page.md`)
1. Custom styles (`styles.css`)
1. Custom scripts (`scripts.js`)

Expand All @@ -24,6 +29,7 @@ To apply the layout, specify it as an attribute named `layout` in the `<frontmat
├── header.md
├── footer.md
├── head.md
├── page.md
├── navigation.md
├── styles.css
└── scripts.js
Expand Down Expand Up @@ -95,3 +101,52 @@ afterSetup(() => {
</frontmatter>
```
</span>

#### Using Expressive Layout Templates

In the `page.md` file of your layouts, it should come with the following reserved variable:

<box>
{{ mainContentBody }}
</box>

which injects the actual page content in every page. This allows you to build layouts in different ways.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reads better this way?

The page.md file of a layout should come with the reserved variable {{ MAIN_CONTENT_BODY }} which injects the actual page content in every page. This allows you to build layouts in different ways.

Alternatively, can be written this way (i.e., avoid breaking sentences across boxes):

The page.md file of a layout should come with the following reserved variable which injects the actual page content in every page.

{{ MAIN_CONTENT_BODY }}

This allows you to build layouts in different ways.


{{ icon_example }} Adding statistics formula to the bottom of every page

<box>
<i>index.md</i>
<br><br>

```html
#### Main content of your page

Content of your page
```
</box>

<box>
<i>page.md</i>
<br><br>
{{ mainContentBody }}
```html
<panel header="Statistics Formula for the class" type="primary">
<img src="path_to_your_formula.png" />
</panel>
```
</box>

Here's how the above content would appear:

<box>
<i>index.html</i>
<br><br>

#### Main content of your page

Content of your page

<panel header="Statistics Formula for the class" type="primary">
<img src="extra/stats.png" width="420px" height="320px" />
</panel>
</box>
53 changes: 53 additions & 0 deletions src/Page.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ const {
LAYOUT_FOOTER,
LAYOUT_HEAD,
LAYOUT_HEADER,
LAYOUT_PAGE,
LAYOUT_PAGE_BODY_VARIABLE,
LAYOUT_NAVIGATION,
NAVIGATION_FOLDER_PATH,
CONTENT_WRAPPER_ID,
Expand Down Expand Up @@ -505,6 +507,56 @@ Page.prototype.removeFrontMatter = function (includedPage) {
return $.html();
};

/**
* Produces expressive layouts by inserting page data into pre-specified layout
* @param pageData a page with its front matter collected
*/
Page.prototype.generateExpressiveLayout = function (pageData, fileConfig) {
const nj = nunjucks;
const markbinder = new MarkBind({
errorHandler: logger.error,
});
nj.configure({
autoescape: false,
});
const template = {};
template[LAYOUT_PAGE_BODY_VARIABLE] = pageData;
const { layout } = this.frontMatter;
const layoutPath = path.join(this.rootPath, LAYOUT_FOLDER_PATH, layout);
const layoutPagePath = path.join(layoutPath, LAYOUT_PAGE);

if (!fs.existsSync(layoutPagePath)) {
return pageData;
}
const layoutFileConfig = {
...fileConfig,
cwf: layoutPagePath,
additionalVariables: {
},
};

layoutFileConfig.additionalVariables[LAYOUT_PAGE_BODY_VARIABLE] = `{{${LAYOUT_PAGE_BODY_VARIABLE}}}`;

// Set expressive layout file as an includedFile
this.includedFiles.add(layoutPagePath);
return new Promise((resolve, reject) => {
// Retrieve Expressive Layouts page and insert content
fs.readFileAsync(layoutPagePath, 'utf8')
.then(result => markbinder.includeData(layoutPagePath, result, layoutFileConfig))
.then(result => nj.renderString(result, template))
.then((result) => {
this.collectIncludedFiles(markbinder.getDynamicIncludeSrc());
this.collectIncludedFiles(markbinder.getStaticIncludeSrc());
this.collectIncludedFiles(markbinder.getBoilerplateIncludeSrc());
this.collectIncludedFiles(markbinder.getMissingIncludeSrc());
return result;
})
.then(resolve)
.catch(reject);
});
};


/**
* Inserts the page layout's header to the start of the page
* @param pageData a page with its front matter collected
Expand Down Expand Up @@ -793,6 +845,7 @@ Page.prototype.generate = function (builtFiles) {
this.collectFrontMatter(result);
return this.removeFrontMatter(result);
})
.then(result => this.generateExpressiveLayout(result, fileConfig))
.then(result => removePageHeaderAndFooter(result))
.then(result => addContentWrapper(result))
.then(result => this.collectPluginSources(result))
Expand Down
3 changes: 3 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ module.exports = {
HEADERS_FOLDER_PATH: '_markbind/headers',
LAYOUT_DEFAULT_NAME: 'default',
LAYOUT_FOLDER_PATH: '_markbind/layouts',

LAYOUT_PAGE: 'page.md',
LAYOUT_PAGE_BODY_VARIABLE: 'MAIN_CONTENT_BODY',
LAYOUT_FOOTER: 'footer.md',
LAYOUT_HEAD: 'head.md',
LAYOUT_HEADER: 'header.md',
Expand Down
92 changes: 65 additions & 27 deletions src/lib/markbind/src/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -655,11 +655,10 @@ Parser.prototype._trimNodes = function (node) {
}
};

Parser.prototype.includeFile = function (file, config) {
const context = {};
context.cwf = config.cwf || file; // current working file
context.mode = 'include';
context.callStack = [];
Parser.prototype.preprocess = function (file, pageData, context, config) {
const currentContext = context;
currentContext.mode = 'include';
currentContext.callStack = [];

return new Promise((resolve, reject) => {
const handler = new htmlparser.DomHandler((error, dom) => {
Expand All @@ -670,7 +669,7 @@ Parser.prototype.includeFile = function (file, config) {
const nodes = dom.map((d) => {
let processed;
try {
processed = this._preprocess(d, context, config);
processed = this._preprocess(d, currentContext, config);
} catch (err) {
err.message += `\nError while preprocessing '${file}'`;
this._onError(err);
Expand All @@ -686,6 +685,44 @@ Parser.prototype.includeFile = function (file, config) {
decodeEntities: true,
});

const { parent, relative } = calculateNewBaseUrls(file, config.rootPath, config.baseUrlMap);
const userDefinedVariables = config.userDefinedVariablesMap[path.resolve(parent, relative)];
const { additionalVariables } = config;
const pageVariables = extractPageVariables(file, pageData, userDefinedVariables, {});

let fileContent = nunjucks.renderString(pageData,
{
...pageVariables,
...userDefinedVariables,
...additionalVariables,
},
{ path: file });
this._extractInnerVariables(fileContent, currentContext, config);
const innerVariables = getImportedVariableMap(currentContext.cwf);
fileContent = nunjucks.renderString(fileContent, {
...userDefinedVariables,
...additionalVariables,
...innerVariables,
});
const fileExt = utils.getExt(file);
if (utils.isMarkdownFileExt(fileExt)) {
currentContext.source = 'md';
parser.parseComplete(fileContent.toString());
} else if (fileExt === 'html') {
currentContext.source = 'html';
parser.parseComplete(fileContent);
} else {
const error = new Error(`Unsupported File Extension: '${fileExt}'`);
reject(error);
}
});
};

Parser.prototype.includeFile = function (file, config) {
const context = {};
context.cwf = config.cwf || file; // current working file

return new Promise((resolve, reject) => {
let actualFilePath = file;
if (!utils.fileExists(file)) {
const boilerplateFilePath = calculateBoilerplateFilePath(path.basename(file), file, config);
Expand All @@ -700,31 +737,32 @@ Parser.prototype.includeFile = function (file, config) {
reject(err);
return;
}
const { parent, relative } = calculateNewBaseUrls(file, config.rootPath, config.baseUrlMap);
const userDefinedVariables = config.userDefinedVariablesMap[path.resolve(parent, relative)];
const pageVariables = extractPageVariables(file, data, userDefinedVariables, {});

let fileContent = nunjucks.renderString(data,
{ ...pageVariables, ...userDefinedVariables },
{ path: actualFilePath });
this._extractInnerVariables(fileContent, context, config);
const innerVariables = getImportedVariableMap(context.cwf);
fileContent = nunjucks.renderString(fileContent, { ...userDefinedVariables, ...innerVariables });
const fileExt = utils.getExt(file);
if (utils.isMarkdownFileExt(fileExt)) {
context.source = 'md';
parser.parseComplete(fileContent.toString());
} else if (fileExt === 'html') {
context.source = 'html';
parser.parseComplete(fileContent);
} else {
const error = new Error(`Unsupported File Extension: '${fileExt}'`);
reject(error);
}
this.preprocess(actualFilePath, data, context, config)
.then(resolve)
.catch(reject);
});
});
};

Parser.prototype.includeData = function (file, pageData, config) {
const context = {};
context.cwf = config.cwf || file; // current working file

return new Promise((resolve, reject) => {
let actualFilePath = file;
if (!utils.fileExists(file)) {
const boilerplateFilePath = calculateBoilerplateFilePath(path.basename(file), file, config);
if (utils.fileExists(boilerplateFilePath)) {
actualFilePath = boilerplateFilePath;
}
}

this.preprocess(actualFilePath, pageData, context, config)
.then(resolve)
.catch(reject);
});
};

Parser.prototype.renderFile = function (file, config) {
const context = {};
context.cwf = file; // current working file
Expand Down
1 change: 1 addition & 0 deletions src/template/default/_markbind/layouts/default/page.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{ MAIN_CONTENT_BODY }}
1 change: 1 addition & 0 deletions src/template/minimal/_markbind/layouts/default/page.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{ MAIN_CONTENT_BODY }}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{ MAIN_CONTENT_BODY }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<footer>
<!-- Support MarkBind by including a link to us on your landing page! -->
<div class="text-center">
<small>[Generated by {{MarkBind}}]</small>
</div>
</footer>
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<variable name="importedVar">This is an importedVar</variable>

This content can be imported to the bottom of the page
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<variable name="layoutVar">Variable from layout</variable>
<import importedVar from="imported.md" as="imported" />

{{ layoutVar }}

{{ importedVar }}

<panel header="Expanded panel" alt="Minimized panel" type="success" minimized>
Math formulas
</panel>

{{ MAIN_CONTENT_BODY }}

Bottom Content

<include src="imported.md" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// eslint-disable-next-line no-undef
MarkBind.afterSetup(() => {
// Include code to be called after MarkBind setup here.
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<variable name="example">
To inject this HTML segment in your markbind files, use {{ example }} where you want to place it.
More generally, surround the segment's id with double curly braces.
</variable>
Loading