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
48 changes: 47 additions & 1 deletion docs/userGuide/contentAuthoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -607,4 +607,50 @@ MarkBind automatically generates heading anchors for your page.

When the reader hovers over a heading in your page, a small anchor icon <i class="fas fa-anchor"></i> will become visible next to the heading. Clicking this icon will redirect the page to that heading, producing the desired URL in the URL bar that the reader can share with someone else. Try it with the headings on this page!

</div>
### Layouts

A layout is a set of styles that can be applied to a page, or glob of pages. Layouts allow you to quickly apply styles to a batch of pages at once. It consists of the following files:

- `footer.md` : See [Using Footers](#using-footers)
- `head.md` : See [Inserting content into a page's head element](#inserting-content-into-a-pages-head-element)
- `navigation.md` : See [Site Navigation](#site-navigation)
- `styles.css` : Contains custom styles
- `scripts.js` : Contains custom javascript

These files will be automatically appended to a page upon generation.

Layouts can be found in `_markbind/layouts`. Markbind will generate a default layout with blank files in `_markbind/layouts/default`. The default layout is automatically applied to every single page.

To make a new layout, simply copy and rename the `default` folder (e.g. `_markbind/layouts/myLayout`) and edit the files within.

A page can be assigned a layout in two ways:

- Using `site.json`
```js
// Layout A will be applied to all index.md files
{
"glob": "**/index.md",
"layout": "LayoutA"
},

// Layout B will be applied to index2.md
{
"src": "index2.md",
"layout": "LayoutB"
},

// No Layout - default layout will be applied
{
"src": "index3.md"
},
```
- Using `<frontmatter>`
```html
<frontmatter>
layout: layoutA
head: myHead.md
</frontmatter>
```

Note that the layout specified in the `<frontmatter>` takes precedence over the one specified in `site.json`, and any files specified in `frontMatter` will take precedence over layout files (`myHead.md` will be used instead of the one in `layoutA`, in this case).
</div>
59 changes: 49 additions & 10 deletions lib/Page.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ const md = require('./markbind/lib/markdown-it');

const FOOTERS_FOLDER_PATH = '_markbind/footers';
const HEAD_FOLDER_PATH = '_markbind/head';
const LAYOUT_DEFAULT_NAME = 'default';
const LAYOUT_FOLDER_PATH = '_markbind/layouts';
const LAYOUT_FOOTER = 'footer.md';
const LAYOUT_HEAD = 'head.md';
const LAYOUT_NAVIGATION = 'navigation.md';
const NAVIGATION_FOLDER_PATH = '_markbind/navigation';

const CONTENT_WRAPPER_ID = 'content-wrapper';
Expand Down Expand Up @@ -47,6 +52,8 @@ function Page(pageConfig) {
this.baseUrlMap = pageConfig.baseUrlMap;
this.content = pageConfig.content || '';
this.faviconUrl = pageConfig.faviconUrl;
this.layout = pageConfig.layout;
this.layoutsAssetPath = pageConfig.layoutsAssetPath;
this.rootPath = pageConfig.rootPath;
this.searchable = pageConfig.searchable;
this.src = pageConfig.src;
Expand Down Expand Up @@ -365,11 +372,14 @@ Page.prototype.collectFrontMatter = function (includedPage) {
this.frontMatter.src = this.src;
// Title specified in site.json will override title specified in front matter
this.frontMatter.title = (this.title || this.frontMatter.title || '');
// Layout specified in front matter will override layout specified in site.json
this.frontMatter.layout = (this.frontMatter.layout || this.layout || '');
} else {
// Page is addressable but no front matter specified
this.frontMatter = {
src: this.src,
title: this.title || '',
layout: LAYOUT_DEFAULT_NAME,
};
}
this.title = this.frontMatter.title;
Expand All @@ -392,11 +402,17 @@ Page.prototype.removeFrontMatter = function (includedPage) {
*/
Page.prototype.insertFooter = function (pageData) {
const { footer } = this.frontMatter;
if (!footer) {
let footerFile;
if (footer) {
footerFile = path.join(FOOTERS_FOLDER_PATH, footer);
} else {
footerFile = path.join(LAYOUT_FOLDER_PATH, this.frontMatter.layout, LAYOUT_FOOTER);
}
const footerPath = path.join(this.rootPath, footerFile);
if (!fs.existsSync(footerPath)) {
return pageData;
}
// Retrieve Markdown file contents
const footerPath = path.join(this.rootPath, FOOTERS_FOLDER_PATH, footer);
const footerContent = fs.readFileSync(footerPath, 'utf8');
// Set footer file as an includedFile
this.includedFiles[footerPath] = true;
Expand All @@ -413,12 +429,21 @@ Page.prototype.insertFooter = function (pageData) {
*/
Page.prototype.insertSiteNav = function (pageData) {
const { siteNav } = this.frontMatter;
if (!siteNav) {
return pageData;
let siteNavFile;
if (siteNav) {
siteNavFile = path.join(NAVIGATION_FOLDER_PATH, siteNav);
} else {
siteNavFile = path.join(LAYOUT_FOLDER_PATH, this.frontMatter.layout, LAYOUT_NAVIGATION);
}
// Retrieve Markdown file contents
const siteNavPath = path.join(this.rootPath, NAVIGATION_FOLDER_PATH, siteNav);
const siteNavPath = path.join(this.rootPath, siteNavFile);
if (!fs.existsSync(siteNavPath)) {
return pageData;
}
const siteNavContent = fs.readFileSync(siteNavPath, 'utf8');
if (siteNavContent === '') {
return pageData;
}
// Set siteNav file as an includedFile
this.includedFiles[siteNavPath] = true;
// Map variables
Expand Down Expand Up @@ -447,14 +472,19 @@ Page.prototype.insertSiteNav = function (pageData) {

Page.prototype.collectHeadFiles = function (baseUrl, hostBaseUrl) {
const { head } = this.frontMatter;
if (!head) {
return;
}
const headFiles = head.replace(/, */g, ',').split(',');
let headFiles;
const collectedTopContent = [];
const collectedBottomContent = [];
if (head) {
headFiles = head.replace(/, */g, ',').split(',').map(headFile => path.join(HEAD_FOLDER_PATH, headFile));
} else {
headFiles = [path.join(LAYOUT_FOLDER_PATH, this.frontMatter.layout, LAYOUT_HEAD)];
}
headFiles.forEach((headFile) => {
const headFilePath = path.join(this.rootPath, HEAD_FOLDER_PATH, headFile);
const headFilePath = path.join(this.rootPath, headFile);
if (!fs.existsSync(headFilePath)) {
return;
}
const headFileContent = fs.readFileSync(headFilePath, 'utf8');
// Set head file as an includedFile
this.includedFiles[headFilePath] = true;
Expand Down Expand Up @@ -513,6 +543,7 @@ Page.prototype.generate = function (builtFiles) {
const baseUrl = newBaseUrl ? `${this.baseUrl}/${newBaseUrl}` : this.baseUrl;
const hostBaseUrl = this.baseUrl;

this.addLayoutFiles();
this.collectHeadFiles(baseUrl, hostBaseUrl);
this.content = nunjucks.renderString(this.content, { baseUrl, hostBaseUrl });
return fs.outputFileAsync(this.resultPath, this.template(this.prepareTemplateData()));
Expand All @@ -537,6 +568,14 @@ Page.prototype.generate = function (builtFiles) {
});
};

/**
* Adds linked layout files to page assets
*/
Page.prototype.addLayoutFiles = function () {
this.asset.layoutScript = path.join(this.layoutsAssetPath, this.frontMatter.layout, 'scripts.js');
this.asset.layoutStyle = path.join(this.layoutsAssetPath, this.frontMatter.layout, 'styles.css');
};

/**
* Pre-render an external dynamic dependency
* Does not pre-render if file is already pre-rendered by another page during site generation
Expand Down
59 changes: 58 additions & 1 deletion lib/Site.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,17 @@ const PAGE_TEMPLATE_NAME = 'page.ejs';
const SITE_CONFIG_NAME = 'site.json';
const SITE_DATA_NAME = 'siteData.json';
const SITE_NAV_PATH = '_markbind/navigation/site-nav.md';
const LAYOUT_DEFAULT_NAME = 'default';
const LAYOUT_FILES = ['navigation.md', 'head.md', 'footer.md', 'styles.css', 'scripts.js'];
const LAYOUT_FOLDER_PATH = '_markbind/layouts';
const LAYOUT_SITE_FOLDER_NAME = 'layouts';
const USER_VARIABLES_PATH = '_markbind/variables.md';

const SITE_CONFIG_DEFAULT = {
baseUrl: '',
titlePrefix: '',
ignore: [
'_markbind/layouts/*',
'_markbind/logs/*',
'_site/*',
'site.json',
Expand Down Expand Up @@ -176,6 +181,8 @@ Site.initSite = function (rootPath) {
const headFolderPath = path.join(rootPath, HEAD_FOLDER_PATH);
const indexPath = path.join(rootPath, INDEX_MARKDOWN_FILE);
const siteNavPath = path.join(rootPath, SITE_NAV_PATH);
const siteLayoutPath = path.join(rootPath, LAYOUT_FOLDER_PATH);
const siteLayoutDefaultPath = path.join(siteLayoutPath, LAYOUT_DEFAULT_NAME);
const userDefinedVariablesPath = path.join(rootPath, USER_VARIABLES_PATH);
// TODO: log the generate info
return new Promise((resolve, reject) => {
Expand Down Expand Up @@ -228,6 +235,31 @@ Site.initSite = function (rootPath) {
}
return fs.outputFileAsync(siteNavPath, SITE_NAV_DEFAULT);
})
.then(() => fs.accessAsync(siteLayoutPath))
.catch(() => {
if (fs.existsSync(siteLayoutPath)) {
return Promise.resolve();
}
return fs.mkdirp(siteLayoutPath);
})
.then(() => fs.accessAsync(siteLayoutDefaultPath))
.catch(() => {
if (fs.existsSync(siteLayoutDefaultPath)) {
return Promise.resolve();
}
return fs.mkdirp(siteLayoutDefaultPath);
})
.then(() => {
LAYOUT_FILES.forEach((layoutFile) => {
const layoutFilePath = path.join(siteLayoutDefaultPath, layoutFile);
fs.accessAsync(layoutFilePath).catch(() => {
if (fs.existsSync(layoutFilePath)) {
return Promise.resolve();
}
return fs.outputFileAsync(layoutFilePath, '');
});
});
})
.then(resolve)
.catch(reject);
});
Expand Down Expand Up @@ -274,6 +306,9 @@ Site.prototype.createPage = function (config) {
rootPath: this.rootPath,
searchable: config.searchable,
src: config.pageSrc,
layoutsAssetPath: path.relative(path.dirname(resultPath),
path.join(this.siteAssetsDestPath, LAYOUT_SITE_FOLDER_NAME)),
layout: config.layout,
title: config.title || '',
titlePrefix: this.siteConfig.titlePrefix,
headingIndexingLevel: this.siteConfig.headingIndexingLevel || HEADING_INDEXING_LEVEL_DEFAULT,
Expand Down Expand Up @@ -323,7 +358,11 @@ Site.prototype.collectAddressablePages = function () {
directories: false,
globs: [addressableGlob.glob],
ignore: [BOILERPLATE_FOLDER_NAME],
}).map(globPath => ({ src: globPath, searchable: addressableGlob.searchable }))), []);
}).map(globPath => ({
src: globPath,
searchable: addressableGlob.searchable,
layout: addressableGlob.layout,
}))), []);
// Add pages collected by walkSync without duplication
this.addressablePages = _.unionWith(this.addressablePages, globPaths,
((pageA, pageB) => pageA.src === pageB.src));
Expand Down Expand Up @@ -412,6 +451,7 @@ Site.prototype.generate = function (baseUrl) {
.then(() => this.buildAssets())
.then(() => this.buildSourceFiles())
.then(() => this.copyMarkBindAsset())
.then(() => this.copyLayouts())
.then(() => this.writeSiteData())
.then(() => {
const endTime = new Date();
Expand Down Expand Up @@ -549,6 +589,7 @@ Site.prototype.generatePages = function () {
faviconUrl,
pageSrc: page.src,
title: page.title,
layout: page.layout || LAYOUT_DEFAULT_NAME,
searchable: page.searchable !== 'no',
}));
const progressBar = new ProgressBar(`[:bar] :current / ${this.pages.length} pages built`,
Expand Down Expand Up @@ -619,6 +660,22 @@ Site.prototype.copyMarkBindAsset = function () {
});
};

/**
* Copies layouts to the assets folder
*/
Site.prototype.copyLayouts = function () {
const siteLayoutPath = path.join(this.rootPath, LAYOUT_FOLDER_PATH);
const layoutsDestPath = path.join(this.siteAssetsDestPath, LAYOUT_SITE_FOLDER_NAME);
if (!fs.existsSync(siteLayoutPath)) {
return Promise.resolve();
}
return new Promise((resolve, reject) => {
fs.copyAsync(siteLayoutPath, layoutsDestPath)
.then(resolve)
.catch(reject);
});
};

/**
* Writes the site data to a file
*/
Expand Down
2 changes: 2 additions & 0 deletions lib/template/page.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<link rel="stylesheet" href="<%- asset.glyphicons %>" >
<link rel="stylesheet" href="<%- asset.highlight %>">
<link rel="stylesheet" href="<%- asset.markbind %>">
<link rel="stylesheet" href="<%- asset.layoutStyle %>">
<% if (siteNav) { %><link rel="stylesheet" href="<%- asset.siteNavCss %>"><% } %>
<%- headFileBottomContent %>
<% if (faviconUrl) { %><link rel="icon" href="<%- faviconUrl %>"><% } %>
Expand All @@ -29,4 +30,5 @@
const baseUrl = '<%- baseUrl %>'
</script>
<script src="<%- asset.setup %>"></script>
<script src="<%- asset.layoutScript %>"></script>
</html>
1 change: 1 addition & 0 deletions test/test_site/_markbind/head/overwriteLayoutHead.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<script src="{{baseUrl}}/headFiles/overwriteLayoutScript.js"></script>
5 changes: 5 additions & 0 deletions test/test_site/_markbind/layouts/default/footer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<footer>
<div class="text-center">
Default footer
</div>
</footer>
4 changes: 4 additions & 0 deletions test/test_site/_markbind/layouts/default/head.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<head-top>
<meta name="default-head-top">
</head-top>
<meta name="default-head-bottom">
Empty file.
2 changes: 2 additions & 0 deletions test/test_site/_markbind/layouts/default/scripts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// eslint-disable-next-line no-console
console.info('Default script');
3 changes: 3 additions & 0 deletions test/test_site/_markbind/layouts/default/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
h1 {
color: red;
}
5 changes: 5 additions & 0 deletions test/test_site/_markbind/layouts/testLayout/footer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<footer>
<div class="text-center">
Layout footer
</div>
</footer>
4 changes: 4 additions & 0 deletions test/test_site/_markbind/layouts/testLayout/head.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<head-top>
<meta name="head-top">
</head-top>
<meta name="head-bottom">
3 changes: 3 additions & 0 deletions test/test_site/_markbind/layouts/testLayout/navigation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<navigation>
* [Layout Nav]
</navigation>
2 changes: 2 additions & 0 deletions test/test_site/_markbind/layouts/testLayout/scripts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// eslint-disable-next-line no-console
console.info('Layout script');
3 changes: 3 additions & 0 deletions test/test_site/_markbind/layouts/testLayout/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
h1 {
color: blue;
}
12 changes: 10 additions & 2 deletions test/test_site/expected/bugs/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>

<meta name="default-head-top">
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
Expand All @@ -12,8 +12,9 @@
<link rel="stylesheet" href="..\markbind\css\bootstrap-glyphicons.min.css" >
<link rel="stylesheet" href="..\markbind\css\github.min.css">
<link rel="stylesheet" href="..\markbind\css\markbind.css">
<link rel="stylesheet" href="..\markbind\layouts\default\styles.css">


<meta name="default-head-bottom">
<link rel="icon" href="/test_site/favicon.png">
</head>
<body>
Expand Down Expand Up @@ -72,6 +73,12 @@ <h2 id="remove-extra-space-in-links">Remove extra space in links<a class="fa fa-
<a href="https://github.com">link text</a>]</p>
</div>
</div>
<div id="flex-div"></div>
<footer>
<div class="text-center">
Default footer
</div>
</footer>
</div>
</body>
<script src="..\markbind\js\vue.min.js"></script>
Expand All @@ -82,4 +89,5 @@ <h2 id="remove-extra-space-in-links">Remove extra space in links<a class="fa fa-
const baseUrl = '/test_site'
</script>
<script src="..\markbind\js\setup.js"></script>
<script src="..\markbind\layouts\default\scripts.js"></script>
</html>
Loading