diff --git a/asset/css/markbind.css b/asset/css/markbind.css
index d38eb2b900..a63f364f3c 100644
--- a/asset/css/markbind.css
+++ b/asset/css/markbind.css
@@ -25,6 +25,36 @@ pre > code.hljs {
counter-reset: line;
}
+pre > code.hljs[heading] {
+ border-top-right-radius: 0;
+}
+
+.code-block {
+ position: relative;
+}
+
+.code-block-heading {
+ background-color: #f2f2ff;
+ border-radius: 6px 6px 0 0;
+ color: #8787a5;
+ float: right;
+ font-size: 85%;
+ line-height: 1;
+ max-width: 85%;
+ overflow-wrap: break-word;
+ padding: 0.25em 0.4em;
+ text-align: right;
+}
+
+.code-block-content {
+ clear: both;
+ display: block;
+}
+
+code > span.highlighted {
+ background: lavender;
+}
+
kbd {
background-color: #fafbfc;
border: 1px solid #c6cbd1;
diff --git a/docs/userGuide/syntax/code.mbdf b/docs/userGuide/syntax/code.mbdf
index d9b0c45701..e4aaef01f2 100644
--- a/docs/userGuide/syntax/code.mbdf
+++ b/docs/userGuide/syntax/code.mbdf
@@ -1,11 +1,24 @@
## Code
-MarkBind can provide syntax coloring and line numbers for a code block (aka _Fenced Code Blocks_).
+#### Fenced Code
+MarkBind provides several features, some of which are added on top of the existing functionality of Markdown's _fenced code blocks_.
+
+More info: https://www.markdownguide.org/extended-syntax#fenced-code-blocks
+
+
+Features:
+- Syntax coloring
+- Line numbering
+- Line highlighting
+- Code block headers
+
+##### Syntax coloring
+To enable syntax coloring, specify a language next to the backticks before the fenced code block.
-````
+```` {.no-line-numbers}
```xml
goo
@@ -24,12 +37,13 @@ MarkBind can provide syntax coloring and line numbers for a code block (aka _Fen
-As seen above, line numbers are automatically provided for a code block. To hide line numbers, add the class `no-line-numbers ` to the code block like below:
+##### Line numbering
+Line numbers are automatically provided by default. To hide line numbers, add the class `no-line-numbers ` to the code block as below
-````
+```` {.no-line-numbers}
```xml {.no-line-numbers}
goo
@@ -47,9 +61,75 @@ As seen above, line numbers are automatically provided for a code block. To hide
-More info: https://www.markdownguide.org/extended-syntax#fenced-code-blocks
+##### Line highlighting
+To highlight lines, add the attribute `highlight-lines` with the line numbers as value, as shown below. You can specify ranges or individual line numbers.
+
+
+
+
+```` {.no-line-numbers}
+```java {highlight-lines="2,4,6-8"}
+import java.util.List;
+
+public class Inventory {
+ private List- items;
+
+ public int getItemCount(){
+ return items.size();
+ }
+
+ //...
+}
+```
+````
+
+
+
+```java {highlight-lines="2,4,6-8"}
+import java.util.List;
+
+public class Inventory {
+ private List- items;
+
+ public int getItemCount(){
+ return items.size();
+ }
+
+ //...
+}
+```
+
+
+
+##### Heading
+To add a heading, add the attribute `heading` with the heading text as the value, as shown below.
+
+
+
+
+```` {.no-line-numbers}
+```xml {heading="Heading title"}
+
+ goo
+
+```
+````
+
+
+
+```xml {heading="Heading title"}
+
+ goo
+
+```
+
+
+
+#### Inline Code
+##### Syntax coloring
+
-In addition, MarkBind can apply syntax-coloring on inline code too.
+MarkBind can apply syntax-coloring on inline code too.
diff --git a/src/lib/markbind/src/lib/markdown-it/index.js b/src/lib/markbind/src/lib/markdown-it/index.js
index 55ae6a443f..d89a5e31cb 100644
--- a/src/lib/markbind/src/lib/markdown-it/index.js
+++ b/src/lib/markbind/src/lib/markdown-it/index.js
@@ -64,18 +64,57 @@ markdownIt.renderer.rules.fence = (tokens, idx, options, env, slf) => {
if (!highlighted) {
lines = markdownIt.utils.escapeHtml(str).split('\n');
}
+
+ const highlightLinesInput = token.attrGet('highlight-lines');
+ let lineNumbersAndRanges = [];
+ if (highlightLinesInput) {
+ // example input format: "1,4-7,8,11-55"
+ // output: [[1],[4,7],[8],[11,55]]
+ // the output is an array contaning either single line numbers [lineNum] or ranges [start, end]
+ // ',' delimits either single line numbers (eg: 1) or ranges (eg: 4-7)
+ highlightLines = highlightLinesInput.split(',');
+ // if it's the single number, it will just be parsed as an int, (eg: ['1'] --> [1] )
+ // if it's a range, it will be parsed as as an array of two ints (eg: ['4-7'] --> [4,6])
+ lineNumbersAndRanges = highlightLines.map(elem => elem.split('-').map(lineNumber => parseInt(lineNumber, 10)));
+ }
+
lines.pop(); // last line is always a single '\n' newline, so we remove it
-
- /* wrap all lines with so we can number them
- if a line is empty we put a 0 width non breaking space
- */
- str = lines.map(line => `${line || ''}`).join('');
+ // wrap all lines with so we can number them
+ str = lines.map((line, index) => {
+ // if a line is empty we put a 0 width non breaking space
+ const content = line || '';
+ const currentLineNumber = index + 1;
+ // check if there is at least one range or line number that matches the current line number
+ // Note: The algorithm is based off markdown-it-highlight-lines (https://github.com/egoist/markdown-it-highlight-lines/blob/master/src/index.js)
+ // This is an O(n^2) solution wrt to the number of lines
+ // I opt to use this approach because it's simple, and it is unlikely that the number of elements in `lineNumbersAndRanges` will be large
+ // There is possible room for improvement for a more efficient algo that is O(n).
+ const inRange = lineNumbersAndRanges.some(([start, end]) => {
+ if (start && end) {
+ return currentLineNumber >= start && currentLineNumber <= end;
+ }
+ return currentLineNumber === start;
+ });
+ if (inRange) {
+ return `${content}`;
+ }
+ return `${content}`;
+ }).join('');
token.attrJoin('class', 'hljs');
if (highlighted) {
token.attrJoin('class', lang);
}
- return `${str}
`;
+
+ const heading = token.attrGet('heading');
+ const codeBlockContent = `${str}
`;
+ if (heading) {
+ return ''
+ + `
${heading}
`
+ + `
${codeBlockContent}
`
+ + '
';
+ }
+ return codeBlockContent;
};
// highlight inline code
diff --git a/test/functional/test_site/expected/markbind/css/markbind.css b/test/functional/test_site/expected/markbind/css/markbind.css
index d38eb2b900..a63f364f3c 100644
--- a/test/functional/test_site/expected/markbind/css/markbind.css
+++ b/test/functional/test_site/expected/markbind/css/markbind.css
@@ -25,6 +25,36 @@ pre > code.hljs {
counter-reset: line;
}
+pre > code.hljs[heading] {
+ border-top-right-radius: 0;
+}
+
+.code-block {
+ position: relative;
+}
+
+.code-block-heading {
+ background-color: #f2f2ff;
+ border-radius: 6px 6px 0 0;
+ color: #8787a5;
+ float: right;
+ font-size: 85%;
+ line-height: 1;
+ max-width: 85%;
+ overflow-wrap: break-word;
+ padding: 0.25em 0.4em;
+ text-align: right;
+}
+
+.code-block-content {
+ clear: both;
+ display: block;
+}
+
+code > span.highlighted {
+ background: lavender;
+}
+
kbd {
background-color: #fafbfc;
border: 1px solid #c6cbd1;
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 d38eb2b900..a63f364f3c 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
@@ -25,6 +25,36 @@ pre > code.hljs {
counter-reset: line;
}
+pre > code.hljs[heading] {
+ border-top-right-radius: 0;
+}
+
+.code-block {
+ position: relative;
+}
+
+.code-block-heading {
+ background-color: #f2f2ff;
+ border-radius: 6px 6px 0 0;
+ color: #8787a5;
+ float: right;
+ font-size: 85%;
+ line-height: 1;
+ max-width: 85%;
+ overflow-wrap: break-word;
+ padding: 0.25em 0.4em;
+ text-align: right;
+}
+
+.code-block-content {
+ clear: both;
+ display: block;
+}
+
+code > span.highlighted {
+ background: lavender;
+}
+
kbd {
background-color: #fafbfc;
border: 1px solid #c6cbd1;
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 d38eb2b900..a63f364f3c 100644
--- a/test/functional/test_site_convert/expected/markbind/css/markbind.css
+++ b/test/functional/test_site_convert/expected/markbind/css/markbind.css
@@ -25,6 +25,36 @@ pre > code.hljs {
counter-reset: line;
}
+pre > code.hljs[heading] {
+ border-top-right-radius: 0;
+}
+
+.code-block {
+ position: relative;
+}
+
+.code-block-heading {
+ background-color: #f2f2ff;
+ border-radius: 6px 6px 0 0;
+ color: #8787a5;
+ float: right;
+ font-size: 85%;
+ line-height: 1;
+ max-width: 85%;
+ overflow-wrap: break-word;
+ padding: 0.25em 0.4em;
+ text-align: right;
+}
+
+.code-block-content {
+ clear: both;
+ display: block;
+}
+
+code > span.highlighted {
+ background: lavender;
+}
+
kbd {
background-color: #fafbfc;
border: 1px solid #c6cbd1;
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 d38eb2b900..a63f364f3c 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
@@ -25,6 +25,36 @@ pre > code.hljs {
counter-reset: line;
}
+pre > code.hljs[heading] {
+ border-top-right-radius: 0;
+}
+
+.code-block {
+ position: relative;
+}
+
+.code-block-heading {
+ background-color: #f2f2ff;
+ border-radius: 6px 6px 0 0;
+ color: #8787a5;
+ float: right;
+ font-size: 85%;
+ line-height: 1;
+ max-width: 85%;
+ overflow-wrap: break-word;
+ padding: 0.25em 0.4em;
+ text-align: right;
+}
+
+.code-block-content {
+ clear: both;
+ display: block;
+}
+
+code > span.highlighted {
+ background: lavender;
+}
+
kbd {
background-color: #fafbfc;
border: 1px solid #c6cbd1;
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 d38eb2b900..a63f364f3c 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
@@ -25,6 +25,36 @@ pre > code.hljs {
counter-reset: line;
}
+pre > code.hljs[heading] {
+ border-top-right-radius: 0;
+}
+
+.code-block {
+ position: relative;
+}
+
+.code-block-heading {
+ background-color: #f2f2ff;
+ border-radius: 6px 6px 0 0;
+ color: #8787a5;
+ float: right;
+ font-size: 85%;
+ line-height: 1;
+ max-width: 85%;
+ overflow-wrap: break-word;
+ padding: 0.25em 0.4em;
+ text-align: right;
+}
+
+.code-block-content {
+ clear: both;
+ display: block;
+}
+
+code > span.highlighted {
+ background: lavender;
+}
+
kbd {
background-color: #fafbfc;
border: 1px solid #c6cbd1;
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 d38eb2b900..a63f364f3c 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
@@ -25,6 +25,36 @@ pre > code.hljs {
counter-reset: line;
}
+pre > code.hljs[heading] {
+ border-top-right-radius: 0;
+}
+
+.code-block {
+ position: relative;
+}
+
+.code-block-heading {
+ background-color: #f2f2ff;
+ border-radius: 6px 6px 0 0;
+ color: #8787a5;
+ float: right;
+ font-size: 85%;
+ line-height: 1;
+ max-width: 85%;
+ overflow-wrap: break-word;
+ padding: 0.25em 0.4em;
+ text-align: right;
+}
+
+.code-block-content {
+ clear: both;
+ display: block;
+}
+
+code > span.highlighted {
+ background: lavender;
+}
+
kbd {
background-color: #fafbfc;
border: 1px solid #c6cbd1;