Skip to content

Commit 0c34f6d

Browse files
heiskrPeter Bengtsson
andauthored
Code-headers plugin rewrite (#37654)
Co-authored-by: Peter Bengtsson <peterbe@github.com>
1 parent 76af8ce commit 0c34f6d

9 files changed

Lines changed: 73 additions & 164 deletions

File tree

components/ui/MarkdownContent/stylesheets/code.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
overflow: auto;
1111
}
1212

13-
[class~="code-extra"] {
13+
[class~="code-example"] {
1414
margin-top: 1.5rem;
1515

1616
pre,

lib/render-content/create-processor.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import erb from 'highlight.js/lib/languages/erb'
1414
import powershell from 'highlight.js/lib/languages/powershell'
1515
import graphql from 'highlight.js/lib/languages/graphql'
1616
import html from 'rehype-stringify'
17-
import remarkCodeExtra from 'remark-code-extra'
1817
import codeHeader from './plugins/code-header.js'
1918
import rewriteLocalLinks from './plugins/rewrite-local-links.js'
2019
import rewriteImgSources from './plugins/rewrite-asset-urls.js'
@@ -34,14 +33,14 @@ export default function createProcessor(context) {
3433
.use(process.env.COMMONMARK ? gfm : null)
3534
// Markdown AST below vvv
3635
.use(parseInfoString)
37-
.use(remarkCodeExtra, { transform: codeHeader })
3836
.use(emoji)
3937
// Markdown AST above ^^^
4038
.use(remark2rehype, { allowDangerousHtml: true })
4139
// HTML AST below vvv
4240
.use(slug)
4341
.use(useEnglishHeadings, context)
4442
.use(headingLinks)
43+
.use(codeHeader)
4544
.use(annotate)
4645
.use(highlight, {
4746
languages: { graphql, dockerfile, http, groovy, erb, powershell },

lib/render-content/plugins/annotate.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { visit } from 'unist-util-visit'
3535
import { h } from 'hastscript'
3636
import { fromMarkdown } from 'mdast-util-from-markdown'
3737
import { toHast } from 'mdast-util-to-hast'
38+
// import { header } from './code-header.js'
3839

3940
const languages = yaml.load(fs.readFileSync('./data/variables/code-languages.yml', 'utf8'))
4041

@@ -71,7 +72,7 @@ function createAnnotatedNode(node) {
7172
const rows = chunk(groups, 2)
7273

7374
// Render the HTML
74-
return template({ lang, rows })
75+
return template({ lang, code, rows })
7576
}
7677

7778
function validate(lang, code) {
@@ -116,10 +117,11 @@ function matchComment(lang) {
116117
return (line) => regex.test(line)
117118
}
118119

119-
function template({ lang, rows }) {
120+
function template({ lang, code, rows }) {
120121
return h(
121122
'div',
122123
{ class: 'annotate' },
124+
// header(lang, code),
123125
rows.map(([note, code]) =>
124126
h('div', { className: 'annotate-row' }, [
125127
h(
Lines changed: 47 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,105 +1,42 @@
1+
/**
2+
* Adds a bar above code blocks that shows the language and a copy button
3+
*/
4+
5+
import yaml from 'js-yaml'
6+
import fs from 'fs'
7+
import { visit } from 'unist-util-visit'
18
import { h } from 'hastscript'
29
import octicons from '@primer/octicons'
310
import { parse } from 'parse5'
411
import { fromParse5 } from 'hast-util-from-parse5'
512

6-
const LANGUAGE_MAP = {
7-
asp: 'ASP',
8-
aspx: 'ASP',
9-
'aspx-vb': 'ASP',
10-
as3: 'ActionScript',
11-
apache: 'ApacheConf',
12-
nasm: 'Assembly',
13-
bat: 'Batchfile',
14-
'c#': 'C#',
15-
csharp: 'C#',
16-
c: 'C',
17-
'c++': 'C++',
18-
cpp: 'C++',
19-
chpl: 'Chapel',
20-
coffee: 'CoffeeScript',
21-
'coffee-script': 'CoffeeScript',
22-
cfm: 'ColdFusion',
23-
'common-lisp': 'Common Lisp',
24-
lisp: 'Common Lisp',
25-
dpatch: 'Darcs Patch',
26-
dart: 'Dart',
27-
elisp: 'Emacs Lisp',
28-
emacs: 'Emacs Lisp',
29-
'emacs-lisp': 'Emacs Lisp',
30-
pot: 'Gettext Catalog',
31-
html: 'HTML',
32-
xhtml: 'HTML',
33-
'html+erb': 'HTML+ERB',
34-
erb: 'HTML+ERB',
35-
irc: 'IRC log',
36-
json: 'JSON',
37-
jsp: 'Java Server Pages',
38-
java: 'Java',
39-
javascript: 'JavaScript',
40-
js: 'JavaScript',
41-
lhs: 'Literate Haskell',
42-
'literate-haskell': 'Literate Haskell',
43-
objc: 'Objective-C',
44-
openedge: 'OpenEdge ABL',
45-
progress: 'OpenEdge ABL',
46-
abl: 'OpenEdge ABL',
47-
pir: 'Parrot Internal Representation',
48-
posh: 'PowerShell',
49-
puppet: 'Puppet',
50-
'pure-data': 'Pure Data',
51-
raw: 'Raw token data',
52-
rb: 'Ruby',
53-
ruby: 'Ruby',
54-
r: 'R',
55-
scheme: 'Scheme',
56-
bash: 'Shell',
57-
sh: 'Shell',
58-
shell: 'Shell',
59-
zsh: 'Shell',
60-
supercollider: 'SuperCollider',
61-
tex: 'TeX',
62-
ts: 'TypeScript',
63-
vim: 'Vim script',
64-
viml: 'Vim script',
65-
rst: 'reStructuredText',
66-
xbm: 'X BitMap',
67-
xpm: 'X PixMap',
68-
yaml: 'YAML',
69-
yml: 'YAML',
70-
71-
// Unofficial languages
72-
shellsession: 'Shell',
73-
jsx: 'JSX',
74-
}
75-
76-
const COPY_REGEX = /\{:copy\}$/
13+
const languages = yaml.load(fs.readFileSync('./data/variables/code-languages.yml', 'utf8'))
7714

78-
/**
79-
* Adds a bar above code blocks that shows the language and a copy button
80-
*/
81-
export default function addCodeHeader(node) {
82-
// Check if the language matches `lang{:copy}`
83-
const hasCopy = node.lang && COPY_REGEX.test(node.lang)
15+
const matcher = (node) =>
16+
node.type === 'element' &&
17+
node.tagName === 'pre' &&
18+
// For now, limit to ones with the copy meta,
19+
// but we may enable for all examples later.
20+
getPreMeta(node).copy &&
21+
// Don't add this header for annotated examples.
22+
!getPreMeta(node).annotate
8423

85-
if (hasCopy) {
86-
// js{:copy} => js
87-
node.lang = node.lang.replace(COPY_REGEX, '')
88-
} else {
89-
// It doesn't have the copy annotation, so don't add the header
90-
return
24+
export default function codeHeader() {
25+
return (tree) => {
26+
visit(tree, matcher, (node, index, parent) => {
27+
parent.children[index] = wrapCodeExample(node)
28+
})
9129
}
30+
}
9231

93-
// Display the language using the above map of `{ [shortCode]: language }`
94-
const language = LANGUAGE_MAP[node.lang] || node.lang || 'Code'
95-
96-
const btnIconHtml = octicons.copy.toSVG()
97-
const btnIconAst = parse(String(btnIconHtml), { sourceCodeLocationInfo: true })
98-
const btnIcon = fromParse5(btnIconAst, { file: btnIconHtml })
32+
function wrapCodeExample(node) {
33+
const lang = node.children[0].properties.className?.[0].replace('language-', '')
34+
const code = node.children[0].children[0].value
35+
return h('div', { className: 'code-example' }, [header(lang, code), node])
36+
}
9937

100-
// Need to create the header using Markdown AST utilities, to fit
101-
// into the Unified processor ecosystem.
102-
const header = h(
38+
export function header(lang, code) {
39+
return h(
10340
'header',
10441
{
10542
class: [
@@ -109,24 +46,35 @@ export default function addCodeHeader(node) {
10946
'p-2',
11047
'text-small',
11148
'rounded-top-1',
112-
'border',
49+
'border-top',
50+
'border-left',
51+
'border-right',
11352
],
11453
},
11554
[
116-
h('span', language),
55+
h('span', languages[lang]?.name),
11756
h(
11857
'button',
11958
{
12059
class: ['js-btn-copy', 'btn', 'btn-sm', 'tooltipped', 'tooltipped-nw'],
121-
'data-clipboard-text': node.value,
60+
'data-clipboard-text': code,
12261
'aria-label': 'Copy code to clipboard',
12362
},
124-
btnIcon
63+
btnIcon()
12564
),
12665
]
12766
)
67+
}
12868

129-
return {
130-
before: [header],
131-
}
69+
function btnIcon() {
70+
const btnIconHtml = octicons.copy.toSVG()
71+
const btnIconAst = parse(String(btnIconHtml), { sourceCodeLocationInfo: true })
72+
const btnIcon = fromParse5(btnIconAst, { file: btnIconHtml })
73+
return btnIcon
74+
}
75+
76+
function getPreMeta(node) {
77+
// Here's why this monstrosity works:
78+
// https://github.com/syntax-tree/mdast-util-to-hast/blob/c87cd606731c88a27dbce4bfeaab913a9589bf83/lib/handlers/code.js#L40-L42
79+
return node.children[0]?.data?.meta || {}
13280
}

lib/render-content/plugins/parse-info-string.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,26 @@
77

88
import { visit } from 'unist-util-visit'
99

10-
const matcher = (node) => node.type === 'code' && node.lang && node.meta
10+
const matcher = (node) => node.type === 'code' && node.lang
1111

1212
export default function parseInfoString() {
1313
return (tree) => {
1414
visit(tree, matcher, (node) => {
1515
node.meta = strToObj(node.meta)
16+
17+
// Temporary, change {:copy} to ` copy` to avoid conflict in styles.
18+
// We may end up enabling copy on all code examples later.
19+
const copyTag = '{:copy}'
20+
if (node.lang.includes(copyTag)) {
21+
node.meta.copy = true
22+
node.lang = node.lang.replace(copyTag, '')
23+
}
1624
})
1725
}
1826
}
1927

2028
function strToObj(str) {
29+
if (!str) return {}
2130
return Object.fromEntries(
2231
str
2332
.split(/\s+/g)

package-lock.json

Lines changed: 0 additions & 49 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,6 @@
120120
"rehype-raw": "^6.1.1",
121121
"rehype-slug": "^5.0.1",
122122
"rehype-stringify": "^9.0.3",
123-
"remark-code-extra": "^1.0.1",
124123
"remark-gemoji-to-emoji": "^1.1.0",
125124
"remark-gfm": "^3.0.1",
126125
"remark-parse": "^10.0.1",

src/codeql-cli/scripts/convert-markdown-for-docs.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ export async function convertContentToDocs(content, frontmatterDefaults = {}) {
8686
visitParents(ast, matcher, (node, ancestors) => {
8787
// Add the copy button to the example command
8888
if (node.type === 'code' && node.value.startsWith(`codeql ${frontmatter.title}`)) {
89-
node.lang = 'shell{:copy}'
89+
node.lang = 'shell'
90+
node.meta = 'copy'
9091
}
9192

9293
// This is the beginning of a secondary options section. For example,

tests/unit/render-content.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -248,12 +248,12 @@ $resourceGroupName = "octocat-testgroup"
248248
test('does not autoguess code block language', async () => {
249249
const template = nl(`
250250
\`\`\`
251-
some code
252-
\`\`\`\
251+
var a = 1
252+
\`\`\`
253253
`)
254254
const html = await renderContent(template)
255255
const $ = cheerio.load(html, { xmlMode: true })
256-
expect($.html().includes('<pre><code>some code\n</code></pre>')).toBeTruthy()
256+
expect($.html().includes('var a = 1')).toBeTruthy()
257257
})
258258

259259
test('renders a line break in a table', async () => {
@@ -267,15 +267,15 @@ some code
267267
)
268268
})
269269

270-
test('renders a copy button for code blocks with {:copy} annotation', async () => {
270+
test('renders a copy button for code blocks with language specified', async () => {
271271
const template = nl(`
272-
\`\`\`js{:copy}
273-
some code
274-
\`\`\`\
272+
\`\`\`javascript copy
273+
var a = 1
274+
\`\`\`
275275
`)
276276
const html = await renderContent(template)
277277
const $ = cheerio.load(html)
278278
const el = $('button.js-btn-copy')
279-
expect(el.data('clipboard-text')).toBe('some code')
279+
expect(el.data('clipboard-text')).toBe('var a = 1\n')
280280
})
281281
})

0 commit comments

Comments
 (0)