Skip to content

Commit 5933525

Browse files
authored
Support AUTOTITLE in code annotation blocks (#56214)
1 parent 549689c commit 5933525

File tree

6 files changed

+135
-56
lines changed

6 files changed

+135
-56
lines changed

src/content-render/tests/__snapshots__/annotate.js.snap

Lines changed: 0 additions & 24 deletions
This file was deleted.

src/content-render/tests/annotate.js

Lines changed: 91 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,50 @@ on:
1818

1919
describe('annotate', () => {
2020
test('renders annotations', async () => {
21-
// We don't normally use snapshots,
22-
// but in this case its a short and concise example
23-
// that won't change regularly.
24-
// If it fails, study the output and make sure it's correct.
25-
// If it is indeed correct, run `vitest --updateSnapshot` to update it.
26-
expect(await renderContent(example)).toMatchSnapshot()
21+
const res = await renderContent(example)
22+
const $ = cheerio.load(res)
23+
24+
// Check that the annotation structure is rendered correctly
25+
const annotation = $('.annotate')
26+
expect(annotation.length).toBe(1)
27+
expect(annotation.hasClass('beside')).toBe(true)
28+
29+
// Check annotation header exists
30+
const header = $('.annotate-header')
31+
expect(header.length).toBe(1)
32+
33+
// Check both beside and inline modes are rendered
34+
const beside = $('.annotate-beside')
35+
const inline = $('.annotate-inline')
36+
expect(beside.length).toBe(1)
37+
expect(inline.length).toBe(1)
38+
39+
// Check that we have the correct number of annotation rows
40+
const rows = $('.annotate-row')
41+
expect(rows.length).toBe(2)
42+
43+
// Check that each row has both code and note sections
44+
rows.each((i, row) => {
45+
const $row = $(row)
46+
expect($row.find('.annotate-code').length).toBe(1)
47+
expect($row.find('.annotate-note').length).toBe(1)
48+
})
49+
50+
// Check specific content of the annotations
51+
const notes = $('.annotate-note p')
52+
const noteTexts = notes.map((i, el) => $(el).text()).get()
53+
expect(noteTexts).toEqual([
54+
'The name of the workflow as it will appear in the "Actions" tab of the GitHub repository.',
55+
'Add the pull_request event, so that the workflow runs automatically\nevery time a pull request is created.',
56+
])
57+
58+
// Check code content
59+
const codes = $('.annotate-code pre')
60+
const codeTexts = codes.map((i, el) => $(el).text()).get()
61+
expect(codeTexts).toEqual([
62+
'name: Post welcome comment',
63+
'on:\n pull_request:\n types: [opened]',
64+
])
2765
})
2866

2967
test('renders bash with hash bang annotations', async () => {
@@ -63,6 +101,7 @@ on:
63101
64102
\`\`\`
65103
`.trim()
104+
66105
const res = await renderContent(example)
67106
const $ = cheerio.load(res)
68107

@@ -79,4 +118,50 @@ on:
79118
"on:\n push:\n branches: ['release']",
80119
])
81120
})
121+
122+
test('supports AUTOTITLE links in annotations', async () => {
123+
const example = `
124+
\`\`\`yaml annotate copy
125+
# For more information about workflow syntax, see [AUTOTITLE](/get-started/start-your-journey/hello-world).
126+
name: Test workflow
127+
128+
# This uses the checkout action. See [AUTOTITLE](/get-started/foo) for details.
129+
on: [push]
130+
\`\`\`
131+
`
132+
133+
// Create a mock context with pages for AUTOTITLE resolution
134+
const mockPages = {
135+
'/get-started/start-your-journey/hello-world': {
136+
href: '/get-started/start-your-journey/hello-world',
137+
rawTitle: 'Hello World',
138+
},
139+
'/get-started/foo': {
140+
href: '/get-started/foo',
141+
rawTitle: 'Fooing Around',
142+
},
143+
}
144+
145+
const mockContext = {
146+
currentLanguage: 'en',
147+
currentVersion: 'free-pro-team@latest',
148+
pages: mockPages,
149+
redirects: {},
150+
}
151+
152+
const res = await renderContent(example, mockContext)
153+
const $ = cheerio.load(res)
154+
155+
const rows = $('.annotate-row')
156+
const notes = $('.annotate-note', rows)
157+
158+
// Check that AUTOTITLE links were resolved to actual titles
159+
const firstNote = notes.eq(0).html()
160+
const secondNote = notes.eq(1).html()
161+
162+
expect(firstNote).toContain('Hello World')
163+
expect(firstNote).not.toContain('[AUTOTITLE]')
164+
expect(secondNote).toContain('Fooing Around')
165+
expect(secondNote).not.toContain('[AUTOTITLE]')
166+
})
82167
})

src/content-render/unified/annotate.js

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { h } from 'hastscript'
3636
import { fromMarkdown } from 'mdast-util-from-markdown'
3737
import { toHast } from 'mdast-util-to-hast'
3838
import { header } from './code-header.js'
39+
import findPage from '#src/frame/lib/find-page.js'
3940

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

@@ -66,15 +67,15 @@ const commentRegexes = {
6667
const matcher = (node) =>
6768
node.type === 'element' && node.tagName === 'pre' && getPreMeta(node).annotate
6869

69-
export default function annotate() {
70+
export default function annotate(context) {
7071
return (tree) => {
7172
visit(tree, matcher, (node, index, parent) => {
72-
parent.children[index] = createAnnotatedNode(node)
73+
parent.children[index] = createAnnotatedNode(node, context)
7374
})
7475
}
7576
}
7677

77-
function createAnnotatedNode(node) {
78+
function createAnnotatedNode(node, context) {
7879
const lang = node.children[0].properties.className[0].replace('language-', '')
7980
const code = node.children[0].children[0].value
8081

@@ -98,7 +99,7 @@ function createAnnotatedNode(node) {
9899
}
99100

100101
// Render the HTML
101-
return template({ lang, code, rows })
102+
return template({ lang, code, rows, context })
102103
}
103104

104105
function validate(lang, code) {
@@ -178,7 +179,7 @@ function getSubnav() {
178179
return h('div', { className: 'annotate-toggle' }, [besideBtn, inlineBtn])
179180
}
180181

181-
function template({ lang, code, rows }) {
182+
function template({ lang, code, rows, context }) {
182183
return h(
183184
'div',
184185
{ class: 'annotate beside' },
@@ -197,7 +198,7 @@ function template({ lang, code, rows }) {
197198
h(
198199
'div',
199200
{ className: 'annotate-note' },
200-
mdToHast(note.map(removeComment(lang)).join('\n')),
201+
mdToHast(note.map(removeComment(lang)).join('\n'), context),
201202
),
202203
]),
203204
),
@@ -209,8 +210,39 @@ function template({ lang, code, rows }) {
209210
)
210211
}
211212

212-
function mdToHast(text) {
213-
return toHast(fromMarkdown(text))
213+
function mdToHast(text, context) {
214+
const mdast = fromMarkdown(text)
215+
216+
// Process AUTOTITLE links if context is available
217+
if (context) {
218+
processAutotitleInMdast(mdast, context)
219+
}
220+
221+
return toHast(mdast)
222+
}
223+
224+
// Helper method to process AUTOTITLE links in MDAST
225+
// This can be reused for other MDAST processing that needs AUTOTITLE support
226+
function processAutotitleInMdast(mdast, context) {
227+
visit(mdast, 'link', (node) => {
228+
if (node.url && node.url.startsWith('/')) {
229+
for (const child of node.children) {
230+
if (child.type === 'text' && /^\s*AUTOTITLE\s*$/.test(child.value)) {
231+
// Find the page and get its title
232+
const page = findPage(node.url, context.pages, context.redirects)
233+
if (page) {
234+
try {
235+
// Use rawTitle for synchronous processing in annotations
236+
child.value = page.rawTitle || 'AUTOTITLE'
237+
} catch (error) {
238+
// Keep AUTOTITLE if we can't get the title
239+
console.warn(`Could not resolve AUTOTITLE for ${node.url}:`, error.message)
240+
}
241+
}
242+
}
243+
}
244+
}
245+
})
214246
}
215247

216248
function removeComment(lang) {

src/content-render/unified/processor.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export function createProcessor(context) {
4747
.use(useEnglishHeadings, context)
4848
.use(headingLinks)
4949
.use(codeHeader)
50-
.use(annotate)
50+
.use(annotate, context)
5151
.use(highlight, {
5252
languages: { ...common, graphql, dockerfile, http, groovy, erb, powershell },
5353
subset: false,

src/fixtures/fixtures/content/get-started/foo/autotitling.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@ Or, a combination of query string and hash:
2929
// This is a code sample
3030
console.log("Hello, World!");
3131

32-
// for more info on this, visit [AUTOTITLE](/get-started/markdown).
32+
// for more info on this, visit the documentation.
3333
function greet(name: string): void {
3434
console.log(`Hello, ${name}!`);
3535
}
3636

37-
// another example is [AUTOTITLE](/get-started/markdown/alerts)
37+
// another example is using TypeScript types
3838
const userName: string = "TypeScript User";
3939
greet(userName);
4040
```
@@ -47,7 +47,7 @@ const { Octokit } = require("octokit");
4747

4848
//
4949
async function checkAndRedeliverWebhooks() {
50-
// See [AUTOTITLE](/get-started/markdown/permissions)
50+
// See the API documentation for permissions
5151
const TOKEN = process.env.TOKEN;
5252
const ORGANIZATION_NAME = process.env.ORGANIZATION_NAME;
5353
const HOOK_ID = process.env.HOOK_ID;

src/fixtures/tests/internal-links.js

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,6 @@ describe('autotitle', () => {
1717
expect.assertions(4)
1818
})
1919

20-
// skipped because autotitles aren't supported in annotated code blocks yet
21-
// see docs-engineering#3691
22-
test.skip('internal links in codeblocks with AUTOTITLE resolves', async () => {
23-
const $ = await getDOM('/get-started/foo/autotitling')
24-
const links = $('#article-contents a[href]')
25-
links.each((i, element) => {
26-
if ($(element).attr('href').includes('/get-started/markdown')) {
27-
expect($(element).text()).toContain('Markdown')
28-
}
29-
})
30-
// There are 2 links on the `autotitling.md` content.
31-
expect.assertions(2)
32-
})
33-
3420
test('typos lead to error when NODE_ENV !== production', async () => {
3521
// The fixture typo-autotitling.md contains two different typos
3622
// of the word "AUTOTITLE", separated by `{% if version ghes %}`

0 commit comments

Comments
 (0)