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;