diff --git a/packages/saber-highlight-css/default.css b/packages/saber-highlight-css/default.css
index 98b7c847d..bb98186ad 100644
--- a/packages/saber-highlight-css/default.css
+++ b/packages/saber-highlight-css/default.css
@@ -11,11 +11,12 @@
position: absolute;
right: 10px;
top: 5px;
- font-size: .75rem;
+ font-size: 0.75rem;
color: #bdc5d1;
}
-.saber-highlight-mask, .saber-highlight-code {
+.saber-highlight-mask,
+.saber-highlight-code {
line-height: 1.5;
background-color: transparent !important;
text-shadow: none !important;
@@ -52,10 +53,34 @@
.saber-highlight-code code,
.saber-highlight-mask {
- font-size: .875rem;
+ font-size: 0.875rem;
}
.code-line.highlighted {
background: rgba(3, 169, 244, 0.121);
- box-shadow: inset 2px 0 0 0 rgba(3, 169, 244, 0.278)
+}
+
+/* Line numbers */
+.saber-highlight-line-numbers {
+ pointer-events: none;
+ font-size: 100%;
+ float: left;
+ letter-spacing: -1px;
+ border-right: 1px solid #999;
+ user-select: none;
+ text-align: right;
+ padding-right: 0.8rem;
+ margin-right: .8rem;
+ counter-reset: linenumber;
+}
+
+.saber-highlight-line-numbers > span {
+ counter-increment: linenumber;
+ display: block;
+}
+
+.saber-highlight-line-numbers > span:before {
+ content: counter(linenumber);
+ color: #999;
+ display: block;
}
diff --git a/packages/saber/lib/markdown/__test__/__snapshots__/highlight-plugin.test.js.snap b/packages/saber/lib/markdown/__test__/__snapshots__/highlight-plugin.test.js.snap
index 7418952ea..7510b15cb 100644
--- a/packages/saber/lib/markdown/__test__/__snapshots__/highlight-plugin.test.js.snap
+++ b/packages/saber/lib/markdown/__test__/__snapshots__/highlight-plugin.test.js.snap
@@ -1,3 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`code block markdown.lineNumbers = true 1`] = `"
const cry = Array(3).fill('ora').join(' ')
"`;
+
+exports[`code block with {lineNumbers:true} 1`] = `"const cry = Array(3).fill('ora').join(' ')
"`;
+
exports[`main 1`] = `""`;
diff --git a/packages/saber/lib/markdown/__test__/highlight-plugin.test.js b/packages/saber/lib/markdown/__test__/highlight-plugin.test.js
index 6dda36792..b731af847 100644
--- a/packages/saber/lib/markdown/__test__/highlight-plugin.test.js
+++ b/packages/saber/lib/markdown/__test__/highlight-plugin.test.js
@@ -16,3 +16,33 @@ test('main', () => {
)
expect(html).toMatchSnapshot()
})
+
+test('code block with {lineNumbers:true}', () => {
+ const md = new Markdown()
+ const { env } = createEnv()
+ md.use(fenceOptionsPlugin)
+ const html = md.render(
+ `
+\`\`\`js {lineNumbers:true}
+const cry = Array(3).fill('ora').join(' ')
+\`\`\`
+ `,
+ env
+ )
+ expect(html).toMatchSnapshot()
+})
+
+test('code block markdown.lineNumbers = true', () => {
+ const md = new Markdown()
+ const { env } = createEnv()
+ md.use(fenceOptionsPlugin, { lineNumbers: true })
+ const html = md.render(
+ `
+\`\`\`js {lineNumbers:true}
+const cry = Array(3).fill('ora').join(' ')
+\`\`\`
+ `,
+ env
+ )
+ expect(html).toMatchSnapshot()
+})
diff --git a/packages/saber/lib/markdown/highlight-plugin.js b/packages/saber/lib/markdown/highlight-plugin.js
index d01d934dc..2ff429437 100644
--- a/packages/saber/lib/markdown/highlight-plugin.js
+++ b/packages/saber/lib/markdown/highlight-plugin.js
@@ -1,34 +1,98 @@
const RE = /\s*{([^}]+)}/
const parseOptions = str => {
+ if (!RE.test(str)) {
+ return {}
+ }
+
const [, options] = RE.exec(str)
const fn = new Function(`return {${options}}`) // eslint-disable-line no-new-func
return fn()
}
-module.exports = (md, { highlightedLineBackground } = {}) => {
- const renderPreWrapper = (
+const generateLineNumbers = code =>
+ '' +
+ code
+ .trim()
+ .split('\n')
+ .map(() => ``)
+ .join('') +
+ ''
+
+module.exports = (
+ md,
+ { highlightedLineBackground, lineNumbers = false } = {}
+) => {
+ const renderPreWrapper = ({
preWrapperAttrs,
preAttrs,
codeAttrs,
code,
- codeMask = ''
- ) =>
- `${codeMask}
${code.trim()}
`
+ codeMask = '',
+ lines = ''
+ }) =>
+ `${codeMask}
${lines}${code.trim()}
`
md.renderer.rules.fence = (...args) => {
const [tokens, idx, options, env, self] = args
const token = tokens[idx]
+ const fenceOptions = parseOptions(token.info)
const langName = token.info.replace(RE, '').trim()
+ const langClass = `language-${langName || 'text'}`
+ token.info = langName
const code = options.highlight
? options.highlight(token.content, langName, env)
: md.utils.escapeHtml(token.content)
+ const highlightLines = fenceOptions.highlightLines
+ ? fenceOptions.highlightLines.map(v =>
+ `${v}`.split('-').map(v => parseInt(v, 10))
+ )
+ : []
+
+ const codeMask =
+ highlightLines.length === 0
+ ? ''
+ : `` +
+ md.utils
+ .escapeHtml(token.content)
+ .split('\n')
+ .map((split, index) => {
+ split = split || ''
+ const lineNumber = index + 1
+ const inRange = highlightLines.some(([start, end]) => {
+ if (start && end) {
+ return lineNumber >= start && lineNumber <= end
+ }
+
+ return lineNumber === start
+ })
+ if (inRange) {
+ const style = highlightedLineBackground
+ ? ` style="background-color: ${highlightedLineBackground}"`
+ : ''
+ return `
${split}
`
+ }
+
+ return `
${split}
`
+ })
+ .join('') +
+ '
'
+
const renderAttrs = attrs => self.renderAttrs({ attrs })
+ const shouldGenerateLineNumbers =
+ // It might be false so check for undefined
+ fenceOptions.lineNumbers === undefined
+ ? // Defaults to global config
+ lineNumbers
+ : // If it's set to false, even if the global config says true, ignore
+ fenceOptions.lineNumbers
+ const lines = shouldGenerateLineNumbers ? generateLineNumbers(code) : ''
- const langClass = `language-${langName || 'text'}`
const preAttrs = renderAttrs([
...(token.attrs || []),
['class', ['saber-highlight-code', langClass].filter(Boolean).join(' ')]
@@ -38,56 +102,21 @@ module.exports = (md, { highlightedLineBackground } = {}) => {
['class', langClass]
])
const preWrapperAttrs = renderAttrs([
- ['class', 'saber-highlight'],
+ [
+ 'class',
+ `saber-highlight${shouldGenerateLineNumbers ? ' has-line-numbers' : ''}`
+ ],
['v-pre', ''],
['data-lang', langName]
])
- if (!token.info || !RE.test(token.info)) {
- return renderPreWrapper(preWrapperAttrs, preAttrs, codeAttrs, code)
- }
-
- const fenceOptions = parseOptions(token.info)
- const highlightLines =
- fenceOptions.highlightLines &&
- fenceOptions.highlightLines.map(v =>
- `${v}`.split('-').map(v => parseInt(v, 10))
- )
- token.info = langName
-
- const codeMask =
- `` +
- md.utils
- .escapeHtml(token.content)
- .split('\n')
- .map((split, index) => {
- split = split || ''
- const lineNumber = index + 1
- const inRange = highlightLines.some(([start, end]) => {
- if (start && end) {
- return lineNumber >= start && lineNumber <= end
- }
-
- return lineNumber === start
- })
- if (inRange) {
- const style = highlightedLineBackground
- ? ` style="background-color: ${highlightedLineBackground}"`
- : ''
- return `
${split}
`
- }
-
- return `
${split}
`
- })
- .join('') +
- '
'
-
- return renderPreWrapper(
+ return renderPreWrapper({
preWrapperAttrs,
preAttrs,
codeAttrs,
code,
- codeMask
- )
+ codeMask,
+ lines
+ })
}
}
diff --git a/packages/saber/lib/plugins/transformer-markdown.js b/packages/saber/lib/plugins/transformer-markdown.js
index 374d156e4..0911a3748 100644
--- a/packages/saber/lib/plugins/transformer-markdown.js
+++ b/packages/saber/lib/plugins/transformer-markdown.js
@@ -77,7 +77,10 @@ function transformMarkdown(api, page) {
},
{
name: 'highlight',
- resolve: require.resolve('../markdown/highlight-plugin')
+ resolve: require.resolve('../markdown/highlight-plugin'),
+ options: {
+ lineNumbers: markdown.lineNumbers
+ }
},
{
name: 'link',
diff --git a/packages/saber/lib/utils/validateConfig.js b/packages/saber/lib/utils/validateConfig.js
index fc11fe33c..c9104ff8f 100644
--- a/packages/saber/lib/utils/validateConfig.js
+++ b/packages/saber/lib/utils/validateConfig.js
@@ -36,6 +36,7 @@ module.exports = (config, { dev }) => {
options: 'object?',
headings: 'object?',
highlighter: 'string?',
+ lineNumbers: 'boolean?',
// Same as the type of Saber plugins
plugins
},
diff --git a/website/pages/docs/markdown-features.md b/website/pages/docs/markdown-features.md
index af63c59bc..c50e02dc0 100644
--- a/website/pages/docs/markdown-features.md
+++ b/website/pages/docs/markdown-features.md
@@ -276,6 +276,54 @@ If you want to override the font size or font family, you need to add CSS for bo
}
```
+### Line Numbers in Code Blocks
+
+Input:
+
+````markdown
+```js {lineNumbers:true,highlightLines:['2-5']}
+[
+ {
+ text: 'A page',
+ slug: 'a-page',
+ level: 1
+ },
+ {
+ text: 'A section',
+ slug: 'a-section',
+ level: 2
+ },
+ {
+ text: 'Another section',
+ slug: 'another-section',
+ level: 3
+ }
+]
+```
+````
+
+Output:
+
+```js {lineNumbers:true,highlightLines:['2-5']}
+[
+ {
+ text: 'A page',
+ slug: 'a-page',
+ level: 1
+ },
+ {
+ text: 'A section',
+ slug: 'a-section',
+ level: 2
+ },
+ {
+ text: 'Another section',
+ slug: 'another-section',
+ level: 3
+ }
+]
+```
+
## Configure markdown-it
Check out [markdown.options](./saber-config.md#options) for setting markdown-it options and [markdown.plugins](./saber-config.md#plugins-2) for adding markdown-it plugins.
diff --git a/website/pages/docs/saber-config.md b/website/pages/docs/saber-config.md
index df158ec20..d221b6b3d 100644
--- a/website/pages/docs/saber-config.md
+++ b/website/pages/docs/saber-config.md
@@ -167,6 +167,13 @@ interface MarkdownPlugin {
}
```
+### lineNumbers
+
+- Type: `boolean`
+- Default: `false`
+
+Show line numbers in code blocks.
+
## permalinks
- Type: `Permalinks` `(page: Page) => Permalinks`