diff --git a/.eslintignore b/.eslintignore
index 31e0c6686b..5855520002 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,4 +1,6 @@
*.min.*
src/lib/markbind/src/lib/markdown-it/*
+src/lib/markbind/src/lib/vue-attribute-renderer/*
+src/lib/markbind/src/lib/markdown-it-shared/*
!.eslintrc.js
diff --git a/asset/css/markbind.css b/asset/css/markbind.css
index 92328d46d5..e8bfdd009f 100644
--- a/asset/css/markbind.css
+++ b/asset/css/markbind.css
@@ -136,6 +136,11 @@ footer {
top: 0;
}
+/* TODO move this back to markdown-it-attr if bundling is implemented */
+.dimmed {
+ color: #777;
+}
+
/* Bootstrap small(sm) responsive breakpoint */
@media (max-width: 767.98px) {
.dropdown-menu > li > a {
diff --git a/docs/index.md b/docs/index.md
index 4f4741fc53..339b06ccc2 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -70,7 +70,7 @@ The example paragraph below has the following dynamic elements: a tooltip, a pop
In CS, a binary tree is a tree data structure in which each node has at most two children, which are referred to as the _left child_ and the _right child_. Primitive data types on the other hand ...
-
+

@@ -79,7 +79,7 @@ In CS, a binary tree is a
-
+
@@ -153,7 +153,7 @@ In CS, a binary tree is a tree data structure in which each node has at most two
-
+
diff --git a/docs/userGuide/addingPages.md b/docs/userGuide/addingPages.md
index 5bed9b62e8..c6d9662ddb 100644
--- a/docs/userGuide/addingPages.md
+++ b/docs/userGuide/addingPages.md
@@ -40,7 +40,7 @@ Here are the steps to add a new page to your site:
1. Update the [`pages` attribute of the `site.json`](siteConfiguration.html#pages) to cover the new file, if necessary.
1. Use the live preview to view the generated web page for the new file.
-
+
diff --git a/docs/userGuide/cliCommands.md b/docs/userGuide/cliCommands.md
index 57dcd468a6..e0586d1be3 100644
--- a/docs/userGuide/cliCommands.md
+++ b/docs/userGuide/cliCommands.md
@@ -97,7 +97,7 @@ MarkBind Command Line Interface (CLI) can be run in the following ways:
1. Starts a web server instance locally and makes the site available at `http://127.0.0.1:8080`.
1. Opens a live preview of the website.
-
+
diff --git a/docs/userGuide/components/advanced.md b/docs/userGuide/components/advanced.md
index c6b22569e6..f4fbe22187 100644
--- a/docs/userGuide/components/advanced.md
+++ b/docs/userGuide/components/advanced.md
@@ -74,15 +74,22 @@ Using the normal syntax, you are only able to use markdown formatting on heading
**Panel Slot Options:**
Slot name | Default class | Notes
--- | --- | ---
-`header` | `card-title` | Aligning text to the center of the panel is not possible, as the header element does not take up the entire container.
+header | `card-title` | Aligning text to the center of the panel is not possible, as the header element does not take up the entire container.
**Modal Slot Options:**
When using slots for Modals, you need to add a single blank line before each `` tag, in order for the customization to render correctly.
Slot name | Default class | Notes
--- | --- | ---
-`modal-header` | `modal-title` |
-`modal-footer` | `modal-footer` | Specifying `modal-footer` will override the `ok-text` attribute, and the OK button will not render.
+header `modal-header` (deprecated) | `modal-title` |
+footer `modal-footer` (deprecated) | `modal-footer` | Specifying `modal-footer` will override the `ok-text` attribute, and the OK button will not render.
+
+**Popover Slot Options:**
+Slot name | Default class
+--- | --- | ---
+header `title` (deprecated) | `popover-header`
+content | `popover-body`
+
### Inserting custom classes into components
diff --git a/docs/userGuide/gettingStarted.md b/docs/userGuide/gettingStarted.md
index 9efe454e6a..748fac72c6 100644
--- a/docs/userGuide/gettingStarted.md
+++ b/docs/userGuide/gettingStarted.md
@@ -73,7 +73,7 @@ By default, you will initialize Markbind with a feature filled [template](https:
Run the following command in the same directory. It will generate a website from your source files, start a web server, and open a live preview of your site in your default Browser.
-
+
diff --git a/docs/userGuide/makingTheSiteSearchable.md b/docs/userGuide/makingTheSiteSearchable.md
index 9c9e8dab4e..7f579abad3 100644
--- a/docs/userGuide/makingTheSiteSearchable.md
+++ b/docs/userGuide/makingTheSiteSearchable.md
@@ -18,7 +18,7 @@
**MarkBind comes with with an in-built _site search_ facility**. You can add a [Search Bar](usingComponents.html#search-bar) component to your pages %%(e.g., into the top navigation bar)%% to allow readers to search your website for keywords.
-**All headings of levels 1-3 are captured in the search index** by default. You can change this setting using the [`headingIndexLevel` property of the `site.json`](siteConfiguration.html#headingindexinglevel).
+**All markdown and html headings of levels 1-3 are captured in the search index** by default. You can change this setting using the [`headingIndexLevel` property of the `site.json`](siteConfiguration.html#headingindexinglevel).
diff --git a/docs/userGuide/syntax/badges.mbdf b/docs/userGuide/syntax/badges.mbdf
index f1df59445e..aadf1a8c11 100644
--- a/docs/userGuide/syntax/badges.mbdf
+++ b/docs/userGuide/syntax/badges.mbdf
@@ -65,8 +65,8 @@ Buttons:
Headings:
-### Feature X beta
-##### Feature Y stable
+### Feature X beta {.no-index}
+##### Feature Y stable {.no-index}
```
@@ -82,8 +82,8 @@ Buttons:
Headings:
-### Feature X beta
-##### Feature Y stable
+### Feature X beta {.no-index}
+##### Feature Y stable {.no-index}
@@ -111,6 +111,6 @@ Headings:
Success
-
\ No newline at end of file
+
diff --git a/docs/userGuide/syntax/boxes.mbdf b/docs/userGuide/syntax/boxes.mbdf
index bbbcf6b571..391d21ca3d 100644
--- a/docs/userGuide/syntax/boxes.mbdf
+++ b/docs/userGuide/syntax/boxes.mbdf
@@ -33,10 +33,11 @@
dismissible info
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
-
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
+
```
@@ -69,10 +70,11 @@
dismissible info
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
-
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
+
@@ -166,7 +168,8 @@ border-color | `String` | `null` |
border-left-color | `String` | `null` | Overrides border-color for the left border.
color | `String` | `null` | Color of the text.
dismissible | `Boolean` | `false` | Adds a button to close the box to the top right corner.
-icon | `String` | `null` |
+icon | `String` | `null` | Inline MarkDown text of the icon displayed on the left.
+header heading (deprecated) | `String` | `null` | Plain text of the header on the top right corner.
type | `String` | `'none'` | Supports: `info`, `warning`, `success`, `important`, `wrong`, `tip`, `definition`, or empty for default.
light | `Boolean` | `false` | Uses a light color scheme for the box.
diff --git a/docs/userGuide/syntax/dropdowns.mbdf b/docs/userGuide/syntax/dropdowns.mbdf
index 181ebfabcb..46d3f7372f 100644
--- a/docs/userGuide/syntax/dropdowns.mbdf
+++ b/docs/userGuide/syntax/dropdowns.mbdf
@@ -6,7 +6,7 @@
```html
-
+
diff --git a/docs/userGuide/syntax/indexing.mbdf b/docs/userGuide/syntax/indexing.mbdf
index 7e7c0741d0..fa2c0b09c8 100644
--- a/docs/userGuide/syntax/indexing.mbdf
+++ b/docs/userGuide/syntax/indexing.mbdf
@@ -1,6 +1,6 @@
## Including or Excluding Headings
-**You can specify headings which are to be included or excluded from the index built by MarkBind's built-in search feature** using the `.always-index` or `.no-index` attributes.
+**You can specify headings which are to be included or excluded from the index built by MarkBind's built-in search feature** using the `.always-index` or `.no-index` classes.
If you wish to index a specific heading outside the specified `headingIndexLevel`, you may add the `.always-index` attribute to the heading. Similarly, if you wish for a specific heading inside the specified `headingIndexLevel` not to be indexed, you may add the `.no-index` attribute to the heading.
@@ -13,4 +13,11 @@ If you wish to index a specific heading outside the specified `headingIndexLevel
# Heading inside heading index level that will not be indexed {.no-index}
```
+
+Equivalently,
+```html
+
Heading outside heading index level that will be indexed
+
+
Heading inside heading index level that will not be indexed
+```
diff --git a/docs/userGuide/syntax/modals.mbdf b/docs/userGuide/syntax/modals.mbdf
index fc6a1f47f0..cdfbe0abea 100644
--- a/docs/userGuide/syntax/modals.mbdf
+++ b/docs/userGuide/syntax/modals.mbdf
@@ -7,7 +7,7 @@
```html
More about trigger.
-
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
@@ -18,7 +18,7 @@ This is the same trigger as last one.
This is a trigger for a centered modal.
-
+
Centered
@@ -27,7 +27,7 @@ This is the same trigger as last one.
More about trigger.
-
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
@@ -38,7 +38,7 @@ This is the same trigger as last one.
This is a trigger for a centered modal.
-
+
Centered
@@ -51,7 +51,7 @@ This is the same trigger as last one.
****Options****
Name | type | Default | Description
--- | --- | --- | ---
-title | `String` | `''` | Title of the Modal component.
+header title (deprecated) | `String` | `''` | Header of the Modal component. Supports inline markdown text.
ok-text | `String` | `''` | Text for the OK button.
effect | `String` | `zoom` | Supports: `zoom`, `fade`.
id | `String` | | Used by [Trigger](#trigger) to activate the Modal by id.
@@ -65,7 +65,7 @@ backdrop | `Boolean` | `true` | Enables closing the Modal by clicking on the bac
```html
Click here to open a modal.
-
+
Modal content
```
@@ -74,7 +74,7 @@ Click here to open a modal
Hover here to open a modal.
-
+
Modal content
-
\ No newline at end of file
+
diff --git a/docs/userGuide/syntax/navBars.mbdf b/docs/userGuide/syntax/navBars.mbdf
index 40631e3709..80a67f661f 100644
--- a/docs/userGuide/syntax/navBars.mbdf
+++ b/docs/userGuide/syntax/navBars.mbdf
@@ -16,7 +16,7 @@ Note: **Navbars** should be placed within a [header file]({{ baseUrl }}/userguid
MarkBind
-
+
diff --git a/docs/userGuide/syntax/panels.mbdf b/docs/userGuide/syntax/panels.mbdf
index e1b1e39ec6..db2e11ace0 100644
--- a/docs/userGuide/syntax/panels.mbdf
+++ b/docs/userGuide/syntax/panels.mbdf
@@ -277,10 +277,10 @@
****Options****
-Name | Type | Default | Description
+Name | Type | Default | Description
--- | --- | --- | ---
-header | `String` | `''` | The clickable text on the Panel's header.
-alt | `String` | Panel header | The clickable text on the minimised Panel.
+header | `String` | `''` | The clickable text on the Panel's header. Supports MarkDown text.
+alt | `String` | Panel header | The clickable text on the minimised Panel. Supports MarkDown text.
expandable | `Boolean`| `true` | Whether Panel is expandable.
expanded | `Boolean` | `false` | Whether Panel is expanded or collapsed when loaded in.
minimized | `Boolean` | `false` | Whether Panel is minimized.
@@ -335,4 +335,4 @@ type | `String` | `light` | The type or color scheme of the panel (single). S
-
\ No newline at end of file
+
diff --git a/docs/userGuide/syntax/popovers.mbdf b/docs/userGuide/syntax/popovers.mbdf
index 2581e8ae70..279fab7fc4 100644
--- a/docs/userGuide/syntax/popovers.mbdf
+++ b/docs/userGuide/syntax/popovers.mbdf
@@ -17,37 +17,37 @@
-
Title
-
+
Header
+
-
+
-
+
-
+
-
Trigger
+
Trigger
-
+
-
+
-
Markdown
+
Markdown
-
+
-
Content using slot
-
+
Content using slot
+
This is a long content...
@@ -55,7 +55,7 @@
-
Wrap Text
+
Wrap Text
What do you say
```
@@ -74,37 +74,37 @@
-
Title
-
+
Header
+
-
+
-
+
-
+
-
Trigger
+
Trigger
-
+
-
+
-
Markdown
+
Markdown
-
+
-
Content using slot
-
+
Content using slot
+
This is a long content...
@@ -112,7 +112,7 @@
-
Wrap Text
+
Wrap Text
What do you say
@@ -148,9 +148,8 @@ Name | Type | Default | Description
---- | ---- | ------- | ------
trigger | `String` | `hover` | How the Popover is triggered. Supports: `click`, `focus`, `hover`, `contextmenu`.
effect | `String` | `fade` | Transition effect for Popover. Supports: `scale`, `fade`.
-title | `String` | `''` | Popover title, supports inline markdown text.
+header title (deprecated) | `String` | `''` | Popover header, supports inline markdown text.
content | `String` | `''` | Popover content, supports inline markdown text.
-header | `Boolean` | `true` | Whether to show the header.
placement | `String` | `top` | How to position the Popover. Supports: `top`, `left`, `right`, `bottom`.
@@ -159,7 +158,7 @@ placement | `String` | `top` | How to position the Popover. Supports: `top`,
```html
Hover over the keyword to see the popover.
-
+
description :+1:
@@ -173,7 +172,7 @@ description :+1:
Hover over the keyword to see the popover.
-
+
description :+1:
diff --git a/docs/userGuide/syntax/tabs.mbdf b/docs/userGuide/syntax/tabs.mbdf
index 2fce82e2c2..3618fb7ae0 100644
--- a/docs/userGuide/syntax/tabs.mbdf
+++ b/docs/userGuide/syntax/tabs.mbdf
@@ -6,20 +6,20 @@
```html
- ...
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla ullamcorper ultrices lobortis.
- star facts
+ Some stuff about stars ...
- ...
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla ullamcorper ultrices lobortis.
@@ -29,7 +29,7 @@
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla ullamcorper ultrices lobortis. Aenean leo lacus, congue vel auctor a, tincidunt sed nunc. Integer in odio sed nunc porttitor venenatis. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nullam fringilla tincidunt augue a pulvinar.
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla ullamcorper ultrices lobortis.
@@ -108,4 +108,4 @@ disabled | `Boolean` | `false` | Whether Tab Group is clickable and can be activ
-
\ No newline at end of file
+
diff --git a/docs/userGuide/syntax/tooltips.mbdf b/docs/userGuide/syntax/tooltips.mbdf
index a7c70931a7..b8c9949ac5 100644
--- a/docs/userGuide/syntax/tooltips.mbdf
+++ b/docs/userGuide/syntax/tooltips.mbdf
@@ -4,16 +4,16 @@
```html
-
+
-
+
-
+
-
+
@@ -43,16 +43,16 @@ Trigger
-
+
-
+
-
+
-
+
diff --git a/package-lock.json b/package-lock.json
index 394a9514e4..e466221389 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6302,6 +6302,16 @@
"resolved": "https://registry.npmjs.org/markdown-it-regexp/-/markdown-it-regexp-0.4.0.tgz",
"integrity": "sha1-1k1xPuzsVc5M/esyF1DswJniwtw="
},
+ "markdown-it-sub": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/markdown-it-sub/-/markdown-it-sub-1.0.0.tgz",
+ "integrity": "sha1-N1/WAm6ufdywEkl/ZBEZXqHjr+g="
+ },
+ "markdown-it-sup": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/markdown-it-sup/-/markdown-it-sup-1.0.0.tgz",
+ "integrity": "sha1-y5yf+RpSVawI8/09YyhuFd8KH8M="
+ },
"markdown-it-table-of-contents": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/markdown-it-table-of-contents/-/markdown-it-table-of-contents-0.4.4.tgz",
diff --git a/package.json b/package.json
index 1a4e9c4dce..dbf5fbe2c8 100644
--- a/package.json
+++ b/package.json
@@ -48,6 +48,8 @@
"markdown-it-table-of-contents": "^0.4.4",
"markdown-it-task-lists": "^1.4.1",
"markdown-it-video": "^0.6.3",
+ "markdown-it-sub": "^1.0.0",
+ "markdown-it-sup": "^1.0.0",
"nunjucks": "^3.2.0",
"path-is-inside": "^1.0.2",
"progress": "^2.0.3",
diff --git a/src/Page.js b/src/Page.js
index 8ec1d80dbd..880ed32198 100644
--- a/src/Page.js
+++ b/src/Page.js
@@ -91,6 +91,7 @@ class Page {
this.keywords = {};
this.navigableHeadings = {};
this.pageSectionsHtml = {};
+ this.headerIdMap = {};
// Flag to indicate whether this page has a site nav
this.hasSiteNav = false;
@@ -148,7 +149,8 @@ class Page {
}
/**
- * Collect headings outside of models and panels
+ * Collect headings outside of models and unexpanded panels.
+ * Collects headings from the header slots of unexpanded panels, but not its content.
* @param content, html content of a page
*/
collectNavigableHeadings(content) {
@@ -158,40 +160,49 @@ class Page {
return;
}
const $ = cheerio.load(content);
- $('modal')
- .remove();
- $(elementSelector)
- .each((i, elem) => {
- // Check if heading or panel is already inside an unexpanded panel
- let isInsideUnexpandedPanel = false;
- $(elem)
- .parents('panel')
- .each((j, elemParent) => {
- if (elemParent.attribs.expanded === undefined) {
- isInsideUnexpandedPanel = true;
- return false;
- }
- return true;
- });
- if (isInsideUnexpandedPanel) {
- return;
- }
- if (elem.name === 'panel') {
- // Get heading from Panel header attribute
- if (elem.attribs.header) {
- this.collectNavigableHeadings(md.render(elem.attribs.header));
- }
- } else if ($(elem)
- .attr('id') !== undefined) {
- // Headings already in content, with a valid ID
- this.navigableHeadings[$(elem)
- .attr('id')] = {
- text: $(elem)
- .text(),
- level: elem.name.replace('h', ''),
- };
+ $('modal').remove();
+ this._collectNavigableHeadings($, $.root()[0], elementSelector);
+ }
+
+
+ _collectNavigableHeadings($, context, pageNavSelector) {
+ $(pageNavSelector, context).each((i, elem) => {
+ // Check if heading or panel is already inside an unexpanded panel
+ let isInsideUnexpandedPanel = false;
+ const panelParents = $(elem).parentsUntil(context).filter('panel').not(elem);
+ panelParents.each((j, elemParent) => {
+ if (elemParent.attribs.expanded === undefined) {
+ isInsideUnexpandedPanel = true;
+ return false;
}
+ return true;
});
+ if (isInsideUnexpandedPanel) {
+ return;
+ }
+
+ // Check if heading / panel is under a panel's header slots, which is handled specially below.
+ const slotParents = $(elem).parentsUntil(context).filter('[slot="header"], [slot="_header"]').not(elem);
+ const panelSlotParents = slotParents.parent('panel');
+ if (panelSlotParents.length) {
+ return;
+ }
+
+ if (elem.name === 'panel') {
+ // Recurse only on the slot which has priority
+ let headings = $(elem).children('[slot="header"]');
+ headings = headings.length ? headings : $(elem).children('[slot="_header"]');
+ if (!headings.length) return;
+
+ this._collectNavigableHeadings($, headings.first(), pageNavSelector);
+ } else if ($(elem).attr('id') !== undefined) {
+ // Headings already in content, with a valid ID
+ this.navigableHeadings[$(elem).attr('id')] = {
+ text: $(elem).text(),
+ level: elem.name.replace('h', ''),
+ };
+ }
+ });
}
/**
@@ -214,14 +225,19 @@ class Page {
collectHeadingsAndKeywordsInContent(content, lastHeading, excludeHeadings, sourceTraversalStack) {
let $ = cheerio.load(content);
const headingsSelector = Page.generateHeadingSelector(this.headingIndexingLevel);
- $('modal')
- .remove();
- $('panel')
- .not('panel panel')
+ $('modal').remove();
+ $('panel').not('panel panel')
.each((index, panel) => {
- if (panel.attribs.header) {
- this.collectHeadingsAndKeywordsInContent(
- md.render(panel.attribs.header), lastHeading, excludeHeadings, sourceTraversalStack);
+ const slotHeader = $(panel).children('[slot="header"]');
+ if (slotHeader.length) {
+ this.collectHeadingsAndKeywordsInContent(slotHeader.html(),
+ lastHeading, excludeHeadings, sourceTraversalStack);
+ } else {
+ const headerAttr = $(panel).children('[slot="_header"]');
+ if (headerAttr.length) {
+ this.collectHeadingsAndKeywordsInContent(headerAttr.html(),
+ lastHeading, excludeHeadings, sourceTraversalStack);
+ }
}
})
.each((index, panel) => {
@@ -230,11 +246,13 @@ class Page {
if (!closestHeading) {
closestHeading = lastHeading;
}
- if (panel.attribs.header) {
- const panelHeader = md.render(panel.attribs.header);
- if ($(panelHeader)
- .is(headingsSelector)) {
- closestHeading = $(panelHeader);
+ const slotHeadings = $(panel).children('[slot="header"]').find(':header');
+ if (slotHeadings.length) {
+ closestHeading = slotHeadings.first();
+ } else {
+ const attributeHeadings = $(panel).children('[slot="_header"]').find(':header');
+ if (attributeHeadings.length) {
+ closestHeading = attributeHeadings.first();
}
}
if (panel.attribs.src) {
@@ -709,7 +727,8 @@ class Page {
$('dropdown')
.each((i, element) => {
const attributes = element.attribs;
- const placeholder = `
${attributes.text || ''}
`;
+ // TODO remove attributes.text once text attribute is fully deprecated
+ const placeholder = `
${attributes.header || attributes.text || ''}
`;
$(element)
.before(placeholder);
$(element)
@@ -743,6 +762,8 @@ class Page {
generate(builtFiles) {
this.includedFiles = new Set([this.sourcePath]);
+ this.headerIdMap = {}; // Reset for live reload
+
const markbinder = new MarkBind({
errorHandler: logger.error,
});
@@ -750,6 +771,7 @@ class Page {
baseUrlMap: this.baseUrlMap,
rootPath: this.rootPath,
userDefinedVariablesMap: this.userDefinedVariablesMap,
+ headerIdMap: this.headerIdMap,
};
return new Promise((resolve, reject) => {
markbinder.includeFile(this.sourcePath, fileConfig)
@@ -1034,6 +1056,7 @@ class Page {
.then(() => markbinder.renderFile(tempPath, {
baseUrlMap: this.baseUrlMap,
rootPath: this.rootPath,
+ headerIdMap: {},
}))
.then(result => markbinder.processDynamicResources(file, result))
.then((result) => {
diff --git a/src/lib/markbind/package-lock.json b/src/lib/markbind/package-lock.json
index 8d6f6beb02..f4453a3550 100644
--- a/src/lib/markbind/package-lock.json
+++ b/src/lib/markbind/package-lock.json
@@ -2353,6 +2353,16 @@
"resolved": "https://registry.npmjs.org/markdown-it-regexp/-/markdown-it-regexp-0.4.0.tgz",
"integrity": "sha1-1k1xPuzsVc5M/esyF1DswJniwtw="
},
+ "markdown-it-sub": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/markdown-it-sub/-/markdown-it-sub-1.0.0.tgz",
+ "integrity": "sha1-N1/WAm6ufdywEkl/ZBEZXqHjr+g="
+ },
+ "markdown-it-sup": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/markdown-it-sup/-/markdown-it-sup-1.0.0.tgz",
+ "integrity": "sha1-y5yf+RpSVawI8/09YyhuFd8KH8M="
+ },
"markdown-it-table-of-contents": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/markdown-it-table-of-contents/-/markdown-it-table-of-contents-0.4.4.tgz",
diff --git a/src/lib/markbind/package.json b/src/lib/markbind/package.json
index 7d403cec48..9965ed3e6f 100644
--- a/src/lib/markbind/package.json
+++ b/src/lib/markbind/package.json
@@ -28,6 +28,8 @@
"markdown-it-ins": "^2.0.0",
"markdown-it-mark": "^2.0.0",
"markdown-it-regexp": "^0.4.0",
+ "markdown-it-sub": "^1.0.0",
+ "markdown-it-sup": "^1.0.0",
"markdown-it-table-of-contents": "^0.4.4",
"markdown-it-task-lists": "^1.4.1",
"markdown-it-video": "^0.6.3",
diff --git a/src/lib/markbind/src/lib/markdown-it/markdown-it-dimmed.js b/src/lib/markbind/src/lib/markdown-it-shared/markdown-it-dimmed.js
similarity index 99%
rename from src/lib/markbind/src/lib/markdown-it/markdown-it-dimmed.js
rename to src/lib/markbind/src/lib/markdown-it-shared/markdown-it-dimmed.js
index 479d810e98..65c574e604 100644
--- a/src/lib/markbind/src/lib/markdown-it/markdown-it-dimmed.js
+++ b/src/lib/markbind/src/lib/markdown-it-shared/markdown-it-dimmed.js
@@ -122,4 +122,4 @@ module.exports = function dimmed_plugin(md) {
md.inline.ruler.before('emphasis', 'dimmed', tokenize);
md.inline.ruler2.before('emphasis', 'dimmed', postProcess);
-};
\ No newline at end of file
+};
diff --git a/src/lib/markbind/src/lib/markdown-it-shared/markdown-it-emoji-fixed.js b/src/lib/markbind/src/lib/markdown-it-shared/markdown-it-emoji-fixed.js
new file mode 100644
index 0000000000..cc1ddba25a
--- /dev/null
+++ b/src/lib/markbind/src/lib/markdown-it-shared/markdown-it-emoji-fixed.js
@@ -0,0 +1,15 @@
+// fix emoji numbers
+const emojiData = require('markdown-it-emoji/lib/data/full.json');
+// Extend emoji here
+emojiData['zero'] = emojiData['0'] = '';
+emojiData['one'] = emojiData['1'] = '';
+emojiData['two'] = emojiData['2'] = '';
+emojiData['three'] = emojiData['3'] = '';
+emojiData['four'] = emojiData['4'] = '';
+emojiData['five'] = emojiData['5'] = '';
+emojiData['six'] = emojiData['6'] = '';
+emojiData['seven'] = emojiData['7'] = '';
+emojiData['eight'] = emojiData['8'] = '';
+emojiData['nine'] = emojiData['9'] = '';
+
+module.exports = emojiData;
\ No newline at end of file
diff --git a/src/lib/markbind/src/lib/markdown-it/markdown-it-icons.js b/src/lib/markbind/src/lib/markdown-it-shared/markdown-it-icons.js
similarity index 100%
rename from src/lib/markbind/src/lib/markdown-it/markdown-it-icons.js
rename to src/lib/markbind/src/lib/markdown-it-shared/markdown-it-icons.js
diff --git a/src/lib/markbind/src/lib/markdown-it/index.js b/src/lib/markbind/src/lib/markdown-it/index.js
index 15c8d17e4a..4d7c4d7cf4 100644
--- a/src/lib/markbind/src/lib/markdown-it/index.js
+++ b/src/lib/markbind/src/lib/markdown-it/index.js
@@ -20,16 +20,15 @@ const slugify = require('@sindresorhus/slugify');
// markdown-it plugins
markdownIt.use(require('markdown-it-mark'))
.use(require('markdown-it-ins'))
- .use(require('markdown-it-anchor'), {slugify: (str) => slugify(str, { decamelize: false })})
.use(require('markdown-it-imsize'), {autofill: false})
.use(require('markdown-it-table-of-contents'))
.use(require('markdown-it-task-lists'), {enabled: true})
.use(require('markdown-it-linkify-images'), {imgClass: 'img-fluid'})
.use(require('markdown-it-attrs'))
- .use(require('./markdown-it-dimmed'))
+ .use(require('../markdown-it-shared/markdown-it-dimmed'))
.use(require('./markdown-it-radio-button'))
.use(require('./markdown-it-block-embed'))
- .use(require('./markdown-it-icons'))
+ .use(require('../markdown-it-shared/markdown-it-icons'))
.use(require('./markdown-it-footnotes'));
// fix link
@@ -60,21 +59,9 @@ markdownIt.renderer.rules.code_inline = (tokens, idx, options, env, slf) => {
}
};
-// fix emoji numbers
-const emojiData = require('markdown-it-emoji/lib/data/full.json');
-// Extend emoji here
-emojiData['zero'] = emojiData['0'] = '';
-emojiData['one'] = emojiData['1'] = '';
-emojiData['two'] = emojiData['2'] = '';
-emojiData['three'] = emojiData['3'] = '';
-emojiData['four'] = emojiData['4'] = '';
-emojiData['five'] = emojiData['5'] = '';
-emojiData['six'] = emojiData['6'] = '';
-emojiData['seven'] = emojiData['7'] = '';
-emojiData['eight'] = emojiData['8'] = '';
-emojiData['nine'] = emojiData['9'] = '';
+const fixedNumberEmojiDefs = require('../markdown-it-shared/markdown-it-emoji-fixed');
markdownIt.use(require('markdown-it-emoji'), {
- defs: emojiData
+ defs: fixedNumberEmojiDefs
});
module.exports = markdownIt;
diff --git a/src/lib/markbind/src/lib/vue-attribute-renderer/index.js b/src/lib/markbind/src/lib/vue-attribute-renderer/index.js
new file mode 100644
index 0000000000..aa3f724955
--- /dev/null
+++ b/src/lib/markbind/src/lib/vue-attribute-renderer/index.js
@@ -0,0 +1,28 @@
+/*
+ * markdownIt instance using a different set of plugins for parsing and rendering
+ * markdown in vue components' attributes.
+ *
+ * Todo standardise this with the main markdown parser
+ */
+
+const markdownIt = require('markdown-it')({
+ html: true,
+ linkify: true,
+});
+
+markdownIt.use(require('markdown-it-mark'))
+ .use(require('markdown-it-ins'))
+ .use(require('markdown-it-sub'))
+ .use(require('markdown-it-sup'))
+ .use(require('../markdown-it-shared/markdown-it-dimmed'))
+ .use(require('../markdown-it-shared/markdown-it-icons'))
+ .use(require('markdown-it-imsize'), {
+ autofill: false,
+ });
+
+const fixedNumberEmojiDefs = require('../markdown-it-shared/markdown-it-emoji-fixed');
+markdownIt.use(require('markdown-it-emoji'), {
+ defs: fixedNumberEmojiDefs,
+});
+
+module.exports = markdownIt;
diff --git a/src/lib/markbind/src/parser.js b/src/lib/markbind/src/parser.js
index 179dbce9db..7a8fc3a11c 100644
--- a/src/lib/markbind/src/parser.js
+++ b/src/lib/markbind/src/parser.js
@@ -6,6 +6,8 @@ const path = require('path');
const Promise = require('bluebird');
const url = require('url');
const pathIsInside = require('path-is-inside');
+const slugify = require('@sindresorhus/slugify');
+const componentParser = require('./parsers/componentParser');
const _ = {};
_.clone = require('lodash/clone');
@@ -458,6 +460,23 @@ class Parser {
if (element.name) {
element.name = element.name.toLowerCase();
}
+
+ if ((/^h[1-6]$/).test(element.name) && !element.attribs.id) {
+ const textContent = utils.getTextContent(element);
+ const slugifiedHeading = slugify(textContent, { decamelize: false });
+
+ let headerId = slugifiedHeading;
+ const { headerIdMap } = config;
+ if (headerIdMap[slugifiedHeading]) {
+ headerId = `${slugifiedHeading}-${headerIdMap[slugifiedHeading]}`;
+ headerIdMap[slugifiedHeading] += 1;
+ } else {
+ headerIdMap[slugifiedHeading] = 2;
+ }
+
+ element.attribs.id = headerId;
+ }
+
switch (element.name) {
case 'md':
element.name = 'span';
@@ -492,11 +511,17 @@ class Parser {
default:
break;
}
+
+ componentParser.parseComponents(element, this._onError);
+
if (element.children) {
element.children.forEach((child) => {
self._parse(child, context, config);
});
}
+
+ componentParser.postParseComponents(element, this._onError);
+
return element;
}
diff --git a/src/lib/markbind/src/parsers/componentParser.js b/src/lib/markbind/src/parsers/componentParser.js
new file mode 100644
index 0000000000..3bbc235cc6
--- /dev/null
+++ b/src/lib/markbind/src/parsers/componentParser.js
@@ -0,0 +1,246 @@
+const cheerio = require('cheerio');
+
+const _ = {};
+_.has = require('lodash/has');
+
+const vueAttrRenderer = require('../lib/vue-attribute-renderer');
+
+cheerio.prototype.options.xmlMode = true; // Enable xml mode for self-closing tag
+cheerio.prototype.options.decodeEntities = false; // Don't escape HTML entities
+
+/*
+ * Private utility functions
+ */
+
+/**
+ * Parses the markdown attribute of the provided element, inserting the corresponding child
+ * if there is no pre-existing slot child with the name of the attribute present.
+ * @param element Element to parse
+ * @param attribute Attribute name to parse
+ * @param isInline Whether to parse the attribute with only inline markdown-it rules
+ * @param slotName Name attribute of the element to insert, which defaults to the attribute name
+ */
+function _parseAttributeWithoutOverride(element, attribute, isInline, slotName = attribute) {
+ const el = element;
+
+ const hasAttributeSlot = el.children
+ && el.children.some(child => _.has(child.attribs, 'slot') && child.attribs.slot === slotName);
+
+ if (!hasAttributeSlot && _.has(el.attribs, attribute)) {
+ let rendered;
+ if (isInline) {
+ rendered = vueAttrRenderer.renderInline(el.attribs[attribute]);
+ } else {
+ rendered = vueAttrRenderer.render(el.attribs[attribute]);
+ }
+
+ const attributeSlotElement = cheerio.parseHTML(
+ `${rendered}`, true);
+ el.children = el.children ? attributeSlotElement.concat(el.children) : attributeSlotElement;
+ }
+
+ delete el.attribs[attribute];
+}
+
+/*
+ * Panels
+ */
+
+function _parsePanelAttributes(element) {
+ const el = element;
+
+ _parseAttributeWithoutOverride(element, 'alt', false, '_alt');
+
+ const slotChildren = el.children && el.children.filter(child => _.has(child.attribs, 'slot'));
+ const hasAltSlot = slotChildren && slotChildren.some(child => child.attribs.slot === '_alt');
+ const hasHeaderSlot = slotChildren && slotChildren.some(child => child.attribs.slot === 'header');
+
+ // If both are present, the header attribute has no effect, and we can simply remove it.
+ if (hasAltSlot && hasHeaderSlot) {
+ delete el.attribs.header;
+ return;
+ }
+
+ _parseAttributeWithoutOverride(element, 'header', false, '_header');
+}
+
+/**
+ * Traverses the dom breadth-first from the specified element to find a html heading child.
+ * @param element Root element to search from
+ * @returns {undefined|*} The header element, or undefined if none is found.
+ */
+function _findHeaderElement(element) {
+ const elements = element.children;
+ if (!elements || !elements.length) {
+ return undefined;
+ }
+
+ const elementQueue = elements.slice(0);
+ while (elementQueue.length) {
+ const nextEl = elementQueue.shift();
+ if ((/^h[1-6]$/).test(nextEl.name)) {
+ return nextEl;
+ }
+
+ if (nextEl.children) {
+ nextEl.children.forEach(child => elementQueue.push(child));
+ }
+ }
+
+ return undefined;
+}
+
+/**
+ * Assigns an id to the root element of a panel component using the heading specified in the
+ * panel's header slot or attribute (if any), with the header slot having priority if present.
+ * This is to ensure anchors still work when panels are in their minimized form.
+ * @param element The root panel element
+ */
+function _assignPanelId(element) {
+ const el = element;
+
+ const slotChildren = el.children && el.children.filter(child => _.has(child.attribs, 'slot'));
+ const headerSlot = slotChildren.find(child => child.attribs.slot === 'header');
+ const headerAttributeSlot = slotChildren.find(child => child.attribs.slot === '_header');
+
+ const slotElement = headerSlot || headerAttributeSlot;
+ if (slotElement) {
+ const header = _findHeaderElement(slotElement);
+ if (!header) {
+ return;
+ }
+
+ if (!header.attribs || !_.has(header.attribs, 'id')) {
+ throw new Error('Found a panel heading without an assigned id.\n'
+ + 'Please report this to the MarkBind developers. Thank you!');
+ }
+
+ el.attribs.id = header.attribs.id;
+ }
+}
+
+
+/*
+ * Popovers
+ */
+
+function _parsePopoverAttributes(element) {
+ _parseAttributeWithoutOverride(element, 'content', true);
+ _parseAttributeWithoutOverride(element, 'header', true);
+ // TODO deprecate title attribute for popovers
+ _parseAttributeWithoutOverride(element, 'title', true, 'header');
+}
+
+/*
+ * Tooltips
+ */
+
+function _parseTooltipAttributes(element) {
+ _parseAttributeWithoutOverride(element, 'content', true, '_content');
+}
+
+/*
+ * Modals
+ */
+
+function _renameSlot(element, originalName, newName) {
+ if (element.children) {
+ element.children.forEach((c) => {
+ const child = c;
+
+ if (_.has(child.attribs, 'slot') && child.attribs.slot === originalName) {
+ child.attribs.slot = newName;
+ }
+ });
+ }
+}
+
+function _parseModalAttributes(element) {
+ _parseAttributeWithoutOverride(element, 'header', true, '_header');
+ // TODO deprecate title attribute for modals
+ _parseAttributeWithoutOverride(element, 'title', true, '_header');
+
+ // TODO deprecate modal-header, modal-footer attributes for modals
+ _renameSlot(element, 'modal-header', 'header');
+ _renameSlot(element, 'modal-footer', 'footer');
+}
+
+/*
+ * Tabs
+ */
+
+function _parseTabAttributes(element) {
+ _parseAttributeWithoutOverride(element, 'header', true, '_header');
+}
+
+/*
+ * Tip boxes
+ */
+
+function _parseBoxAttributes(element) {
+ _parseAttributeWithoutOverride(element, 'icon', true, '_icon');
+}
+
+/*
+ * API
+ */
+
+function parseComponents(element, errorHandler) {
+ try {
+ switch (element.name) {
+ case 'panel':
+ _parsePanelAttributes(element);
+ break;
+ case 'popover':
+ _parsePopoverAttributes(element);
+ break;
+ case 'tooltip':
+ _parseTooltipAttributes(element);
+ break;
+ case 'modal':
+ _parseModalAttributes(element);
+ break;
+ case 'tab':
+ case 'tab-group':
+ _parseTabAttributes(element);
+ break;
+ case 'box':
+ _parseBoxAttributes(element);
+ break;
+ default:
+ break;
+ }
+ } catch (error) {
+ if (!errorHandler) {
+ // eslint-disable-next-line no-console
+ console.error(error);
+ return;
+ }
+ errorHandler(error);
+ }
+}
+
+function postParseComponents(element, errorHandler) {
+ try {
+ switch (element.name) {
+ case 'panel':
+ _assignPanelId(element);
+ break;
+ default:
+ break;
+ }
+ } catch (error) {
+ if (!errorHandler) {
+ // eslint-disable-next-line no-console
+ console.error(error);
+ return;
+ }
+ errorHandler(error);
+ }
+}
+
+
+module.exports = {
+ parseComponents,
+ postParseComponents,
+};
diff --git a/src/lib/markbind/src/utils.js b/src/lib/markbind/src/utils.js
index c2098adfca..4e6eee399f 100644
--- a/src/lib/markbind/src/utils.js
+++ b/src/lib/markbind/src/utils.js
@@ -72,4 +72,32 @@ module.exports = {
createErrorElement(error) {
return `
${error.message}
`;
},
+
+ /**
+ * Traverses the dom depth-first from the specified element to concatenate
+ * all text of the specified element.
+ * @param element Root element to search from
+ * @returns string The concatenated text, or undefined if it is an empty string.
+ */
+ getTextContent(element) {
+ const elements = element.children;
+ if (!elements || !elements.length) {
+ return undefined;
+ }
+
+ const elementStack = elements.slice();
+ const text = [];
+ while (elementStack.length) {
+ const nextEl = elementStack.shift();
+ if (nextEl.type === 'text') {
+ text.push(nextEl.data);
+ }
+
+ if (nextEl.children && nextEl.type !== 'comment') {
+ elementStack.unshift(...nextEl.children);
+ }
+ }
+
+ return text.join('').trim();
+ },
};
diff --git a/src/plugins/default/markbind-plugin-anchors.js b/src/plugins/default/markbind-plugin-anchors.js
index 1499038335..11b5bf1353 100644
--- a/src/plugins/default/markbind-plugin-anchors.js
+++ b/src/plugins/default/markbind-plugin-anchors.js
@@ -1,5 +1,4 @@
const cheerio = module.parent.require('cheerio');
-const md = require('./../../lib/markbind/src/lib/markdown-it');
const {
ANCHOR_HTML,
@@ -17,14 +16,6 @@ module.exports = {
$(heading).append(ANCHOR_HTML.replace('#', `#${$(heading).attr('id')}`));
}
});
- $('panel[header]').each((i, panel) => {
- const panelHeading = cheerio.load(md.render(panel.attribs.header), { xmlMode: false });
- if (panelHeading(HEADER_TAGS).length >= 1) {
- const headingId = $(panelHeading(HEADER_TAGS)[0]).attr('id');
- const anchorIcon = ANCHOR_HTML.replace(/"/g, "'").replace('#', `#${headingId}`);
- $(panel).attr('header', `${$(panel).attr('header')}${anchorIcon}`);
- }
- });
return $.html();
},
};
diff --git a/src/template/default/_markbind/headers/header.md b/src/template/default/_markbind/headers/header.md
index 2ef08b7a74..cadbbb5a5c 100644
--- a/src/template/default/_markbind/headers/header.md
+++ b/src/template/default/_markbind/headers/header.md
@@ -3,7 +3,7 @@
Your Logo
@@ -13,4 +13,4 @@
-
\ No newline at end of file
+
diff --git a/src/template/default/index.md b/src/template/default/index.md
index 231d945f88..8668ea84fb 100755
--- a/src/template/default/index.md
+++ b/src/template/default/index.md
@@ -9,7 +9,7 @@
-
Landing Page Title
+
Landing Page Title
A tagline can go here
@@ -41,7 +41,7 @@ Some text some text some text some text some text some text some text. **Some te
A tooltip, a modal, a link, a badge, another badge.
-
+
Some text some text some text some text some text some text some text. Some text some text some text some text some text some text some text. Some text some text some text some text some text some text some text some text some text some text some text some text some text some text. Some text some text some text some text some text some text. Some text some text some text some text some text some text some text.
diff --git a/test/functional/test_site/bugs/modal.md b/test/functional/test_site/bugs/modal.md
index 99e7557c03..6a09255b1c 100644
--- a/test/functional/test_site/bugs/modal.md
+++ b/test/functional/test_site/bugs/modal.md
@@ -1,5 +1,5 @@
This is to reproduce multiple inclusions of a modal bug
-
+
diff --git a/test/functional/test_site/expected/bugs/index.html b/test/functional/test_site/expected/bugs/index.html
index 2bcde6c1cc..ef7877e2e0 100644
--- a/test/functional/test_site/expected/bugs/index.html
+++ b/test/functional/test_site/expected/bugs/index.html
@@ -58,7 +58,7 @@
This is to reproduce
multiple inclusions of a modal bug
-
+ 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.
@@ -70,7 +70,7 @@
This is to reproduce
multiple inclusions of a modal bug
-
+ 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.
diff --git a/test/functional/test_site/expected/index.html b/test/functional/test_site/expected/index.html
index bf9ac77b7d..9ad3ba5a13 100644
--- a/test/functional/test_site/expected/index.html
+++ b/test/functional/test_site/expected/index.html
@@ -200,18 +200,18 @@
Focus groups are a kind of informal interview within an interactive group setting. A
- group of people
+ e.g. potential users, beta testersgroup 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.
Panel content of unexpanded panel should not appear in search data
-
+
Panel header inside unexpanded panel should not appear in search data
Panel content inside unexpanded panel should not appear in search data
@@ -568,6 +568,7 @@
Outer nested panel without src
Inner panel header without srcPanel with src from another Markbind site header
+ Panel with src from another Markbind site headerUnexpanded panel headerMarkbind Plugin Pre-render
diff --git a/test/functional/test_site/expected/markbind/css/markbind.css b/test/functional/test_site/expected/markbind/css/markbind.css
index 92328d46d5..e8bfdd009f 100644
--- a/test/functional/test_site/expected/markbind/css/markbind.css
+++ b/test/functional/test_site/expected/markbind/css/markbind.css
@@ -136,6 +136,11 @@ footer {
top: 0;
}
+/* TODO move this back to markdown-it-attr if bundling is implemented */
+.dimmed {
+ color: #777;
+}
+
/* Bootstrap small(sm) responsive breakpoint */
@media (max-width: 767.98px) {
.dropdown-menu > li > a {
diff --git a/test/functional/test_site/expected/requirements/notInside._include_.html b/test/functional/test_site/expected/requirements/notInside._include_.html
index 6c66d584a0..3f9e278036 100644
--- a/test/functional/test_site/expected/requirements/notInside._include_.html
+++ b/test/functional/test_site/expected/requirements/notInside._include_.html
@@ -3,4 +3,4 @@
Path within
Like static include, pages within the site should be able to use files located in folders within boilerplate.
Also, the boilerplate file name (e.g. inside.md) and the file that it is supposed to act as (notInside.md) can be different.
This file should behaves as if it is in the requirements folder:
-
\ No newline at end of file
+
Tested with the folllowing include
\ No newline at end of file
diff --git a/test/functional/test_site/expected/siteData.json b/test/functional/test_site/expected/siteData.json
index e7d31b4113..83b3ff88d2 100644
--- a/test/functional/test_site/expected/siteData.json
+++ b/test/functional/test_site/expected/siteData.json
@@ -23,6 +23,7 @@
"outer-nested-panel": "Outer nested panel",
"outer-nested-panel-without-src": "Outer nested panel without src",
"panel-with-src-from-another-markbind-site-header": "Panel with src from another Markbind site header",
+ "panel-with-src-from-another-markbind-site-header-2": "Panel with src from another Markbind site header",
"unexpanded-panel-header": "Unexpanded panel header",
"keyword-should-be-tagged-to-this-heading-not-the-panel-heading": "Keyword should be tagged to this heading, not the panel heading | panel keyword",
"panel-normal-source-content-headings": "Panel normal source content headings",
@@ -140,24 +141,26 @@
},
{
"headings": {
- "should-have-anchor": "should have anchor",
"should-have-anchor-7": "should have anchor",
+ "should-have-anchor-20": "should have anchor",
"should-have-anchor-8": "should have anchor",
"should-have-anchor-9": "should have anchor",
"should-have-anchor-10": "should have anchor",
- "should-have-anchor-19": "should have anchor",
- "should-have-anchor-20": "should have anchor",
+ "should-have-anchor-11": "should have anchor",
"should-have-anchor-21": "should have anchor",
"should-have-anchor-22": "should have anchor",
+ "should-have-anchor-23": "should have anchor",
+ "should-have-anchor-24": "should have anchor",
"root-file": "Root file",
+ "should-have-anchor": "should have anchor",
"should-have-anchor-2": "should have anchor",
"should-have-anchor-3": "should have anchor",
"should-have-anchor-4": "should have anchor",
"included-file": "Included File",
- "should-have-anchor-13": "should have anchor",
"should-have-anchor-14": "should have anchor",
"should-have-anchor-15": "should have anchor",
- "should-have-anchor-16": "should have anchor"
+ "should-have-anchor-16": "should have anchor",
+ "should-have-anchor-17": "should have anchor"
},
"title": "Anchor Generation Test",
"src": "testAnchorGeneration.md",
diff --git a/test/functional/test_site/expected/testAnchorGeneration.html b/test/functional/test_site/expected/testAnchorGeneration.html
index bf2b31a70d..2853fd0f6b 100644
--- a/test/functional/test_site/expected/testAnchorGeneration.html
+++ b/test/functional/test_site/expected/testAnchorGeneration.html
@@ -33,10 +33,9 @@
diff --git a/test/functional/test_site/expected/testPanels/NestedPanel._include_.html b/test/functional/test_site/expected/testPanels/NestedPanel._include_.html
index c4c0445879..a4b5b4139a 100644
--- a/test/functional/test_site/expected/testPanels/NestedPanel._include_.html
+++ b/test/functional/test_site/expected/testPanels/NestedPanel._include_.html
@@ -1 +1 @@
-
\ No newline at end of file
+
Nested Panel
\ No newline at end of file
diff --git a/test/functional/test_site/index.md b/test/functional/test_site/index.md
index 0910e72a61..8821f50a97 100644
--- a/test/functional/test_site/index.md
+++ b/test/functional/test_site/index.md
@@ -247,7 +247,7 @@ tags: ["tag-frontmatter-shown", "tag-included-file", "+tag-exp*", "-tag-exp-hidd
trigger
-
+
**Panel content inside modal**
diff --git a/test/functional/test_site/testAntiFOUCStyles.md b/test/functional/test_site/testAntiFOUCStyles.md
index 5502705a9b..39f529ccfe 100644
--- a/test/functional/test_site/testAntiFOUCStyles.md
+++ b/test/functional/test_site/testAntiFOUCStyles.md
@@ -3,7 +3,7 @@
**Modal content should have algolia-no-index class**
-
+
Content should have `algolia-no-index` class
Trigger should not have `algolia-no-index` class
@@ -32,12 +32,12 @@
**Popover content should have algolia-no-index class**
-
+
A
- tooltip, a
+ ❗️ some important explanationtooltip, a
modal, a link, a badge, another badge.
-
- Some text some text some text some text some text some text some text. Some text some text some text some text some text some text some text. Some text some text some text some text some text some text some text some text some text some text some text
- some text some text some text. Some text some text some text some text some text some text. Some text some text some text some text some text some text some text.
+ Modal Title Some text some text some text some text some text some text some text. Some text some text some text some text some text some text some text. Some text some text some text some text some text some
+ text some text some text some text some text some text some text some text some text. Some text some text some text some text some text some text. Some text some text some text some text some text some text some text.
A table:
@@ -132,19 +131,15 @@
Sub Heading 1.2
Tabs:
-
- Some text some text some text some text some text some text some text. Some text some text some text some text some text some text some text. Some text some text some text some text some text some text some text some text some text some text some text
- some text some text some text. Some text some text some text some text some text some text. Some text some text some text some text some text some text some text.
+ Tab X Some text some text some text some text some text some text some text. Some text some text some text some text some text some text some text. Some text some text some text some text some text some text
+ some text some text some text some text some text some text some text some text. Some text some text some text some text some text some text. Some text some text some text some text some text some text some text.
-
- ...
+ Tab Y ...
-
-
- ...
+ Tab group
+ Tab Y.1 ...
-
- ...
+ Tab Y.2 ...
@@ -174,22 +169,19 @@
Heading 2
Heading 3
-
- Some text some text some text some text some text some text some text. Some text some text some text some text some text some text some text. Some text some text some text some text some text some text some text some text some text some text some text
- some text some text some text. Some text some text some text some text some text some text. Some text some text some text some text some text some text some text.
+
Expandable panel
Some text some text some text some text some text some text some text. Some text some text some text some text some text some text some text. Some text some text some text some text some
+ text some text some text some text some text some text some text some text some text some text. Some text some text some text some text some text some text. Some text some text some text some text some text some text some text.
-
- ...
+
Expanded panel
Minimized panel
...
-
- ...
+
Expanded panel
Minimized panel
...
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
-
- Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
+
Minimal panel ->
Minimal panel
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
+ ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
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 92328d46d5..e8bfdd009f 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
@@ -136,6 +136,11 @@ footer {
top: 0;
}
+/* TODO move this back to markdown-it-attr if bundling is implemented */
+.dimmed {
+ color: #777;
+}
+
/* Bootstrap small(sm) responsive breakpoint */
@media (max-width: 767.98px) {
.dropdown-menu > li > a {
diff --git a/test/functional/test_site_templates/test_default/expected/siteData.json b/test/functional/test_site_templates/test_default/expected/siteData.json
index c8dbf41472..ad3e3ff7b6 100644
--- a/test/functional/test_site_templates/test_default/expected/siteData.json
+++ b/test/functional/test_site_templates/test_default/expected/siteData.json
@@ -3,7 +3,6 @@
"pages": [
{
"headings": {
- "undefined": "Landing Page Title",
"heading-1": "Heading 1",
"sub-heading-1-1": "Sub Heading 1.1",
"sub-heading-1-2": "Sub Heading 1.2",
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 92328d46d5..e8bfdd009f 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
@@ -136,6 +136,11 @@ footer {
top: 0;
}
+/* TODO move this back to markdown-it-attr if bundling is implemented */
+.dimmed {
+ color: #777;
+}
+
/* Bootstrap small(sm) responsive breakpoint */
@media (max-width: 767.98px) {
.dropdown-menu > li > a {
diff --git a/test/unit/markdown-it-icons.test.js b/test/unit/markdown-it-icons.test.js
index 26197c6b48..d8013636c1 100644
--- a/test/unit/markdown-it-icons.test.js
+++ b/test/unit/markdown-it-icons.test.js
@@ -1,5 +1,5 @@
const markdownIt = require('markdown-it')()
- .use(require('../../src/lib/markbind/src/lib/markdown-it/markdown-it-icons'));
+ .use(require('../../src/lib/markbind/src/lib/markdown-it-shared/markdown-it-icons'));
test('markdown-it-icons renders icon syntax correctly', () => {
const source = ':fab-font-awesome: :glyphicon-home:';
diff --git a/test/unit/parser.test.js b/test/unit/parser.test.js
index 44b3a9b53a..98cb71a848 100644
--- a/test/unit/parser.test.js
+++ b/test/unit/parser.test.js
@@ -513,6 +513,7 @@ test('includeFile replaces with ', async () => {
test('renderFile converts markdown headers to
', async () => {
const result = await markbinder.renderFile(indexPath, {
baseUrlMap,
rootPath,
+ headerIdMap,
});
const expected = [
diff --git a/test/unit/parsers/componentParser.test.js b/test/unit/parsers/componentParser.test.js
new file mode 100644
index 0000000000..8ba8473357
--- /dev/null
+++ b/test/unit/parsers/componentParser.test.js
@@ -0,0 +1,95 @@
+const cheerio = require('cheerio');
+const htmlparser = require('htmlparser2');
+const componentParser = require('../../../src/lib/markbind/src/parsers/componentParser');
+const testData = require('../utils/componentParserData');
+
+/**
+ * Runs the parseComponent or postParseComponent method of componentParser on the provided
+ * template, verifying it with the expected result.
+ * @param template The html template, which should only have one root element
+ * @param expectedTemplate The expected result template
+ * @param postParse Boolean of whether to run postParseComponent instead of parseComponent.
+ * Defaults to false
+ */
+const parseAndVerifyTemplate = (template, expectedTemplate, postParse = false) => {
+ const handler = new htmlparser.DomHandler((error, dom) => {
+ expect(error).toBeFalsy();
+
+ if (postParse) {
+ dom.forEach(node => componentParser.postParseComponents(node));
+ } else {
+ dom.forEach(node => componentParser.parseComponents(node));
+ }
+ const result = cheerio.html(dom);
+
+ expect(result).toEqual(expectedTemplate);
+ });
+
+ const htmlParser = new htmlparser.Parser(handler, {
+ xmlMode: true,
+ decodeEntities: false,
+ });
+
+ htmlParser.parseComplete(template);
+};
+
+test('parseComponent parses panel attributes and inserts into dom as slots correctly', () => {
+ parseAndVerifyTemplate(testData.PARSE_PANEL_ATTRIBUTES,
+ testData.PARSE_PANEL_ATTRIBUTES_EXPECTED);
+ parseAndVerifyTemplate(testData.PARSE_PANEL_HEADER_NO_OVERRIDE,
+ testData.PARSE_PANEL_HEADER_NO_OVERRIDE_EXPECTED);
+});
+
+test('parseComponent parses popover attributes and inserts into dom as slots correctly', () => {
+ parseAndVerifyTemplate(testData.PARSE_POPOVER_ATTRIBUTES,
+ testData.PARSE_POPOVER_ATTRIBUTES_EXPECTED);
+ parseAndVerifyTemplate(testData.PARSE_POPOVER_ATTRIBUTES_NO_OVERRIDE,
+ testData.PARSE_POPOVER_ATTRIBUTES_NO_OVERRIDE_EXPECTED);
+
+ // todo remove these once 'title' for popover is fully deprecated
+ parseAndVerifyTemplate(testData.PARSE_POPOVER_TITLE,
+ testData.PARSE_POPOVER_TITLE_EXPECTED);
+ parseAndVerifyTemplate(testData.PARSE_POPOVER_TITLE_NO_OVERRIDE,
+ testData.PARSE_POPOVER_TITLE_NO_OVERRIDE_EXPECTED);
+});
+
+test('parseComponent parses tooltip attributes and inserts into dom as slots correctly', () => {
+ parseAndVerifyTemplate(testData.PARSE_TOOLTIP_CONTENT,
+ testData.PARSE_TOOLTIP_CONTENT_EXPECTED);
+});
+
+test('parseComponent parses modal attributes and inserts into dom as slots correctly', () => {
+ parseAndVerifyTemplate(testData.PARSE_MODAL_HEADER,
+ testData.PARSE_MODAL_HEADER_EXPECTED);
+
+ // todo remove these once 'title' for modals is fully deprecated
+ parseAndVerifyTemplate(testData.PARSE_MODAL_TITLE,
+ testData.PARSE_MODAL_TITLE_EXPECTED);
+ parseAndVerifyTemplate(testData.PARSE_MODAL_TITLE_NO_OVERRIDE,
+ testData.PARSE_MODAL_TITLE_NO_OVERRIDE_EXPECTED);
+
+ // todo remove these once 'modal-header' / 'modal-footer' for popover is fully deprecated
+ parseAndVerifyTemplate(testData.PARSE_MODAL_SLOTS_RENAMING,
+ testData.PARSE_MODAL_SLOTS_RENAMING_EXPECTED);
+});
+
+test('parseComponent parses tab & tab-group attributes and inserts into dom as slots correctly', () => {
+ parseAndVerifyTemplate(testData.PARSE_TAB_HEADER,
+ testData.PARSE_TAB_HEADER_EXPECTED);
+ parseAndVerifyTemplate(testData.PARSE_TAB_GROUP_HEADER,
+ testData.PARSE_TAB_GROUP_HEADER_EXPECTED);
+});
+
+test('parseComponent parses box attributes and inserts into dom as slots correctly', () => {
+ parseAndVerifyTemplate(testData.PARSE_BOX_ICON,
+ testData.PARSE_BOX_ICON_EXPECTED);
+});
+
+test('postParseComponent assigns the correct header id to panels', () => {
+ parseAndVerifyTemplate(testData.POST_PARSE_PANEL_ID_ASSIGNED_USING_HEADER_ATTRIBUTE,
+ testData.POST_PARSE_PANEL_ID_ASSIGNED_USING_HEADER_ATTRIBUTE_EXPECTED,
+ true);
+ parseAndVerifyTemplate(testData.POST_PARSE_PANEL_ID_ASSIGNED_USING_HEADER_SLOT,
+ testData.POST_PARSE_PANEL_ID_ASSIGNED_USING_HEADER_SLOT_EXPECTED,
+ true);
+});
diff --git a/test/unit/utils/componentParserData.js b/test/unit/utils/componentParserData.js
new file mode 100644
index 0000000000..b17eb7912f
--- /dev/null
+++ b/test/unit/utils/componentParserData.js
@@ -0,0 +1,254 @@
+/* eslint-disable max-len */
+
+/*
+ * Panel
+ */
+
+module.exports.PARSE_PANEL_ATTRIBUTES = `
+
+ Header and alt attributes should be parsed and inserted under panel as internal slots and deleted.
+
+`;
+
+module.exports.PARSE_PANEL_ATTRIBUTES_EXPECTED = `
+
Lorem ipsum
+
emphasized alt
+
+ Header and alt attributes should be parsed and inserted under panel as internal slots and deleted.
+
+`;
+
+module.exports.PARSE_PANEL_HEADER_NO_OVERRIDE = `
+
+
+ This existing header slot should be preserved in favour over header attribute.
+
+ Header attribute should not be inserted under panel since there is both an alt attribute and header slot,
+ but should be deleted.
+ Alt attribute should be inserted under panel as slot.
+
+`;
+
+module.exports.PARSE_PANEL_HEADER_NO_OVERRIDE_EXPECTED = `
+
strong alt
+
+
+ This existing header slot should be preserved in favour over header attribute.
+
+ Header attribute should not be inserted under panel since there is both an alt attribute and header slot,
+ but should be deleted.
+ Alt attribute should be inserted under panel as slot.
+
+`;
+
+// Post Parse
+module.exports.POST_PARSE_PANEL_ID_ASSIGNED_USING_HEADER_ATTRIBUTE = `
+
Lorem ipsum
+
+ Header and alt attributes should be parsed and inserted under panel as internal slots and deleted.
+
+`;
+
+module.exports.POST_PARSE_PANEL_ID_ASSIGNED_USING_HEADER_ATTRIBUTE_EXPECTED = `
+
Lorem ipsum
+
+ Header and alt attributes should be parsed and inserted under panel as internal slots and deleted.
+
+`;
+
+module.exports.POST_PARSE_PANEL_ID_ASSIGNED_USING_HEADER_SLOT = `
+
Attribute Header
+
+
Slot Header
+ Header and alt attributes should be parsed and inserted under panel as internal slots and deleted.
+
+`;
+
+module.exports.POST_PARSE_PANEL_ID_ASSIGNED_USING_HEADER_SLOT_EXPECTED = `
+
Attribute Header
+
+
Slot Header
+ Header and alt attributes should be parsed and inserted under panel as internal slots and deleted.
+
+`;
+
+/*
+ * Popovers
+ */
+
+module.exports.PARSE_POPOVER_ATTRIBUTES = `
+
+ Content and header attributes should be parsed and inserted under panel as slots and deleted.
+
+`;
+
+module.exports.PARSE_POPOVER_ATTRIBUTES_EXPECTED = `
+Lorem ipsumLorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vel tellus elit.
+ Content and header attributes should be parsed and inserted under panel as slots and deleted.
+
+`;
+
+module.exports.PARSE_POPOVER_ATTRIBUTES_NO_OVERRIDE = `
+
+
Some header slot content that should not be overwritten
+
Some content slot that should not be overwritten
+ Content and header attributes should not be inserted under panel as slots, but should be deleted.
+
+`;
+
+module.exports.PARSE_POPOVER_ATTRIBUTES_NO_OVERRIDE_EXPECTED = `
+
+
Some header slot content that should not be overwritten
+
Some content slot that should not be overwritten
+ Content and header attributes should not be inserted under panel as slots, but should be deleted.
+
+`;
+
+// todo remove tests for these once 'title' attribute is fully deprecated for popovers
+
+module.exports.PARSE_POPOVER_TITLE = `
+
+ Title attribute should be parsed and inserted under panel as header slot and deleted.
+
+`;
+
+module.exports.PARSE_POPOVER_TITLE_EXPECTED = `
+Lorem ipsum
+ Title attribute should be parsed and inserted under panel as header slot and deleted.
+
+`;
+
+module.exports.PARSE_POPOVER_TITLE_NO_OVERRIDE = `
+
+ Title attribute should not be inserted as slot as header attribute is present, and should be deleted.
+
+`;
+
+module.exports.PARSE_POPOVER_TITLE_NO_OVERRIDE_EXPECTED = `
+Header header
+ Title attribute should not be inserted as slot as header attribute is present, and should be deleted.
+
+`;
+
+/*
+ * Tooltips
+ */
+
+module.exports.PARSE_TOOLTIP_CONTENT = `
+
+
+
+`;
+
+module.exports.PARSE_TOOLTIP_CONTENT_EXPECTED = `
+Lorem ipsum dolor sit amet
+
+
+`;
+
+/*
+ * Modals
+ */
+
+module.exports.PARSE_MODAL_HEADER = `
+
+ Header attribute should be inserted as internal _header slot.
+
+`;
+
+module.exports.PARSE_MODAL_HEADER_EXPECTED = `
+Lorem ipsum dolor sit amet
+ Header attribute should be inserted as internal _header slot.
+
+`;
+
+// todo remove tests for these once 'title' attribute is fully deprecated for modals
+
+module.exports.PARSE_MODAL_TITLE = `
+
+ Title attribute should be inserted as internal _header slot.
+
+`;
+
+module.exports.PARSE_MODAL_TITLE_EXPECTED = `
+Lorem ipsum dolor sit amet
+ Title attribute should be inserted as internal _header slot.
+
+`;
+
+module.exports.PARSE_MODAL_TITLE_NO_OVERRIDE = `
+
+ Title attribute should not have priority over newer header attribute, and should be deleted.
+
+`;
+
+module.exports.PARSE_MODAL_TITLE_NO_OVERRIDE_EXPECTED = `
+Header header
+ Title attribute should not have priority over newer header attribute, and should be deleted.
+
+`;
+
+// todo remove these once modal-header modal-footer slot names are deprecated fully.
+
+module.exports.PARSE_MODAL_SLOTS_RENAMING = `
+
+