From 82ba04add79d82f22db69e959691461827966166 Mon Sep 17 00:00:00 2001 From: Chng-Zhi-Xuan Date: Wed, 13 Mar 2019 16:51:55 +0800 Subject: [PATCH 01/15] Change Page.ejs template Updated template allows for seamless insertion of page sections while staying consistent with the holy grail layout --- src/template/page.ejs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/template/page.ejs b/src/template/page.ejs index f122e87615..d85e19e29b 100644 --- a/src/template/page.ejs +++ b/src/template/page.ejs @@ -25,9 +25,15 @@ <% if (faviconUrl) { %><% } %> data-spy="scroll" data-target="#page-nav" data-offset="100" <%_ } %>> -
- <%- content %> -
+
+ <%- headerHtml _%> +
+ <%- siteNavHtml _%> + <%- content %> + <%- pageNavHtml _%> +
+ <%- footerHtml _%> +
From bdff76a48473b86df692fc2e7f056fad95187981 Mon Sep 17 00:00:00 2001 From: Chng-Zhi-Xuan Date: Wed, 13 Mar 2019 16:57:07 +0800 Subject: [PATCH 02/15] Add Header file support Adding support for header files allows us to be consistent with the holy grail layout. --- src/Page.js | 30 ++++++++++++++++++++++++++++++ src/Site.js | 21 ++++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/Page.js b/src/Page.js index 4501ec76a3..ef82ea05d7 100644 --- a/src/Page.js +++ b/src/Page.js @@ -21,10 +21,12 @@ const CLI_VERSION = require('../package.json').version; const FOOTERS_FOLDER_PATH = '_markbind/footers'; const HEAD_FOLDER_PATH = '_markbind/head'; +const HEADERS_FOLDER_PATH = '_markbind/headers'; const LAYOUT_DEFAULT_NAME = 'default'; const LAYOUT_FOLDER_PATH = '_markbind/layouts'; const LAYOUT_FOOTER = 'footer.md'; const LAYOUT_HEAD = 'head.md'; +const LAYOUT_HEADER = 'header.md'; const LAYOUT_NAVIGATION = 'navigation.md'; const NAVIGATION_FOLDER_PATH = '_markbind/navigation'; @@ -535,6 +537,33 @@ Page.prototype.removeFrontMatter = function (includedPage) { return $.html(); }; +/** + * Inserts the page layout's header to the start of the page + * @param pageData a page with its front matter collected + */ +Page.prototype.insertHeaderFile = function (pageData) { + const { header } = this.frontMatter; + let headerFile; + if (header) { + headerFile = path.join(HEADERS_FOLDER_PATH, header); + } else { + headerFile = path.join(LAYOUT_FOLDER_PATH, this.frontMatter.layout, LAYOUT_HEADER); + } + const headerPath = path.join(this.rootPath, headerFile); + if (!fs.existsSync(headerPath)) { + return pageData; + } + // Retrieve Markdown file contents + const headerContent = fs.readFileSync(headerPath, 'utf8'); + // Set header file as an includedFile + this.includedFiles[headerPath] = true; + // Map variables + const newBaseUrl = calculateNewBaseUrl(this.sourcePath, this.rootPath, this.baseUrlMap) || ''; + const userDefinedVariables = this.userDefinedVariablesMap[path.join(this.rootPath, newBaseUrl)]; + return `${nunjucks.renderString(headerContent, userDefinedVariables)}\n${pageData}`; +}; + + /** * Inserts the footer specified in front matter to the end of the page * @param pageData a page with its front matter collected @@ -790,6 +819,7 @@ Page.prototype.generate = function (builtFiles) { .then(result => this.insertTemporaryStyles(result)) .then(result => this.insertFooter(result)) // Footer has to be inserted last to ensure proper formatting .then(result => formatFooter(result)) + .then(result => this.insertHeaderFile(result)) .then(result => markbinder.resolveBaseUrl(result, fileConfig)) .then(result => fs.outputFileAsync(this.tempPath, result)) .then(() => markbinder.renderFile(this.tempPath, fileConfig)) diff --git a/src/Site.js b/src/Site.js index 96ab50b95d..68d6326c66 100644 --- a/src/Site.js +++ b/src/Site.js @@ -40,6 +40,7 @@ const BUILT_IN_PLUGIN_FOLDER_NAME = 'plugins'; const FAVICON_DEFAULT_PATH = 'favicon.ico'; const FONT_AWESOME_PATH = 'asset/font-awesome.csv'; const FOOTER_PATH = '_markbind/footers/footer.md'; +const HEADER_PATH = '_markbind/headers/header.md'; const GLYPHICONS_PATH = 'asset/glyphicons.csv'; const HEAD_FOLDER_PATH = '_markbind/head'; const INDEX_MARKDOWN_FILE = 'index.md'; @@ -49,7 +50,7 @@ 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']; +const LAYOUT_FILES = ['navigation.md', 'head.md', 'footer.md', 'header.md', 'styles.css']; const LAYOUT_FOLDER_PATH = '_markbind/layouts'; const LAYOUT_SCRIPTS_PATH = 'scripts.js'; const LAYOUT_SITE_FOLDER_NAME = 'layouts'; @@ -119,9 +120,19 @@ const FOOTER_DEFAULT = '
\n' + ' \n' + '
\n'; +const HEADER_DEFAULT = '
\n' + + '
\n' + + '
\n' + + ' Start authoring your MarkBind website.\n' + + '
\n' + + '
\n' + + '
\n' + + '
\n'; + const INDEX_MARKDOWN_DEFAULT = '\n' + ' title: "Hello World"\n' + ' footer: footer.md\n' + + ' header: header.md\n' + ' siteNav: site-nav.md\n' + '\n\n' + '# Hello world\n' @@ -219,6 +230,7 @@ Site.initSite = function (rootPath) { const boilerplatePath = path.join(rootPath, BOILERPLATE_FOLDER_NAME); const configPath = path.join(rootPath, SITE_CONFIG_NAME); const footerPath = path.join(rootPath, FOOTER_PATH); + const headerPath = path.join(rootPath, HEADER_PATH); const headFolderPath = path.join(rootPath, HEAD_FOLDER_PATH); const indexPath = path.join(rootPath, INDEX_MARKDOWN_FILE); const siteNavPath = path.join(rootPath, SITE_NAV_PATH); @@ -264,6 +276,13 @@ Site.initSite = function (rootPath) { } return fs.outputFileAsync(footerPath, FOOTER_DEFAULT); }) + .then(() => fs.accessAsync(headerPath)) + .catch(() => { + if (fs.existsSync(headerPath)) { + return Promise.resolve(); + } + return fs.outputFileAsync(headerPath, HEADER_DEFAULT); + }) .then(() => fs.accessAsync(headFolderPath)) .catch(() => { if (fs.existsSync(headFolderPath)) { From cffe229c49287b3893e50a60f13e27486a94cc0e Mon Sep 17 00:00:00 2001 From: Chng-Zhi-Xuan Date: Wed, 13 Mar 2019 16:57:53 +0800 Subject: [PATCH 03/15] Test: Update Site.test.js for header files --- test/unit/Site.test.js | 16 +++++++++++++--- test/unit/utils/data.js | 11 +++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/test/unit/Site.test.js b/test/unit/Site.test.js index 8137ac7225..e0e6da1573 100644 --- a/test/unit/Site.test.js +++ b/test/unit/Site.test.js @@ -5,6 +5,7 @@ const Site = require('../../src/Site'); const { FOOTER_MD_DEFAULT, + HEADER_MD_DEFAULT, INDEX_MD_DEFAULT, PAGE_EJS, SITE_JSON_DEFAULT, @@ -47,6 +48,7 @@ test('Site Generate builds the correct amount of assets', async () => { 'inner/_markbind/layouts/default/footer.md': '', 'inner/_markbind/layouts/default/head.md': '', + 'inner/_markbind/layouts/default/header.md': '', 'inner/_markbind/layouts/default/navigation.md': '', 'inner/_markbind/layouts/default/scripts.js': '', 'inner/_markbind/layouts/default/styles.css': '', @@ -56,7 +58,7 @@ test('Site Generate builds the correct amount of assets', async () => { await site.generate(); const paths = Object.keys(fs.vol.toJSON()); const originalNumFiles = Object.keys(json).length; - const expectedNumBuilt = 19; + const expectedNumBuilt = 20; expect(paths.length).toEqual(originalNumFiles + expectedNumBuilt); // site @@ -89,6 +91,7 @@ test('Site Generate builds the correct amount of assets', async () => { // layouts expect(fs.existsSync(path.resolve('inner/_site/markbind/layouts/default/footer.md'))).toEqual(true); expect(fs.existsSync(path.resolve('inner/_site/markbind/layouts/default/head.md'))).toEqual(true); + expect(fs.existsSync(path.resolve('inner/_site/markbind/layouts/default/header.md'))).toEqual(true); expect(fs.existsSync(path.resolve('inner/_site/markbind/layouts/default/navigation.md'))).toEqual(true); expect(fs.existsSync(path.resolve('inner/_site/markbind/layouts/default/scripts.js'))).toEqual(true); expect(fs.existsSync(path.resolve('inner/_site/markbind/layouts/default/styles.css'))).toEqual(true); @@ -103,7 +106,7 @@ test('Site Init in existing directory generates correct assets', async () => { await Site.initSite(''); const paths = Object.keys(fs.vol.toJSON()); const originalNumFiles = Object.keys(json).length; - const expectedNumBuilt = 13; + const expectedNumBuilt = 15; expect(paths.length).toEqual(originalNumFiles + expectedNumBuilt); // _boilerplates @@ -115,6 +118,9 @@ test('Site Init in existing directory generates correct assets', async () => { // head folder expect(fs.existsSync(path.resolve('_markbind/head'), 'utf8')).toEqual(true); + // header.md + expect(fs.readFileSync(path.resolve('_markbind/headers/header.md'), 'utf8')).toEqual(HEADER_MD_DEFAULT); + // site-nav.md expect(fs.readFileSync(path.resolve('_markbind/navigation/site-nav.md'), 'utf8')) .toEqual(SITE_NAV_MD_DEFAULT); @@ -147,7 +153,7 @@ test('Site Init in directory which does not exist generates correct assets', asy await Site.initSite('newDir'); const paths = Object.keys(fs.vol.toJSON()); const originalNumFiles = Object.keys(json).length; - const expectedNumBuilt = 13; + const expectedNumBuilt = 15; expect(paths.length).toEqual(originalNumFiles + expectedNumBuilt); @@ -160,6 +166,10 @@ test('Site Init in directory which does not exist generates correct assets', asy // head folder expect(fs.existsSync(path.resolve('newDir/_markbind/head'), 'utf8')).toEqual(true); + // header.md + expect(fs.readFileSync(path.resolve('newDir/_markbind/headers/header.md'), 'utf8')) + .toEqual(HEADER_MD_DEFAULT); + // site-nav.md expect(fs.readFileSync(path.resolve('newDir/_markbind/navigation/site-nav.md'), 'utf8')) .toEqual(SITE_NAV_MD_DEFAULT); diff --git a/test/unit/utils/data.js b/test/unit/utils/data.js index cd7558d240..0948101d70 100644 --- a/test/unit/utils/data.js +++ b/test/unit/utils/data.js @@ -1,6 +1,7 @@ module.exports.LAYOUT_FILES_DEFAULT = [ 'footer.md', 'head.md', + 'header.md', 'navigation.md', 'styles.css', ]; @@ -85,9 +86,19 @@ module.exports.FOOTER_MD_DEFAULT = '
\n' + ' \n' + '
\n'; +module.exports.HEADER_MD_DEFAULT = '
\n' + + '
\n' + + '
\n' + + ' Start authoring your MarkBind website.\n' + + '
\n' + + '
\n' + + '
\n' + + '
\n'; + module.exports.INDEX_MD_DEFAULT = '\n' + ' title: "Hello World"\n' + ' footer: footer.md\n' + + ' header: header.md\n' + ' siteNav: site-nav.md\n' + '\n\n' + '# Hello world\n' From 15b2ae72cd91f5a3aeb990bd26ded4d8d80aa127 Mon Sep 17 00:00:00 2001 From: Chng-Zhi-Xuan Date: Wed, 13 Mar 2019 17:02:57 +0800 Subject: [PATCH 04/15] Remove support for inline footers and headers Checking and shifting elements around has large overhead. So let's remove support for inline footers and headers and promote authors to utilize page section files instead. --- src/Page.js | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/src/Page.js b/src/Page.js index ef82ea05d7..2cc60a7782 100644 --- a/src/Page.js +++ b/src/Page.js @@ -125,25 +125,14 @@ function calculateNewBaseUrl(filePath, root, lookUp) { return calculate(filePath, []); } -function formatFooter(pageData) { +function removePageHeaderAndFooter(pageData) { const $ = cheerio.load(pageData); - const footers = $('footer'); - if (footers.length === 0) { + const pageHeaderAndFooter = $('header', 'footer'); + if (pageHeaderAndFooter.length === 0) { return pageData; } // Remove preceding footers - footers.slice(0, -1).remove(); // footers.not(':last').remove(); - // Unwrap last footer - const lastFooter = footers.last(); - const lastFooterParents = lastFooter.parents(); - if (lastFooterParents.length) { - const lastFooterOutermostParent = lastFooterParents.last(); - lastFooterOutermostParent.after(lastFooter); - } - // Insert flex div before last footer - if (lastFooter.prev().attr('id') !== FLEX_DIV_ID) { - $(lastFooter).before(FLEX_DIV_HTML); - } + pageHeaderAndFooter.remove(); return $.html(); } @@ -568,7 +557,7 @@ Page.prototype.insertHeaderFile = function (pageData) { * Inserts the footer specified in front matter to the end of the page * @param pageData a page with its front matter collected */ -Page.prototype.insertFooter = function (pageData) { +Page.prototype.insertFooterFile = function (pageData) { const { footer } = this.frontMatter; let footerFile; if (footer) { @@ -812,14 +801,13 @@ Page.prototype.generate = function (builtFiles) { this.collectFrontMatter(result); return this.removeFrontMatter(result); }) + .then(result => removePageHeaderAndFooter(result)) .then(result => addContentWrapper(result)) .then(result => this.preRender(result)) - .then(result => this.insertPageNavWrapper(result)) .then(result => this.insertSiteNav((result))) .then(result => this.insertTemporaryStyles(result)) - .then(result => this.insertFooter(result)) // Footer has to be inserted last to ensure proper formatting - .then(result => formatFooter(result)) .then(result => this.insertHeaderFile(result)) + .then(result => this.insertFooterFile(result)) .then(result => markbinder.resolveBaseUrl(result, fileConfig)) .then(result => fs.outputFileAsync(this.tempPath, result)) .then(() => markbinder.renderFile(this.tempPath, fileConfig)) From 57d852cf70bc5bc807441ad168f2a213b4d8f751 Mon Sep 17 00:00:00 2001 From: Chng-Zhi-Xuan Date: Wed, 13 Mar 2019 17:08:15 +0800 Subject: [PATCH 05/15] Remove site nav expand button The current functionality of the site nav expand button is to change the width of site nav to either hide or show it on smaller screen widths. Under the new layout, changing the width of any side navigation menus will result in content shifting within the main content page. This is not reader friendly so let's remove the functionality. --- src/Page.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Page.js b/src/Page.js index 2cc60a7782..7af75e6c26 100644 --- a/src/Page.js +++ b/src/Page.js @@ -45,13 +45,6 @@ const DROPDOWN_BUTTON_ICON_HTML = '\n' + '\n' + ''; const DROPDOWN_EXPAND_KEYWORD = ':expanded:'; -const SITE_NAV_BUTTON_HTML = ''; const TEMP_NAVBAR_CLASS = 'temp-navbar'; const TEMP_DROPDOWN_CLASS = 'temp-dropdown'; From eac7513f76adb5c3c4e2f34ece04afa796c0f1ef Mon Sep 17 00:00:00 2001 From: Chng-Zhi-Xuan Date: Wed, 13 Mar 2019 17:15:58 +0800 Subject: [PATCH 06/15] Refactor inline styles to classes Inline styles cannot be overwritten by authors, so let's refactor them into classes for easy overwritting. --- asset/css/page-nav.css | 11 +++++++++++ asset/css/site-nav.css | 7 +++++++ src/Page.js | 14 +++++++------- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/asset/css/page-nav.css b/asset/css/page-nav.css index c8d709fec5..90b891662e 100644 --- a/asset/css/page-nav.css +++ b/asset/css/page-nav.css @@ -31,6 +31,17 @@ margin-right: 300px; transition: 0.4s; -webkit-transition: 0.4s; +.page-nav-title { + color: black; + white-space: inherit; +} + +.nested { + margin-left: 5%; +} + +.no-flex-wrap { + flex-wrap: nowrap; } /* Responsive site navigation */ diff --git a/asset/css/site-nav.css b/asset/css/site-nav.css index 4d030b05e8..a024640c40 100644 --- a/asset/css/site-nav.css +++ b/asset/css/site-nav.css @@ -73,6 +73,13 @@ -webkit-transition: 0.4s; } +/* Navigation list */ + +.site-nav-list { + list-style-type: none; + margin-left: -1em; +} + /* Navigation dropdown menu */ .dropdown-btn { diff --git a/src/Page.js b/src/Page.js index 7af75e6c26..331107d484 100644 --- a/src/Page.js +++ b/src/Page.js @@ -136,7 +136,7 @@ function formatSiteNav(renderedSiteNav, src) { return renderedSiteNav; } // Tidy up the style of the unordered list