Add Copilot prompt blocks and inline prompts (#57272)
Co-authored-by: Sarah Schneider <sarahs@github.com> Co-authored-by: Sarah Schneider <sarahs@users.noreply.github.com>
This commit is contained in:
@@ -7,6 +7,9 @@ bash:
|
||||
bicep:
|
||||
name: Bicep
|
||||
comment: slash
|
||||
copilot:
|
||||
name: Copilot Chat prompt
|
||||
comment: none
|
||||
csharp:
|
||||
name: C#
|
||||
comment: slash
|
||||
|
||||
26
package-lock.json
generated
26
package-lock.json
generated
@@ -96,6 +96,7 @@
|
||||
"tcp-port-used": "1.0.2",
|
||||
"tsx": "^4.19.4",
|
||||
"unified": "^11.0.5",
|
||||
"unist-util-find": "^3.0.0",
|
||||
"unist-util-visit": "^5.0.0",
|
||||
"url-template": "^3.1.1",
|
||||
"walk-sync": "^4.0.1"
|
||||
@@ -10563,6 +10564,11 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
||||
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
|
||||
},
|
||||
"node_modules/lodash.iteratee": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.iteratee/-/lodash.iteratee-4.7.0.tgz",
|
||||
"integrity": "sha512-yv3cSQZmfpbIKo4Yo45B1taEvxjNvcpF1CEOc0Y6dEyvhPIfEJE3twDwPgWTPQubcSgXyBwBKG6wpQvWMDOf6Q=="
|
||||
},
|
||||
"node_modules/lodash.kebabcase": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz",
|
||||
@@ -15653,6 +15659,20 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.0.tgz",
|
||||
"integrity": "sha512-MFETx3tbTjE7Uk6vvnWINA/1iJ7LuMdO4fcq8UfF0pRbj01aGLduVvQcRyswuACJdpnHgg8E3rQLhaRdNEJS0w=="
|
||||
},
|
||||
"node_modules/unist-util-find": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-find/-/unist-util-find-3.0.0.tgz",
|
||||
"integrity": "sha512-T7ZqS7immLjYyC4FCp2hDo3ksZ1v+qcbb+e5+iWxc2jONgHOLXPCpms1L8VV4hVxCXgWTxmBHDztuEZFVwC+Gg==",
|
||||
"dependencies": {
|
||||
"@types/unist": "^3.0.0",
|
||||
"lodash.iteratee": "^4.0.0",
|
||||
"unist-util-visit": "^5.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/unist-util-find-after": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz",
|
||||
@@ -15683,6 +15703,12 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/unist-util-find/node_modules/@types/unist": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
|
||||
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unist-util-is": {
|
||||
"version": "5.1.1",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -243,6 +243,7 @@
|
||||
"tcp-port-used": "1.0.2",
|
||||
"tsx": "^4.19.4",
|
||||
"unified": "^11.0.5",
|
||||
"unist-util-find": "^3.0.0",
|
||||
"unist-util-visit": "^5.0.0",
|
||||
"url-template": "^3.1.1",
|
||||
"walk-sync": "^4.0.1"
|
||||
|
||||
@@ -6,6 +6,7 @@ import Octicon from './octicon'
|
||||
import Ifversion from './ifversion'
|
||||
import { Tool, tags as toolTags } from './tool'
|
||||
import { Spotlight, tags as spotlightTags } from './spotlight'
|
||||
import { Prompt } from './prompt'
|
||||
|
||||
export const engine = new Liquid({
|
||||
extname: '.html',
|
||||
@@ -25,6 +26,8 @@ for (const tag in spotlightTags) {
|
||||
engine.registerTag(tag, Spotlight)
|
||||
}
|
||||
|
||||
engine.registerTag('prompt', Prompt)
|
||||
|
||||
/**
|
||||
* Like the `size` filter, but specifically for
|
||||
* getting the number of keys in an object
|
||||
|
||||
31
src/content-render/liquid/prompt.js
Normal file
31
src/content-render/liquid/prompt.js
Normal file
@@ -0,0 +1,31 @@
|
||||
// src/content-render/liquid/prompt.js
|
||||
// Defines {% prompt %}…{% endprompt %} to wrap its content in <code> and append the Copilot icon.
|
||||
|
||||
import octicons from '@primer/octicons'
|
||||
|
||||
export const Prompt = {
|
||||
type: 'block',
|
||||
|
||||
// Collect everything until {% endprompt %}
|
||||
parse(tagToken, remainTokens) {
|
||||
this.templates = []
|
||||
const stream = this.liquid.parser.parseStream(remainTokens)
|
||||
stream
|
||||
.on('template', (tpl) => this.templates.push(tpl))
|
||||
.on('tag:endprompt', () => stream.stop())
|
||||
.on('end', () => {
|
||||
throw new Error(`{% prompt %} tag not closed`)
|
||||
})
|
||||
stream.start()
|
||||
},
|
||||
|
||||
// Render the inner Markdown, wrap in <code>, then append the SVG
|
||||
render: function* (scope) {
|
||||
const content = yield this.liquid.renderer.renderTemplates(this.templates, scope)
|
||||
|
||||
// build a URL with the prompt text encoded as query parameter
|
||||
const promptParam = encodeURIComponent(content)
|
||||
const href = `https://github.com/copilot?prompt=${promptParam}`
|
||||
return `<code>${content}</code><a href="${href}" target="_blank" class="tooltipped tooltipped-nw ml-1" aria-label="Run this prompt in Copilot Chat" style="text-decoration:none;">${octicons.copilot.toSVG()}</a>`
|
||||
},
|
||||
}
|
||||
203
src/content-render/tests/copilot-code-blocks.js
Normal file
203
src/content-render/tests/copilot-code-blocks.js
Normal file
@@ -0,0 +1,203 @@
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
import { renderContent } from '@/content-render/index'
|
||||
|
||||
describe('code-header plugin', () => {
|
||||
describe('copilot language code blocks', () => {
|
||||
it('should render basic copilot code block without header (no copy meta)', async () => {
|
||||
const markdown = '```copilot\nImprove the variable names in this function\n```'
|
||||
|
||||
const html = await renderContent(markdown)
|
||||
|
||||
// Should keep copilot as the language (not convert to text without copy meta)
|
||||
expect(html).toContain('language-copilot')
|
||||
// Should NOT wrap in code-example div since no copy meta
|
||||
expect(html).not.toContain('code-example')
|
||||
// Should NOT have header since no copy meta
|
||||
expect(html).not.toContain('<header')
|
||||
})
|
||||
|
||||
it('should render copilot code block with copy button when copy meta is present', async () => {
|
||||
const markdown = '```copilot copy\nImprove the variable names in this function\n```'
|
||||
|
||||
const html = await renderContent(markdown)
|
||||
|
||||
// Should be wrapped in code-example div
|
||||
expect(html).toContain('code-example')
|
||||
// Should have header with copy button
|
||||
expect(html).toContain('<header')
|
||||
expect(html).toContain('js-btn-copy')
|
||||
expect(html).toContain('language-copilot')
|
||||
// Should NOT have prompt button (no prompt meta)
|
||||
expect(html).not.toContain('https://github.com/copilot?prompt=')
|
||||
})
|
||||
|
||||
it('should render copilot code block with prompt button only (no copy meta)', async () => {
|
||||
const markdown = '```copilot prompt\nImprove the variable names in this function\n```'
|
||||
|
||||
const html = await renderContent(markdown)
|
||||
|
||||
// Should be wrapped in code-example div
|
||||
expect(html).toContain('code-example')
|
||||
// Should have header
|
||||
expect(html).toContain('<header')
|
||||
// Should have prompt button
|
||||
expect(html).toContain('https://github.com/copilot?prompt=')
|
||||
expect(html).toContain('language-copilot')
|
||||
// Should NOT have copy button
|
||||
expect(html).not.toContain('js-btn-copy')
|
||||
})
|
||||
|
||||
it('should render copilot code block with both copy and prompt buttons when prompt meta is present', async () => {
|
||||
const markdown = '```copilot copy prompt\nImprove the variable names in this function\n```'
|
||||
|
||||
const html = await renderContent(markdown)
|
||||
|
||||
// Should be wrapped in code-example div
|
||||
expect(html).toContain('code-example')
|
||||
// Should have header with copy button
|
||||
expect(html).toContain('<header')
|
||||
expect(html).toContain('js-btn-copy')
|
||||
// Should have prompt button with encoded URL
|
||||
expect(html).toContain('https://github.com/copilot?prompt=')
|
||||
expect(html).toContain('Improve%20the%20variable%20names%20in%20this%20function')
|
||||
// Should have Copilot icon button
|
||||
expect(html).toContain('aria-label="Run this prompt in Copilot Chat"')
|
||||
expect(html).toContain('language-copilot')
|
||||
})
|
||||
|
||||
it('should render copilot code block with context reference when ref meta is present', async () => {
|
||||
const markdown = `
|
||||
\`\`\`javascript id=js-age
|
||||
function logPersonsAge(a, b, c) {
|
||||
if (c) {
|
||||
console.log(a + " is " + b + " years old.");
|
||||
} else {
|
||||
console.log(a + " does not want to reveal their age.");
|
||||
}
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
\`\`\`copilot copy prompt ref=js-age
|
||||
Improve the variable names in this function
|
||||
\`\`\`
|
||||
`
|
||||
|
||||
const html = await renderContent(markdown)
|
||||
|
||||
// Should have prompt button with both code blocks in URL
|
||||
expect(html).toContain('https://github.com/copilot?prompt=')
|
||||
// Should contain encoded content from both the referenced code and the prompt
|
||||
expect(html).toContain('function%20logPersonsAge')
|
||||
expect(html).toContain('Improve%20the%20variable%20names')
|
||||
// Should have different aria-label indicating context
|
||||
expect(html).toContain('aria-label="Run this prompt with context in Copilot Chat"')
|
||||
})
|
||||
|
||||
it('should render copilot code block with prompt and ref only (no copy meta)', async () => {
|
||||
const markdown = `
|
||||
\`\`\`javascript id=js-age
|
||||
function logPersonsAge(a, b, c) {
|
||||
if (c) {
|
||||
console.log(a + " is " + b + " years old.");
|
||||
} else {
|
||||
console.log(a + " does not want to reveal their age.");
|
||||
}
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
\`\`\`copilot prompt ref=js-age
|
||||
Improve the variable names in this function
|
||||
\`\`\`
|
||||
`
|
||||
|
||||
const html = await renderContent(markdown)
|
||||
|
||||
// Should have prompt button with both code blocks in URL
|
||||
expect(html).toContain('https://github.com/copilot?prompt=')
|
||||
// Should contain encoded content from both the referenced code and the prompt
|
||||
expect(html).toContain('function%20logPersonsAge')
|
||||
expect(html).toContain('Improve%20the%20variable%20names')
|
||||
// Should have different aria-label indicating context
|
||||
expect(html).toContain('aria-label="Run this prompt with context in Copilot Chat"')
|
||||
// Should NOT have copy button
|
||||
expect(html).not.toContain('js-btn-copy')
|
||||
})
|
||||
})
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('should handle missing reference gracefully and fall back to current code only', async () => {
|
||||
// Mock console.warn to capture warning
|
||||
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
|
||||
const markdown =
|
||||
'```copilot copy prompt ref=nonexistent-id\nImprove the variable names in this function\n```'
|
||||
|
||||
const html = await renderContent(markdown)
|
||||
|
||||
// Should warn about missing reference
|
||||
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Can't find referenced code block with id=nonexistent-id"),
|
||||
)
|
||||
|
||||
// Should still render with prompt button using current code only
|
||||
expect(html).toContain('https://github.com/copilot?prompt=')
|
||||
expect(html).toContain('Improve%20the%20variable%20names%20in%20this%20function')
|
||||
// Should NOT contain any referenced code since none was found
|
||||
expect(html).not.toContain('function%20logPersonsAge')
|
||||
// Should have standard aria-label (not context version)
|
||||
expect(html).toContain('aria-label="Run this prompt in Copilot Chat"')
|
||||
// Should not crash or fail
|
||||
expect(html).toContain('code-example')
|
||||
|
||||
// Restore console.warn
|
||||
consoleWarnSpy.mockRestore()
|
||||
})
|
||||
|
||||
it('should not process annotated code blocks', async () => {
|
||||
const markdown = `\`\`\`javascript copy annotate
|
||||
// This is an annotation
|
||||
function test() {}
|
||||
\`\`\``
|
||||
|
||||
const html = await renderContent(markdown)
|
||||
|
||||
// Should NOT wrap in code-example div (annotated blocks are excluded)
|
||||
expect(html).not.toContain('code-example')
|
||||
})
|
||||
|
||||
it('should handle regular code blocks with copy', async () => {
|
||||
const markdown = '```javascript copy\nfunction test() {}\n```'
|
||||
|
||||
const html = await renderContent(markdown)
|
||||
|
||||
// Should render with copy button
|
||||
expect(html).toContain('code-example')
|
||||
expect(html).toContain('js-btn-copy')
|
||||
expect(html).toContain('language-javascript')
|
||||
})
|
||||
})
|
||||
|
||||
describe('URL encoding', () => {
|
||||
it('should properly encode special characters in prompt URLs', async () => {
|
||||
const markdown = '```copilot copy prompt\nHow do I handle "quotes" and & symbols?\n```'
|
||||
|
||||
const html = await renderContent(markdown)
|
||||
|
||||
// Should encode quotes and ampersands properly
|
||||
expect(html).toContain('%22quotes%22')
|
||||
expect(html).toContain('%26%20symbols')
|
||||
})
|
||||
|
||||
it('should handle multiline prompts correctly', async () => {
|
||||
const markdown = `\`\`\`copilot copy prompt
|
||||
This is line 1
|
||||
This is line 2
|
||||
\`\`\``
|
||||
|
||||
const html = await renderContent(markdown)
|
||||
|
||||
// Should encode newlines properly
|
||||
expect(html).toContain('This%20is%20line%201%0AThis%20is%20line%202')
|
||||
})
|
||||
})
|
||||
})
|
||||
11
src/content-render/tests/prompt.js
Normal file
11
src/content-render/tests/prompt.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { describe, expect, test } from 'vitest'
|
||||
import { renderContent } from '@/content-render/index'
|
||||
|
||||
describe('prompt tag', () => {
|
||||
test('wraps content in <code> and appends svg', async () => {
|
||||
const input = 'Here is your prompt: {% prompt %}example prompt text{% endprompt %}.'
|
||||
const output = await renderContent(input)
|
||||
expect(output).toContain('<code>example prompt text</code><a')
|
||||
expect(output).toContain('<svg')
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,6 @@
|
||||
/**
|
||||
* Adds a bar above code blocks that shows the language and a copy button
|
||||
* Adds a bar above code blocks that shows the language and a copy button.
|
||||
* Optionally, adds a prompt button to Copilot Chat blocks.
|
||||
*/
|
||||
|
||||
import yaml from 'js-yaml'
|
||||
@@ -10,34 +11,43 @@ import octicons from '@primer/octicons'
|
||||
import { parse } from 'parse5'
|
||||
import { fromParse5 } from 'hast-util-from-parse5'
|
||||
import murmur from 'imurmurhash'
|
||||
import { getPrompt } from './copilot-prompt'
|
||||
|
||||
const languages = yaml.load(fs.readFileSync('./data/code-languages.yml', 'utf8'))
|
||||
|
||||
const matcher = (node) =>
|
||||
node.type === 'element' &&
|
||||
node.tagName === 'pre' &&
|
||||
// For now, limit to ones with the copy meta,
|
||||
// For now, limit to ones with the copy or prompt meta,
|
||||
// but we may enable for all examples later.
|
||||
getPreMeta(node).copy &&
|
||||
(getPreMeta(node).copy || getPreMeta(node).prompt) &&
|
||||
// Don't add this header for annotated examples.
|
||||
!getPreMeta(node).annotate
|
||||
|
||||
export default function codeHeader() {
|
||||
return (tree) => {
|
||||
visit(tree, matcher, (node, index, parent) => {
|
||||
parent.children[index] = wrapCodeExample(node)
|
||||
parent.children[index] = wrapCodeExample(node, tree)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function wrapCodeExample(node) {
|
||||
function wrapCodeExample(node, tree) {
|
||||
const lang = node.children[0].properties.className?.[0].replace('language-', '')
|
||||
const code = node.children[0].children[0].value
|
||||
return h('div', { className: 'code-example' }, [header(lang, code), node])
|
||||
|
||||
const subnav = null // getSubnav() lives in annotate.js, not needed for normal code blocks
|
||||
const prompt = getPrompt(node, tree, code) // returns null if there's no prompt
|
||||
const hasCopy = Boolean(getPreMeta(node).copy) // defaults to true
|
||||
|
||||
const headerHast = header(lang, code, subnav, prompt, hasCopy)
|
||||
|
||||
return h('div', { className: 'code-example' }, [headerHast, node])
|
||||
}
|
||||
|
||||
export function header(lang, code, subnav) {
|
||||
export function header(lang, code, subnav = null, prompt = null, hasCopy = true) {
|
||||
const codeId = murmur('js-btn-copy').hash(code).result()
|
||||
|
||||
return h(
|
||||
'header',
|
||||
{
|
||||
@@ -56,7 +66,9 @@ export function header(lang, code, subnav) {
|
||||
[
|
||||
h('span', { className: 'flex-1' }, languages[lang]?.name),
|
||||
subnav,
|
||||
h(
|
||||
prompt,
|
||||
hasCopy
|
||||
? h(
|
||||
'button',
|
||||
{
|
||||
class: ['js-btn-copy', 'btn', 'btn-sm', 'tooltipped', 'tooltipped-nw'],
|
||||
@@ -64,7 +76,8 @@ export function header(lang, code, subnav) {
|
||||
'data-clipboard': codeId,
|
||||
},
|
||||
btnIcon(),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
h('pre', { hidden: true, 'data-clipboard': codeId }, code),
|
||||
],
|
||||
)
|
||||
@@ -77,7 +90,7 @@ function btnIcon() {
|
||||
return btnIcon
|
||||
}
|
||||
|
||||
function getPreMeta(node) {
|
||||
export function getPreMeta(node) {
|
||||
// Here's why this monstrosity works:
|
||||
// https://github.com/syntax-tree/mdast-util-to-hast/blob/c87cd606731c88a27dbce4bfeaab913a9589bf83/lib/handlers/code.js#L40-L42
|
||||
return node.children[0]?.data?.meta || {}
|
||||
|
||||
75
src/content-render/unified/copilot-prompt.js
Normal file
75
src/content-render/unified/copilot-prompt.js
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Adds a runnable prompt button in the header of Copilot Chat blocks.
|
||||
*/
|
||||
|
||||
import { find } from 'unist-util-find'
|
||||
import { h } from 'hastscript'
|
||||
import octicons from '@primer/octicons'
|
||||
import { parse } from 'parse5'
|
||||
import { fromParse5 } from 'hast-util-from-parse5'
|
||||
import { getPreMeta } from './code-header'
|
||||
|
||||
export function getPrompt(node, tree, code) {
|
||||
const hasPrompt = Boolean(getPreMeta(node).prompt)
|
||||
if (!hasPrompt) return null
|
||||
|
||||
const { promptContent, ariaLabel } = buildPromptData(node, tree, code)
|
||||
const promptLink = `https://github.com/copilot?prompt=${encodeURIComponent(promptContent.trim())}`
|
||||
|
||||
return h(
|
||||
'a',
|
||||
{
|
||||
href: promptLink,
|
||||
target: '_blank',
|
||||
class: ['btn', 'btn-sm', 'mr-1', 'tooltipped', 'tooltipped-nw', 'no-underline'],
|
||||
'aria-label': ariaLabel,
|
||||
},
|
||||
copilotIcon(),
|
||||
)
|
||||
}
|
||||
|
||||
function buildPromptData(node, tree, code) {
|
||||
// Find a ref meta in the format 'ref=<id>'
|
||||
const ref = getPreMeta(node).ref
|
||||
|
||||
if (!ref) {
|
||||
// If no 'ref=<id>' meta is found, use just the current code for the prompt link.
|
||||
return promptOnly(code)
|
||||
}
|
||||
|
||||
// If the 'ref=<id>' meta is found, find a matching code block to include as context in the prompt link.
|
||||
const matchingCodeEl = findMatchingCode(ref, tree)
|
||||
if (!matchingCodeEl) {
|
||||
console.warn(`Can't find referenced code block with id=${ref}`)
|
||||
return promptOnly(code)
|
||||
}
|
||||
const matchingCode = matchingCodeEl?.children[0].children[0].value || null
|
||||
return promptAndContext(code, matchingCode)
|
||||
}
|
||||
|
||||
function promptOnly(code) {
|
||||
return {
|
||||
promptContent: code,
|
||||
ariaLabel: 'Run this prompt in Copilot Chat',
|
||||
}
|
||||
}
|
||||
|
||||
function promptAndContext(code, matchingCode) {
|
||||
return {
|
||||
promptContent: `${matchingCode}\n${code}`,
|
||||
ariaLabel: 'Run this prompt with context in Copilot Chat',
|
||||
}
|
||||
}
|
||||
|
||||
function findMatchingCode(ref, tree) {
|
||||
return find(tree, (node) => {
|
||||
return node.type === 'element' && node.tagName === 'pre' && getPreMeta(node).id === ref
|
||||
})
|
||||
}
|
||||
|
||||
function copilotIcon() {
|
||||
const copilotIconHtml = octicons.copilot.toSVG()
|
||||
const copilotIconAst = parse(String(copilotIconHtml), { sourceCodeLocationInfo: true })
|
||||
const copilotIcon = fromParse5(copilotIconAst, { file: copilotIconHtml })
|
||||
return copilotIcon
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
// becomes...
|
||||
// node.lang = javascript
|
||||
// node.meta = { lineNumbers: 'left', copy: 'all', annotate: true }
|
||||
// Also parse equals signs, where id=some-id becomes { id: 'some-id' }
|
||||
|
||||
import { visit } from 'unist-util-visit'
|
||||
|
||||
@@ -25,7 +26,7 @@ function strToObj(str) {
|
||||
return Object.fromEntries(
|
||||
str
|
||||
.split(/\s+/g)
|
||||
.map((k) => k.split(':'))
|
||||
.map((k) => k.split(/[:=]/)) // split by colon or equals sign
|
||||
.map(([k, ...v]) => [k, v.length ? v.join(':') : true]),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ export function createProcessor(context: Context): UnifiedProcessor {
|
||||
subset: false,
|
||||
aliases: {
|
||||
// As of Jan 2024, 'jsonc' is not supported by highlight.js. It
|
||||
// just because plain text.
|
||||
// just becomes plain text.
|
||||
// But 'jsonc' works great in github.com. For example, when
|
||||
// previewing and edited .md content in the browser. Or viewing
|
||||
// PR diffs in web view.
|
||||
@@ -66,6 +66,9 @@ export function createProcessor(context: Context): UnifiedProcessor {
|
||||
// but with this alias you get the nice syntax highlighting when
|
||||
// viewed on our site.
|
||||
json: 'jsonc',
|
||||
// Docs supports a custom 'copilot' language, which is useful for contributors,
|
||||
// but is not a supported highlight.js language, so alias to 'text'.
|
||||
text: 'copilot',
|
||||
},
|
||||
})
|
||||
.use(raw)
|
||||
|
||||
Reference in New Issue
Block a user