diff --git a/asset/css/markbind.css b/asset/css/markbind.css index 24462b16fa..13f91e660b 100644 --- a/asset/css/markbind.css +++ b/asset/css/markbind.css @@ -111,7 +111,7 @@ code.hljs:hover { /* Header */ -header.header-fixed { +header[fixed] { position: fixed; width: 100%; z-index: 1000; diff --git a/asset/js/setup.js b/asset/js/setup.js index d7910b2eb3..914bbbefc3 100644 --- a/asset/js/setup.js +++ b/asset/js/setup.js @@ -20,32 +20,30 @@ function insertCss(cssCode) { } function setupAnchorsForFixedNavbar() { - const headerSelector = jQuery('header'); - const isFixed = headerSelector.filter('.header-fixed').length !== 0; + const headerSelector = jQuery('header[fixed]'); + const isFixed = headerSelector.length !== 0; if (!isFixed) { return; } const headerHeight = headerSelector.height(); const bufferHeight = 1; - jQuery('.nav-inner').css('padding-top', `calc(${headerHeight}px)`); + jQuery('.nav-inner').css('padding-top', `calc(${headerHeight}px + 1rem)`); jQuery('#content-wrapper').css('padding-top', `calc(${headerHeight}px)`); insertCss( `span.anchor { - display: block; position: relative; top: calc(-${headerHeight}px - ${bufferHeight}rem) }`, ); + insertCss(`span.card-container::before { + display: block; + content: ''; + margin-top: calc(-${headerHeight}px - ${bufferHeight}rem); + height: calc(${headerHeight}px + ${bufferHeight}rem); + }`); jQuery('h1, h2, h3, h4, h5, h6, .header-wrapper').each((index, heading) => { if (heading.id) { - /** - * Fixing the top navbar would break anchor navigation, - * by creating empty spans above the tag we can prevent - * the headings from being covered by the navbar. - */ - const spanId = heading.id; - heading.insertAdjacentHTML('beforebegin', ``); jQuery(heading).removeAttr('id'); // to avoid duplicated id problem } }); diff --git a/docs/_markbind/headers/header.md b/docs/_markbind/headers/header.md index 57f169c4b7..2a771742cb 100644 --- a/docs/_markbind/headers/header.md +++ b/docs/_markbind/headers/header.md @@ -1,4 +1,4 @@ -
+
diff --git a/docs/_markbind/layouts/devGuide/header.md b/docs/_markbind/layouts/devGuide/header.md index 57f169c4b7..2a771742cb 100644 --- a/docs/_markbind/layouts/devGuide/header.md +++ b/docs/_markbind/layouts/devGuide/header.md @@ -1,4 +1,4 @@ -
+
diff --git a/docs/_markbind/layouts/userGuide/header.md b/docs/_markbind/layouts/userGuide/header.md index 57f169c4b7..2a771742cb 100644 --- a/docs/_markbind/layouts/userGuide/header.md +++ b/docs/_markbind/layouts/userGuide/header.md @@ -1,4 +1,4 @@ -
+
diff --git a/docs/userGuide/syntax/headers.mbdf b/docs/userGuide/syntax/headers.mbdf index 9cd3fef74f..bcf1e5525a 100644 --- a/docs/userGuide/syntax/headers.mbdf +++ b/docs/userGuide/syntax/headers.mbdf @@ -23,6 +23,17 @@ In the page that you want to include the header: ``` +You can fix the header to be always on the top by add `fixed` attribute to the `
` tag of your header file: + +{{ icon_example }} +**`_markbind/headers/`**`header.md`: +```html +
+ +
+ +``` + Notes: - Any inline headers will be removed by MarkBind to ensure compatibility with header files. - If a [Layout]({{ baseUrl }}/userGuide/tweakingThePageStructure.html#page-layouts) is specified, the header file specified in the `` will override the header within the Layout. diff --git a/src/Page.js b/src/Page.js index b407bb50ab..a5405d90c3 100644 --- a/src/Page.js +++ b/src/Page.js @@ -241,6 +241,12 @@ class Page { * @type {boolean} */ this.hasSiteNav = false; + + /** + * Flag to indicate whether a fixed header is enabled. + * @type {boolean} + */ + this.fixedHeader = false; } /** @@ -604,9 +610,11 @@ class Page { /** * Inserts the page layout's header to the start of the page + * Determines if a fixed header is present, update the page config accordingly * @param pageData a page with its front matter collected + * @param {FileConfig} fileConfig */ - insertHeaderFile(pageData) { + insertHeaderFile(pageData, fileConfig) { const { header } = this.frontMatter; if (header === FRONT_MATTER_NONE_ATTR) { return pageData; @@ -624,6 +632,13 @@ class Page { } // Retrieve Markdown file contents const headerContent = fs.readFileSync(headerPath, 'utf8'); + // Decide if fixed header is applied + const headerSelector = cheerio.load(headerContent)('header'); + if (headerSelector.length >= 1 + && headerSelector[0].attribs.fixed !== undefined) { + this.fixedHeader = true; + fileConfig.fixedHeader = true; + } // Set header file as an includedFile this.includedFiles.add(headerPath); // Map variables @@ -894,6 +909,7 @@ class Page { * @property {string} rootPath * @property {Object} userDefinedVariablesMap * @property {Object} headerIdMap + * @property {boolean} fixedHeader indicates whether the header of the page is fixed */ generate(builtFiles) { @@ -911,6 +927,7 @@ class Page { rootPath: this.rootPath, userDefinedVariablesMap: this.userDefinedVariablesMap, headerIdMap: this.headerIdMap, + fixedHeader: this.fixedHeader, }; return new Promise((resolve, reject) => { markbinder.includeFile(this.sourcePath, fileConfig) @@ -924,7 +941,7 @@ class Page { .then(result => this.collectPluginSources(result)) .then(result => this.preRender(result)) .then(result => this.insertSiteNav((result))) - .then(result => this.insertHeaderFile(result)) + .then(result => this.insertHeaderFile(result, fileConfig)) .then(result => this.insertFooterFile(result)) .then(result => Page.insertTemporaryStyles(result)) .then(result => markbinder.resolveBaseUrl(result, fileConfig)) diff --git a/src/lib/markbind/src/parser.js b/src/lib/markbind/src/parser.js index 97bdf4be25..3fb28f84a6 100644 --- a/src/lib/markbind/src/parser.js +++ b/src/lib/markbind/src/parser.js @@ -292,7 +292,9 @@ class Parser { node.name = node.name.toLowerCase(); } - if ((/^h[1-6]$/).test(node.name) && !node.attribs.id) { + const isHeadingTag = (/^h[1-6]$/).test(node.name); + + if (isHeadingTag && !node.attribs.id) { const textContent = utils.getTextContent(node); // remove the '<' and '>' symbols that markdown-it uses to escape '<' and '>' const cleanedContent = textContent.replace(/<|>/g, ''); @@ -353,6 +355,11 @@ class Parser { componentParser.postParseComponents(node, this._onError); + // If a fixed header is applied to this page, generate dummy spans as anchor points + if (config.fixedHeader && isHeadingTag && node.attribs.id) { + cheerio(node).append(cheerio.parseHTML(``)); + } + return node; } diff --git a/test/functional/test_site/expected/markbind/css/markbind.css b/test/functional/test_site/expected/markbind/css/markbind.css index 24462b16fa..13f91e660b 100644 --- a/test/functional/test_site/expected/markbind/css/markbind.css +++ b/test/functional/test_site/expected/markbind/css/markbind.css @@ -111,7 +111,7 @@ code.hljs:hover { /* Header */ -header.header-fixed { +header[fixed] { position: fixed; width: 100%; z-index: 1000; diff --git a/test/functional/test_site/expected/markbind/js/setup.js b/test/functional/test_site/expected/markbind/js/setup.js index d7910b2eb3..914bbbefc3 100644 --- a/test/functional/test_site/expected/markbind/js/setup.js +++ b/test/functional/test_site/expected/markbind/js/setup.js @@ -20,32 +20,30 @@ function insertCss(cssCode) { } function setupAnchorsForFixedNavbar() { - const headerSelector = jQuery('header'); - const isFixed = headerSelector.filter('.header-fixed').length !== 0; + const headerSelector = jQuery('header[fixed]'); + const isFixed = headerSelector.length !== 0; if (!isFixed) { return; } const headerHeight = headerSelector.height(); const bufferHeight = 1; - jQuery('.nav-inner').css('padding-top', `calc(${headerHeight}px)`); + jQuery('.nav-inner').css('padding-top', `calc(${headerHeight}px + 1rem)`); jQuery('#content-wrapper').css('padding-top', `calc(${headerHeight}px)`); insertCss( `span.anchor { - display: block; position: relative; top: calc(-${headerHeight}px - ${bufferHeight}rem) }`, ); + insertCss(`span.card-container::before { + display: block; + content: ''; + margin-top: calc(-${headerHeight}px - ${bufferHeight}rem); + height: calc(${headerHeight}px + ${bufferHeight}rem); + }`); jQuery('h1, h2, h3, h4, h5, h6, .header-wrapper').each((index, heading) => { if (heading.id) { - /** - * Fixing the top navbar would break anchor navigation, - * by creating empty spans above the tag we can prevent - * the headings from being covered by the navbar. - */ - const spanId = heading.id; - heading.insertAdjacentHTML('beforebegin', ``); jQuery(heading).removeAttr('id'); // to avoid duplicated id problem } }); diff --git a/test/functional/test_site_algolia_plugin/expected/markbind/css/markbind.css b/test/functional/test_site_algolia_plugin/expected/markbind/css/markbind.css index 24462b16fa..13f91e660b 100644 --- a/test/functional/test_site_algolia_plugin/expected/markbind/css/markbind.css +++ b/test/functional/test_site_algolia_plugin/expected/markbind/css/markbind.css @@ -111,7 +111,7 @@ code.hljs:hover { /* Header */ -header.header-fixed { +header[fixed] { position: fixed; width: 100%; z-index: 1000; diff --git a/test/functional/test_site_algolia_plugin/expected/markbind/js/setup.js b/test/functional/test_site_algolia_plugin/expected/markbind/js/setup.js index d7910b2eb3..914bbbefc3 100644 --- a/test/functional/test_site_algolia_plugin/expected/markbind/js/setup.js +++ b/test/functional/test_site_algolia_plugin/expected/markbind/js/setup.js @@ -20,32 +20,30 @@ function insertCss(cssCode) { } function setupAnchorsForFixedNavbar() { - const headerSelector = jQuery('header'); - const isFixed = headerSelector.filter('.header-fixed').length !== 0; + const headerSelector = jQuery('header[fixed]'); + const isFixed = headerSelector.length !== 0; if (!isFixed) { return; } const headerHeight = headerSelector.height(); const bufferHeight = 1; - jQuery('.nav-inner').css('padding-top', `calc(${headerHeight}px)`); + jQuery('.nav-inner').css('padding-top', `calc(${headerHeight}px + 1rem)`); jQuery('#content-wrapper').css('padding-top', `calc(${headerHeight}px)`); insertCss( `span.anchor { - display: block; position: relative; top: calc(-${headerHeight}px - ${bufferHeight}rem) }`, ); + insertCss(`span.card-container::before { + display: block; + content: ''; + margin-top: calc(-${headerHeight}px - ${bufferHeight}rem); + height: calc(${headerHeight}px + ${bufferHeight}rem); + }`); jQuery('h1, h2, h3, h4, h5, h6, .header-wrapper').each((index, heading) => { if (heading.id) { - /** - * Fixing the top navbar would break anchor navigation, - * by creating empty spans above the tag we can prevent - * the headings from being covered by the navbar. - */ - const spanId = heading.id; - heading.insertAdjacentHTML('beforebegin', ``); jQuery(heading).removeAttr('id'); // to avoid duplicated id problem } }); diff --git a/test/functional/test_site_convert/expected/markbind/css/markbind.css b/test/functional/test_site_convert/expected/markbind/css/markbind.css index 24462b16fa..13f91e660b 100644 --- a/test/functional/test_site_convert/expected/markbind/css/markbind.css +++ b/test/functional/test_site_convert/expected/markbind/css/markbind.css @@ -111,7 +111,7 @@ code.hljs:hover { /* Header */ -header.header-fixed { +header[fixed] { position: fixed; width: 100%; z-index: 1000; diff --git a/test/functional/test_site_convert/expected/markbind/js/setup.js b/test/functional/test_site_convert/expected/markbind/js/setup.js index d7910b2eb3..914bbbefc3 100644 --- a/test/functional/test_site_convert/expected/markbind/js/setup.js +++ b/test/functional/test_site_convert/expected/markbind/js/setup.js @@ -20,32 +20,30 @@ function insertCss(cssCode) { } function setupAnchorsForFixedNavbar() { - const headerSelector = jQuery('header'); - const isFixed = headerSelector.filter('.header-fixed').length !== 0; + const headerSelector = jQuery('header[fixed]'); + const isFixed = headerSelector.length !== 0; if (!isFixed) { return; } const headerHeight = headerSelector.height(); const bufferHeight = 1; - jQuery('.nav-inner').css('padding-top', `calc(${headerHeight}px)`); + jQuery('.nav-inner').css('padding-top', `calc(${headerHeight}px + 1rem)`); jQuery('#content-wrapper').css('padding-top', `calc(${headerHeight}px)`); insertCss( `span.anchor { - display: block; position: relative; top: calc(-${headerHeight}px - ${bufferHeight}rem) }`, ); + insertCss(`span.card-container::before { + display: block; + content: ''; + margin-top: calc(-${headerHeight}px - ${bufferHeight}rem); + height: calc(${headerHeight}px + ${bufferHeight}rem); + }`); jQuery('h1, h2, h3, h4, h5, h6, .header-wrapper').each((index, heading) => { if (heading.id) { - /** - * Fixing the top navbar would break anchor navigation, - * by creating empty spans above the tag we can prevent - * the headings from being covered by the navbar. - */ - const spanId = heading.id; - heading.insertAdjacentHTML('beforebegin', ``); jQuery(heading).removeAttr('id'); // to avoid duplicated id problem } }); diff --git a/test/functional/test_site_expressive_layout/expected/markbind/css/markbind.css b/test/functional/test_site_expressive_layout/expected/markbind/css/markbind.css index 24462b16fa..13f91e660b 100644 --- a/test/functional/test_site_expressive_layout/expected/markbind/css/markbind.css +++ b/test/functional/test_site_expressive_layout/expected/markbind/css/markbind.css @@ -111,7 +111,7 @@ code.hljs:hover { /* Header */ -header.header-fixed { +header[fixed] { position: fixed; width: 100%; z-index: 1000; diff --git a/test/functional/test_site_expressive_layout/expected/markbind/js/setup.js b/test/functional/test_site_expressive_layout/expected/markbind/js/setup.js index d7910b2eb3..914bbbefc3 100644 --- a/test/functional/test_site_expressive_layout/expected/markbind/js/setup.js +++ b/test/functional/test_site_expressive_layout/expected/markbind/js/setup.js @@ -20,32 +20,30 @@ function insertCss(cssCode) { } function setupAnchorsForFixedNavbar() { - const headerSelector = jQuery('header'); - const isFixed = headerSelector.filter('.header-fixed').length !== 0; + const headerSelector = jQuery('header[fixed]'); + const isFixed = headerSelector.length !== 0; if (!isFixed) { return; } const headerHeight = headerSelector.height(); const bufferHeight = 1; - jQuery('.nav-inner').css('padding-top', `calc(${headerHeight}px)`); + jQuery('.nav-inner').css('padding-top', `calc(${headerHeight}px + 1rem)`); jQuery('#content-wrapper').css('padding-top', `calc(${headerHeight}px)`); insertCss( `span.anchor { - display: block; position: relative; top: calc(-${headerHeight}px - ${bufferHeight}rem) }`, ); + insertCss(`span.card-container::before { + display: block; + content: ''; + margin-top: calc(-${headerHeight}px - ${bufferHeight}rem); + height: calc(${headerHeight}px + ${bufferHeight}rem); + }`); jQuery('h1, h2, h3, h4, h5, h6, .header-wrapper').each((index, heading) => { if (heading.id) { - /** - * Fixing the top navbar would break anchor navigation, - * by creating empty spans above the tag we can prevent - * the headings from being covered by the navbar. - */ - const spanId = heading.id; - heading.insertAdjacentHTML('beforebegin', ``); jQuery(heading).removeAttr('id'); // to avoid duplicated id problem } }); diff --git a/test/functional/test_site_special_tags/expected/markbind/css/markbind.css b/test/functional/test_site_special_tags/expected/markbind/css/markbind.css index 24462b16fa..13f91e660b 100644 --- a/test/functional/test_site_special_tags/expected/markbind/css/markbind.css +++ b/test/functional/test_site_special_tags/expected/markbind/css/markbind.css @@ -111,7 +111,7 @@ code.hljs:hover { /* Header */ -header.header-fixed { +header[fixed] { position: fixed; width: 100%; z-index: 1000; diff --git a/test/functional/test_site_special_tags/expected/markbind/js/setup.js b/test/functional/test_site_special_tags/expected/markbind/js/setup.js index d7910b2eb3..914bbbefc3 100644 --- a/test/functional/test_site_special_tags/expected/markbind/js/setup.js +++ b/test/functional/test_site_special_tags/expected/markbind/js/setup.js @@ -20,32 +20,30 @@ function insertCss(cssCode) { } function setupAnchorsForFixedNavbar() { - const headerSelector = jQuery('header'); - const isFixed = headerSelector.filter('.header-fixed').length !== 0; + const headerSelector = jQuery('header[fixed]'); + const isFixed = headerSelector.length !== 0; if (!isFixed) { return; } const headerHeight = headerSelector.height(); const bufferHeight = 1; - jQuery('.nav-inner').css('padding-top', `calc(${headerHeight}px)`); + jQuery('.nav-inner').css('padding-top', `calc(${headerHeight}px + 1rem)`); jQuery('#content-wrapper').css('padding-top', `calc(${headerHeight}px)`); insertCss( `span.anchor { - display: block; position: relative; top: calc(-${headerHeight}px - ${bufferHeight}rem) }`, ); + insertCss(`span.card-container::before { + display: block; + content: ''; + margin-top: calc(-${headerHeight}px - ${bufferHeight}rem); + height: calc(${headerHeight}px + ${bufferHeight}rem); + }`); jQuery('h1, h2, h3, h4, h5, h6, .header-wrapper').each((index, heading) => { if (heading.id) { - /** - * Fixing the top navbar would break anchor navigation, - * by creating empty spans above the tag we can prevent - * the headings from being covered by the navbar. - */ - const spanId = heading.id; - heading.insertAdjacentHTML('beforebegin', ``); jQuery(heading).removeAttr('id'); // to avoid duplicated id problem } }); diff --git a/test/functional/test_site_templates/test_default/expected/markbind/css/markbind.css b/test/functional/test_site_templates/test_default/expected/markbind/css/markbind.css index 24462b16fa..13f91e660b 100644 --- a/test/functional/test_site_templates/test_default/expected/markbind/css/markbind.css +++ b/test/functional/test_site_templates/test_default/expected/markbind/css/markbind.css @@ -111,7 +111,7 @@ code.hljs:hover { /* Header */ -header.header-fixed { +header[fixed] { position: fixed; width: 100%; z-index: 1000; diff --git a/test/functional/test_site_templates/test_default/expected/markbind/js/setup.js b/test/functional/test_site_templates/test_default/expected/markbind/js/setup.js index d7910b2eb3..914bbbefc3 100644 --- a/test/functional/test_site_templates/test_default/expected/markbind/js/setup.js +++ b/test/functional/test_site_templates/test_default/expected/markbind/js/setup.js @@ -20,32 +20,30 @@ function insertCss(cssCode) { } function setupAnchorsForFixedNavbar() { - const headerSelector = jQuery('header'); - const isFixed = headerSelector.filter('.header-fixed').length !== 0; + const headerSelector = jQuery('header[fixed]'); + const isFixed = headerSelector.length !== 0; if (!isFixed) { return; } const headerHeight = headerSelector.height(); const bufferHeight = 1; - jQuery('.nav-inner').css('padding-top', `calc(${headerHeight}px)`); + jQuery('.nav-inner').css('padding-top', `calc(${headerHeight}px + 1rem)`); jQuery('#content-wrapper').css('padding-top', `calc(${headerHeight}px)`); insertCss( `span.anchor { - display: block; position: relative; top: calc(-${headerHeight}px - ${bufferHeight}rem) }`, ); + insertCss(`span.card-container::before { + display: block; + content: ''; + margin-top: calc(-${headerHeight}px - ${bufferHeight}rem); + height: calc(${headerHeight}px + ${bufferHeight}rem); + }`); jQuery('h1, h2, h3, h4, h5, h6, .header-wrapper').each((index, heading) => { if (heading.id) { - /** - * Fixing the top navbar would break anchor navigation, - * by creating empty spans above the tag we can prevent - * the headings from being covered by the navbar. - */ - const spanId = heading.id; - heading.insertAdjacentHTML('beforebegin', ``); jQuery(heading).removeAttr('id'); // to avoid duplicated id problem } }); diff --git a/test/functional/test_site_templates/test_minimal/expected/markbind/css/markbind.css b/test/functional/test_site_templates/test_minimal/expected/markbind/css/markbind.css index 24462b16fa..13f91e660b 100644 --- a/test/functional/test_site_templates/test_minimal/expected/markbind/css/markbind.css +++ b/test/functional/test_site_templates/test_minimal/expected/markbind/css/markbind.css @@ -111,7 +111,7 @@ code.hljs:hover { /* Header */ -header.header-fixed { +header[fixed] { position: fixed; width: 100%; z-index: 1000; diff --git a/test/functional/test_site_templates/test_minimal/expected/markbind/js/setup.js b/test/functional/test_site_templates/test_minimal/expected/markbind/js/setup.js index d7910b2eb3..914bbbefc3 100644 --- a/test/functional/test_site_templates/test_minimal/expected/markbind/js/setup.js +++ b/test/functional/test_site_templates/test_minimal/expected/markbind/js/setup.js @@ -20,32 +20,30 @@ function insertCss(cssCode) { } function setupAnchorsForFixedNavbar() { - const headerSelector = jQuery('header'); - const isFixed = headerSelector.filter('.header-fixed').length !== 0; + const headerSelector = jQuery('header[fixed]'); + const isFixed = headerSelector.length !== 0; if (!isFixed) { return; } const headerHeight = headerSelector.height(); const bufferHeight = 1; - jQuery('.nav-inner').css('padding-top', `calc(${headerHeight}px)`); + jQuery('.nav-inner').css('padding-top', `calc(${headerHeight}px + 1rem)`); jQuery('#content-wrapper').css('padding-top', `calc(${headerHeight}px)`); insertCss( `span.anchor { - display: block; position: relative; top: calc(-${headerHeight}px - ${bufferHeight}rem) }`, ); + insertCss(`span.card-container::before { + display: block; + content: ''; + margin-top: calc(-${headerHeight}px - ${bufferHeight}rem); + height: calc(${headerHeight}px + ${bufferHeight}rem); + }`); jQuery('h1, h2, h3, h4, h5, h6, .header-wrapper').each((index, heading) => { if (heading.id) { - /** - * Fixing the top navbar would break anchor navigation, - * by creating empty spans above the tag we can prevent - * the headings from being covered by the navbar. - */ - const spanId = heading.id; - heading.insertAdjacentHTML('beforebegin', ``); jQuery(heading).removeAttr('id'); // to avoid duplicated id problem } });