From 8fd37d34c92672f03bfd360bde7bf9c5fa67632e Mon Sep 17 00:00:00 2001 From: Jamos Tay Date: Fri, 28 Sep 2018 18:29:43 +0800 Subject: [PATCH] Add anchors to headings --- asset/css/markbind.css | 13 ++++ asset/js/setup.js | 12 ++++ docs/userGuide/contentAuthoring.md | 10 ++- lib/Page.js | 34 +++++++-- test/test_site/expected/bugs/index.html | 6 +- test/test_site/expected/index.html | 72 +++++++++---------- .../expected/markbind/css/markbind.css | 13 ++++ test/test_site/expected/markbind/js/setup.js | 27 ++++++- test/test_site/expected/sub_site/index.html | 2 +- test/test_site/expected/test_md_fragment.html | 2 +- 10 files changed, 143 insertions(+), 48 deletions(-) diff --git a/asset/css/markbind.css b/asset/css/markbind.css index 56850ec7cf..bd323dbb86 100644 --- a/asset/css/markbind.css +++ b/asset/css/markbind.css @@ -23,6 +23,19 @@ pre > code.hljs { outline: none !important; } +.fa.fa-anchor { + color: #ccc; + display: none; + font-size: 14px; + margin-left: 10px; + padding: 3px; + text-decoration: none; +} + +.fa.fa-anchor:hover { + color: #555; +} + .markbind-table { width: auto; } diff --git a/asset/js/setup.js b/asset/js/setup.js index 094a68f313..168c00424e 100644 --- a/asset/js/setup.js +++ b/asset/js/setup.js @@ -15,9 +15,21 @@ function flattenModals() { }); } +function setupAnchorVisibility() { + jQuery('h1, h2, h3, h4, h5, h6').each((index, heading) => { + jQuery(heading).on('mouseenter', function () { + jQuery(this).children('.fa.fa-anchor').show(); + }); + jQuery(heading).on('mouseleave', function () { + jQuery(this).children('.fa.fa-anchor').hide(); + }); + }); +} + function executeAfterMountedRoutines() { flattenModals(); scrollToUrlAnchorHeading(); + setupAnchorVisibility(); } function setupSiteNav() { diff --git a/docs/userGuide/contentAuthoring.md b/docs/userGuide/contentAuthoring.md index 7286c9a715..7ff2df21ae 100644 --- a/docs/userGuide/contentAuthoring.md +++ b/docs/userGuide/contentAuthoring.md @@ -589,4 +589,12 @@ Note: - Footers should not be nested in other components or HTML tags. If found inside, they will be shifted outside to be rendered properly. - [MarkBind components](#use-components) and [includes](#include-contents) are not supported in footers. - +### Heading Anchors + +Your site's readers may want to obtain a link to a heading within a page, to share with someone else for example. + +MarkBind automatically generates heading anchors for your page. + +When the reader hovers over a heading in your page, a small anchor icon 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! + + \ No newline at end of file diff --git a/lib/Page.js b/lib/Page.js index 3d685dfd21..f819bba803 100644 --- a/lib/Page.js +++ b/lib/Page.js @@ -25,6 +25,7 @@ const PAGE_CONTENT_ID = 'page-content'; const SITE_NAV_ID = 'site-nav'; const TITLE_PREFIX_SEPARATOR = ' - '; +const ANCHOR_HTML = ''; const DROPDOWN_BUTTON_ICON_HTML = '\n' + '\n' + ''; @@ -219,16 +220,25 @@ Page.prototype.collectHeadingsAndKeywords = function () { this.collectHeadingsAndKeywordsInContent($(`#${CONTENT_WRAPPER_ID}`).html(), null); }; +/** + * Generates a heading selector based on the indexing level + * @param headingIndexingLevel to generate + */ +function generateHeadingSelector(headingIndexingLevel) { + let headingsSelector = 'h1'; + for (let i = 2; i <= headingIndexingLevel; i += 1) { + headingsSelector += `, h${i}`; + } + return headingsSelector; +} + /** * Records headings and keywords inside content into this.headings and this.keywords respectively * @param content that contains the headings and keywords */ Page.prototype.collectHeadingsAndKeywordsInContent = function (content, lastHeading) { let $ = cheerio.load(content); - let headingsSelector = 'h1'; - for (let i = 2; i <= this.headingIndexingLevel; i += 1) { - headingsSelector += `, h${i}`; - } + const headingsSelector = generateHeadingSelector(this.headingIndexingLevel); $('modal').remove(); $('panel').not('panel panel') .each((index, panel) => { @@ -309,6 +319,21 @@ Page.prototype.concatenateHeadingsAndKeywords = function () { }); }; +/** + * Adds anchor links to headings in the page + * @param content of the page + */ +Page.prototype.addAnchors = function (content) { + const $ = cheerio.load(content, { xmlMode: false }); + if (this.headingIndexingLevel > 0) { + const headingsSelector = generateHeadingSelector(this.headingIndexingLevel); + $(headingsSelector).each((i, heading) => { + $(heading).append(ANCHOR_HTML.replace('#', `#${$(heading).attr('id')}`)); + }); + } + return $.html(); +}; + /** * Records the dynamic or static included files into this.includedFiles * @param dependencies array of maps of the external dependency and where it is included @@ -471,6 +496,7 @@ Page.prototype.generate = function (builtFiles) { .then(result => markbinder.resolveBaseUrl(result, fileConfig)) .then(result => fs.outputFileAsync(this.tempPath, result)) .then(() => markbinder.renderFile(this.tempPath, fileConfig)) + .then(result => this.addAnchors(result)) .then((result) => { this.content = htmlBeautify(result, { indent_size: 2 }); diff --git a/test/test_site/expected/bugs/index.html b/test/test_site/expected/bugs/index.html index 8eba5ec171..3afa0d69a0 100644 --- a/test/test_site/expected/bugs/index.html +++ b/test/test_site/expected/bugs/index.html @@ -26,7 +26,7 @@
-

popover initiated by trigger: honor trigger attribute

+

popover initiated by trigger: honor trigger attribute

Issue #49

Repro:

@@ -40,7 +40,7 @@

popover initiated

-

Support multiple inclusions of a modal

+

Support multiple inclusions of a modal

Issue #107

Repro:

@@ -65,7 +65,7 @@

Support multiple inclusions of a

- +

Issue #147

Repro:

This is a link. [ diff --git a/test/test_site/expected/index.html b/test/test_site/expected/index.html index 4d8acec1c7..4b9b5fc461 100644 --- a/test/test_site/expected/index.html +++ b/test/test_site/expected/index.html @@ -26,12 +26,12 @@

-

Variables that reference another variable

+

Variables that reference another variable

This variable can be referenced.

References can be several levels deep.

-

Heading with multiple keywords

+

Heading with multiple keywords

keyword 1 keyword 2

-

Heading with keyword in panel

+

Heading with keyword in panel

panel keyword -

Heading with included keyword

+

Heading with included keyword

included keyword

-

Heading with nested keyword

+

Heading with nested keyword

nested keyword
-

Normal include

+

Normal include

-

Establishing Requirements

+

Establishing Requirements

Requirements gathering, requirements elicitation, requirements analysis, requirements capture are some of the terms commonly and interchangeably used to represent the activity of understanding what a software product should do.

There are many techniques used during a requirements gathering. The following are some of the techniques.

-

Brainstorming

+

Brainstorming

Brainstorming is a group activity designed to generate a large number of diverse and creative ideas for the solution of a problem. In a brainstorming session there are no "bad" ideas. The aim is to generate ideas; not to validate them. Brainstorming encourages you to "think outside the box" and put "crazy" ideas on the table without fear of rejection.

-

User surveys

+

User surveys

Carefully designed questionnaires can be used to solicit responses and opinions from a large number of users regarding any current system or a new innovation.

-

Focus groups

+

Focus groups

Focus groups are a kind of informal interview within an interactive group setting. A group of people are asked about their understanding of a specific issue or a process. Focus groups can bring out undiscovered conflicts and misunderstandings among stakeholder interests which can then be resolved or clarified as necessary.

-

Include segment

+

Include segment

Requirements gathering, requirements elicitation, requirements analysis, requirements capture are some of the terms commonly and interchangeably used to represent the activity of understanding what a software product should do.

-

Dynamic include

+

Dynamic include

-

Boilerplate include

+

Boilerplate include

@@ -191,7 +191,7 @@

Boilerplate include

-

Nested include

+

Nested include

  1. Establishing requirements: Requirements gathering, requirements elicitation, requirements analysis, @@ -203,22 +203,22 @@

    Nested include

    specification that specifies how the product will address the requirements.
-

HTML include

+

HTML include

This is a HTML document

It is possible to use Markdown in HTML

-

Mbd, Mbdf include

+

Mbd, Mbdf include

MarkBind supports .mbd files.

MarkBind supports .mbdf files.

-

Include from another Markbind site

+

Include from another Markbind site

This is a page from another Markbind site.

-

Feature list

+

Feature list

It is a list of features (or functionalities) grouped according to some criteria such as priority (e.g. must-have, nice-to-have, etc. ), order of delivery, object or process related (e.g. order-related, invoice-related, etc.). Here is a sample feature list from Minesweeper (only a brief description has been provided to save space).

    @@ -228,45 +228,45 @@

    Feature list

  1. Timer – Additional fixed time restriction on the player.
-

Panel without src

+

Panel without src

-

Panel without src content heading

+

Panel without src content heading

-

Panel with normal src

+

Panel with normal src

-

Panel with src from a page segment

+

Panel with src from a page segment

-

Panel with boilerplate

+

Panel with boilerplate

-

Nested panel

+

Nested panel

-

Nested panel without src

+

Nested panel without src

-

Panel content of outer nested panel

+

Panel content of outer nested panel

-

Panel content of inner nested panel

+

Panel content of inner nested panel

-

Panel with src from another Markbind site

+

Panel with src from another Markbind site

-

Modal with panel inside

+

Modal with panel inside

trigger

-

Panel content inside modal

+

Panel content inside modal

-

Unexpanded panel

+

Unexpanded panel

-

Panel content of unexpanded panel should not appear in search data

+

Panel content of unexpanded panel should not appear in search data

-

Panel content inside unexpanded panel should not appear in search data

+

Panel content inside unexpanded panel should not appear in search data

@@ -274,7 +274,7 @@