Skip to content

Commit 733f4ef

Browse files
committed
Add support for line numbers in Markdown code fences
1 parent b6af99a commit 733f4ef

File tree

2 files changed

+63
-1
lines changed

2 files changed

+63
-1
lines changed

packages/kg-markdown-html-renderer/lib/markdown-html-renderer.js

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,35 @@ const namedHeaders = function ({ghostVersion} = {}) {
4242
};
4343
};
4444

45+
const fenceLineNumbers = function (md) {
46+
const proxy = (tokens, idx, options, env, self) => self.renderToken(tokens, idx, options);
47+
const defaultFence = md.renderer.rules.fence || proxy;
48+
49+
// Originally from https://github.com/coreyasmith/markdown-it-fence-line-numbers
50+
md.renderer.rules.fence = function (tokens, idx, options, env, self) {
51+
const token = tokens[idx];
52+
const info = token.info;
53+
const attribute = 'line-numbers';
54+
55+
if (!info) {
56+
return defaultFence(tokens, idx, options, env, self);
57+
}
58+
59+
// line-numbers must come after the first word in the info string to be rendered.
60+
// Example: ```ruby line-numbers
61+
// If line-numbers is specified as the first word, fallback to the default behavior
62+
// (i.e., treat line-numbers as the language).
63+
const langAttrs = info.split(/(\s+)/g).slice(2).join('');
64+
const attributeRegex = new RegExp(`\\b${attribute}\\b`);
65+
if (!langAttrs || !attributeRegex.test(langAttrs)) {
66+
return defaultFence(tokens, idx, options, env, self);
67+
}
68+
69+
token.attrJoin('class', attribute);
70+
return defaultFence(tokens, idx, options, env, self);
71+
};
72+
};
73+
4574
const selectRenderer = function (options) {
4675
const version = semver.coerce(options.ghostVersion || '4.0');
4776

@@ -77,7 +106,8 @@ const selectRenderer = function (options) {
77106
.use(require('markdown-it-image-lazy-loading'))
78107
.use(namedHeaders(options))
79108
.use(require('markdown-it-sub'))
80-
.use(require('markdown-it-sup'));
109+
.use(require('markdown-it-sup'))
110+
.use(fenceLineNumbers);
81111

82112
markdownIt.linkify.set({
83113
fuzzyLink: false

packages/kg-markdown-html-renderer/test/markdown-html-renderer.test.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,27 @@ describe('Markdown HTML renderer', function () {
2323
const result = renderer.render(markdown, {ghostVersion: '3.0'});
2424
result.should.containEql('loading="lazy"');
2525
});
26+
27+
it('outputs `line-numbers` class on fenced code blocks when specified', function () {
28+
const markdown = `
29+
\`\`\`javascript line-numbers
30+
const foo = 'bar';
31+
\`\`\`
32+
`;
33+
const result = renderer.render(markdown, {ghostVersion: '4.0'});
34+
result.should.containEql('class="line-numbers language-javascript"');
35+
});
36+
37+
it('does not output `line-numbers` class on fenced code blocks when not specified', function () {
38+
const markdown = `
39+
\`\`\`javascript
40+
const foo = 'bar';
41+
\`\`\`
42+
`;
43+
const result = renderer.render(markdown, {ghostVersion: '4.0'});
44+
result.should.containEql('class="language-javascript"');
45+
result.should.not.containEql('line-numbers');
46+
});
2647
});
2748

2849
describe('<4.x', function () {
@@ -43,5 +64,16 @@ describe('Markdown HTML renderer', function () {
4364
result.should.match(/<h1 id="headerone">/);
4465
result.should.match(/<h2 id="hadertwo">/);
4566
});
67+
68+
it('does not output `line-numbers` class on fenced code blocks when specified', function () {
69+
const markdown = `
70+
\`\`\`javascript line-numbers
71+
const foo = 'bar';
72+
\`\`\`
73+
`;
74+
const result = renderer.render(markdown, {ghostVersion: '3.0'});
75+
result.should.containEql('class="language-javascript"');
76+
result.should.not.containEql('line-numbers');
77+
});
4678
});
4779
});

0 commit comments

Comments
 (0)