Inline documentation links in type excerpts (#3931)

Before: 
<img width="667" alt="Screenshot 2024-06-12 at 15 54 38"
src="https://github.com/tldraw/tldraw/assets/1489520/3a5fc43c-fa2e-4b08-8e8b-c1c66decf7fa">

After: 
<img width="654" alt="Screenshot 2024-06-12 at 15 55 10"
src="https://github.com/tldraw/tldraw/assets/1489520/8c8abcaa-f156-4be4-a5e9-d1a4eff39ff4">

Previously, when items in our documentation referred to each other in
code snippets, we'd put the links to their documentation pages in a
separate "references" section at the bottom of the docs. Generally I
find that this makes links harder to find (they're not in-context) and
adds a fair bit of visual noise to our API documentation.

This diff moves those links inline by adding a post-processing step to
our highlighted code. This is slightly more involved than I wanted it to
be (see the comments in code.tsx for an explanation of why) but it gets
the job done. I've added small link icons next to linked code items - i
experimented with underlines and a 🔗 icon too, but this seemed to look
the best.

### Change Type

- [x] `docs` — Changes to the documentation, examples, or templates.
- [x] `improvement` — Improving existing features
This commit is contained in:
alex 2024-06-13 14:47:13 +01:00 committed by GitHub
parent 6cb797a074
commit 012e54959d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 287 additions and 52 deletions

View file

@ -22,7 +22,6 @@ import {
ApiTypeAlias,
ApiVariable,
Excerpt,
ExcerptToken,
ReleaseTag,
} from '@microsoft/api-extractor-model'
import { MarkdownWriter, formatWithPrettier, getPath, getSlug } from '../utils'
@ -118,9 +117,13 @@ export async function getApiMarkdown(
componentProps instanceof ApiDeclaredItem &&
componentProps?.kind !== 'Interface'
) {
propertiesResult.markdown += await typeExcerptToMarkdown(componentProps.excerpt, {
kind: componentProps.kind,
})
propertiesResult.markdown += await excerptToMarkdown(
componentProps,
componentProps.excerpt,
{
kind: componentProps.kind,
}
)
}
if (propertiesResult.markdown.trim()) {
addMarkdown(toc, `- [Properties](#properties)\n`)
@ -153,7 +156,6 @@ export async function getApiMarkdown(
await addDocComment(model, result, item)
addReferences(model, result, item)
addLinkToSource(result, item)
if (membersResult.markdown.length) {
@ -180,7 +182,6 @@ async function addMarkdownForMember(
addMemberName(result, member)
addTags(model, result, member, { isComponentProp })
await addDocComment(model, result, member, { isComponentProp })
addReferences(model, result, member)
}
async function addFrontmatter(
@ -282,7 +283,7 @@ async function addDocComment(
member instanceof ApiMethod
) {
if (!isComponentProp) result.markdown += `<ApiHeading>Signature</ApiHeading>\n\n`
result.markdown += await typeExcerptToMarkdown(member.excerpt, {
result.markdown += await excerptToMarkdown(member, member.excerpt, {
kind: member.kind,
})
result.markdown += `\n\n`
@ -308,7 +309,7 @@ async function addDocComment(
result.markdown += `\`${param.name}\`\n\n`
result.markdown += `</ParametersTableName>\n`
result.markdown += `<ParametersTableDescription>\n\n`
result.markdown += await typeExcerptToMarkdown(param.parameterTypeExcerpt, {
result.markdown += await excerptToMarkdown(member, param.parameterTypeExcerpt, {
kind: 'ParameterType',
printWidth: 60,
})
@ -327,7 +328,7 @@ async function addDocComment(
if (!(member instanceof ApiConstructor)) {
result.markdown += `<ApiHeading>Returns</ApiHeading>\n\n`
result.markdown += await typeExcerptToMarkdown(member.returnTypeExcerpt, {
result.markdown += await excerptToMarkdown(member, member.returnTypeExcerpt, {
kind: 'ReturnType',
})
result.markdown += `\n\n`
@ -369,13 +370,28 @@ async function addDocComment(
}
}
async function typeExcerptToMarkdown(
async function excerptToMarkdown(
item: ApiItem,
excerpt: Excerpt,
{ kind, printWidth }: { kind: ApiItemKind | 'ReturnType' | 'ParameterType'; printWidth?: number }
) {
const links = {} as Record<string, string>
let code = ''
for (const token of excerpt.spannedTokens) {
code += token.text
if (!token.canonicalReference) continue
const apiItemResult = item
.getAssociatedModel()!
.resolveDeclarationReference(token.canonicalReference!, item)
if (apiItemResult.errorMessage) continue
const apiItem = apiItemResult.resolvedApiItem!
const url = `/reference/${getPath(apiItem)}`
links[token.text] = url
}
code = code.replace(/^export /, '')
@ -425,7 +441,15 @@ async function typeExcerptToMarkdown(
throw Error()
}
return ['```ts', code, '```'].join('\n')
return [
`<CodeLinkProvider links={${JSON.stringify(links)}}>`,
'',
'```ts',
code,
'```',
'',
'</CodeLinkProvider>',
].join('\n')
}
function addTags(
@ -456,40 +480,6 @@ function addTags(
result.markdown += `<Small>${tags.filter((t) => t.toLowerCase() !== 'none').join(' ')}</Small>\n\n`
}
function addReferences(model: TldrawApiModel, result: Result, member: ApiItem) {
if (!(member instanceof ApiDeclaredItem)) return
const references = new Set<string>()
function addToken(item: ApiDeclaredItem, token: ExcerptToken) {
if (token.kind !== 'Reference') return
const apiItemResult = item
.getAssociatedModel()!
.resolveDeclarationReference(token.canonicalReference!, item)
if (apiItemResult.errorMessage) {
return
}
const apiItem = apiItemResult.resolvedApiItem!
const url = `/reference/${getPath(apiItem)}`
references.add(`[${token.text}](${url})`)
}
member.excerptTokens.forEach((token) => {
addToken(member, token)
})
const componentProps = model.isComponent(member) ? model.getReactPropsItem(member) : null
if (componentProps && componentProps instanceof ApiDeclaredItem) {
componentProps.excerptTokens.forEach((token) => {
addToken(componentProps, token)
})
}
if (references.size) {
result.markdown += `<ApiHeading>References</ApiHeading>\n\n`
result.markdown += Array.from(references).join(', ') + '\n\n'
}
}
function addExtends(result: Result, item: ApiItem) {
const extendsTypes =
item instanceof ApiClass && item.extendsType
@ -500,7 +490,30 @@ function addExtends(result: Result, item: ApiItem) {
if (!extendsTypes.length) return
result.markdown += `Extends \`${extendsTypes.map((type) => type.excerpt.text).join(', ')}\`.\n\n`
const links = {} as Record<string, string>
for (const type of extendsTypes) {
for (const token of type.excerpt.spannedTokens) {
if (!token.canonicalReference) continue
const apiItemResult = item
.getAssociatedModel()!
.resolveDeclarationReference(token.canonicalReference!, item)
if (apiItemResult.errorMessage) continue
const apiItem = apiItemResult.resolvedApiItem!
links[token.text] = `/reference/${getPath(apiItem)}`
}
}
result.markdown += [
`<CodeLinkProvider links={${JSON.stringify(links)}}>`,
'',
`Extends \`${extendsTypes.map((type) => type.excerpt.text).join(', ')}\`.`,
'',
'</CodeLinkProvider>',
'',
].join('\n')
}
function addLinkToSource(result: Result, member: ApiItem) {