Generated docs cleanup (#3935)

Our generated docs are pretty verbose and space inefficient. This diff
has a few design tweaks to try and make sure that the information that's
emphasised is the stuff that's most important, and makes the typical
docs item use a bit less space in the process.


![image](https://github.com/tldraw/tldraw/assets/1489520/df433ae0-1400-4f5b-951e-e25869621a40)



### 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 17:04:12 +01:00 committed by GitHub
parent 012e54959d
commit fba82ed924
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 219 additions and 82 deletions

View file

@ -5,6 +5,7 @@ import { Breadcrumb } from './Breadcrumb'
import { Header } from './Header'
import { Mdx } from './Mdx'
import { Sidebar } from './Sidebar'
import { TitleWithSourceLink } from './mdx-components/api-docs'
import { Image } from './mdx-components/generic'
export async function ArticleReferenceDocsPage({ article }: { article: Article }) {
@ -25,7 +26,9 @@ export async function ArticleReferenceDocsPage({ article }: { article: Article }
<main className="main-content article article__api-docs">
<div className="page-header">
<Breadcrumb section={section} category={category} />
<h1>{article.title}</h1>
<TitleWithSourceLink source={article.sourceUrl} large tags={article.apiTags?.split(',')}>
<h1>{article.title}</h1>
</TitleWithSourceLink>
</div>
{article.hero && <Image alt="hero" title={article.title} src={`images/${article.hero}`} />}
{article.content && <Mdx content={article.content} />}

View file

@ -1,4 +1,6 @@
import classNames from 'classnames'
import { ReactNode } from 'react'
import { Icon } from '../Icon'
export function ParametersTable({ children }: { children: ReactNode }) {
return (
@ -27,3 +29,38 @@ export function ParametersTableName({ children }: { children: ReactNode }) {
export function ParametersTableDescription({ children }: { children: ReactNode }) {
return <td className="article__parameters-table__description">{children}</td>
}
export function TitleWithSourceLink({
children,
source,
large,
tags,
}: {
children: ReactNode
source?: string | null
large?: boolean
tags?: string[]
}) {
return (
<div
className={classNames(
'article__title-with-source-link',
large && 'article__title-with-source-link--large'
)}
>
{children}
<div className="article__title-with-source-link__meta">
{tags?.map((tag) => <Tag key={tag}>{tag}</Tag>)}
{source && (
<a href={source} target="_blank" rel="noopener noreferrer" title="Source code">
<Icon icon="code" />
</a>
)}
</div>
</div>
)
}
export function Tag({ children }: { children: string }) {
return <span className={classNames(`article__tag`, `article__tag--${children}`)}>{children}</span>
}

View file

@ -0,0 +1,5 @@
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.9285 5.37141C20.1336 4.85863 19.8842 4.27666 19.3714 4.07155C18.8586 3.86643 18.2766 4.11585 18.0715 4.62863L10.0715 24.6286C9.86641 25.1414 10.1158 25.7234 10.6286 25.9285C11.1414 26.1336 11.7234 25.8842 11.9285 25.3714L19.9285 5.37141Z" fill="black"/>
<path d="M7.70711 10.2929C8.09763 10.6834 8.09763 11.3166 7.70711 11.7071L4.41421 15L7.70711 18.2929C8.09763 18.6834 8.09763 19.3166 7.70711 19.7071C7.31658 20.0977 6.68342 20.0977 6.29289 19.7071L2.29289 15.7071C1.90237 15.3166 1.90237 14.6834 2.29289 14.2929L6.29289 10.2929C6.68342 9.90239 7.31658 9.90239 7.70711 10.2929Z" fill="black"/>
<path d="M22.2929 10.2929C22.6834 9.90239 23.3166 9.90239 23.7071 10.2929L27.7071 14.2929C28.0976 14.6834 28.0976 15.3166 27.7071 15.7071L23.7071 19.7071C23.3166 20.0977 22.6834 20.0977 22.2929 19.7071C21.9024 19.3166 21.9024 18.6834 22.2929 18.2929L25.5858 15L22.2929 11.7071C21.9024 11.3166 21.9024 10.6834 22.2929 10.2929Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -72,6 +72,7 @@ export async function connect(opts: { reset?: boolean; mode: 'readonly' | 'readw
componentCode TEXT,
componentCodeFiles TEXT,
keywords TEXT,
apiTags TEXT,
content TEXT NOT NULL,
path TEXT,
FOREIGN KEY (authorId) REFERENCES authors(id),

View file

@ -118,6 +118,7 @@ export function generateSection(section: InputSection, articles: Articles, index
: isUncategorized
? `/${section.id}/${articleId}`
: `/${section.id}/${categoryId}/${articleId}`,
apiTags: parsed.data.apiTags ?? null,
}
if (isExamples) {

View file

@ -14,15 +14,14 @@ import {
ApiMethod,
ApiMethodSignature,
ApiNamespace,
ApiOptionalMixin,
ApiProperty,
ApiPropertySignature,
ApiReadonlyMixin,
ApiReleaseTagMixin,
ApiStaticMixin,
ApiTypeAlias,
ApiVariable,
Excerpt,
ReleaseTag,
} from '@microsoft/api-extractor-model'
import { MarkdownWriter, formatWithPrettier, getPath, getSlug } from '../utils'
@ -31,8 +30,6 @@ interface Result {
keywords: string[]
}
const REPO_URL = 'https://github.com/tldraw/tldraw/blob/main/'
const date = new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: '2-digit',
@ -101,8 +98,7 @@ export async function getApiMarkdown(
addMarkdown(membersResult, constructorResult.markdown)
}
if (properties.length || componentProps) {
addMarkdown(propertiesResult, `## Properties\n\n`)
if (properties.length || isComponent) {
if (componentProps) addExtends(propertiesResult, componentProps)
for (const member of properties) {
const slug = getSlug(member)
@ -125,9 +121,14 @@ export async function getApiMarkdown(
}
)
}
if (propertiesResult.markdown.trim()) {
addMarkdown(toc, `- [Properties](#properties)\n`)
addMarkdown(membersResult, `## Properties\n\n`)
addMarkdown(membersResult, propertiesResult.markdown)
} else if (isComponent && !componentProps) {
addMarkdown(membersResult, `## Properties\n\n`)
addMarkdown(membersResult, `This component does not take any props.\n\n`)
}
}
@ -152,12 +153,8 @@ export async function getApiMarkdown(
result.markdown += `</details>\n\n`
}
addTags(model, result, item)
await addDocComment(model, result, item)
addLinkToSource(result, item)
if (membersResult.markdown.length) {
addHorizontalRule(result)
addMarkdown(result, membersResult.markdown)
@ -179,9 +176,8 @@ async function addMarkdownForMember(
{ isComponentProp = false } = {}
) {
if (member.displayName.startsWith('_')) return
addMemberName(result, member)
addTags(model, result, member, { isComponentProp })
await addDocComment(model, result, member, { isComponentProp })
addMemberNameAndMeta(result, model, member, { isComponentProp })
await addDocComment(model, result, member)
}
async function addFrontmatter(
@ -211,40 +207,71 @@ async function addFrontmatter(
}
}
result.markdown += `---
title: ${member.displayName}
status: published
description: ${description}
category: ${categoryName}
group: ${model.isComponent(member) ? APIGroup.Component : member.kind}
author: api
date: ${date}
order: ${order}
sourceUrl: ${'_fileUrlPath' in member ? member._fileUrlPath : ''}${kw}
---
`
const frontmatter: Record<string, string> = {
title: member.displayName,
status: 'published',
description,
category: categoryName,
group: model.isComponent(member) ? APIGroup.Component : member.kind,
date,
order: order.toString(),
apiTags: getTags(model, member).join(','),
}
if (member instanceof ApiDeclaredItem && member.sourceLocation.fileUrl) {
frontmatter.sourceUrl = member.sourceLocation.fileUrl
}
result.markdown += [
'---',
...Object.entries(frontmatter).map(([key, value]) => `${key}: ${value}`),
kw,
'---',
'',
].join('\n')
}
function addHorizontalRule(result: Result) {
result.markdown += `---\n\n`
}
function addMemberName(result: Result, member: ApiItem) {
if (member.kind === 'Constructor') {
result.markdown += `### Constructor\n\n`
return
function getItemTitle(item: ApiItem) {
if (item.kind === ApiItemKind.Constructor) {
return 'Constructor'
}
if (!member.displayName) return
result.markdown += `### \`${member.displayName}${member.kind === 'Method' ? '()' : ''}\`\n\n`
const name = item.displayName
if (item.kind === ApiItemKind.Method || item.kind === ApiItemKind.Function) {
return `${name}()`
}
return name
}
function addMemberNameAndMeta(
result: Result,
model: TldrawApiModel,
item: ApiItem,
{ level = 3, isComponentProp = false } = {}
) {
const heading = `${'#'.repeat(level)} ${getItemTitle(item)}`
if (item instanceof ApiDeclaredItem && item.sourceLocation.fileUrl) {
const source = item.sourceLocation.fileUrl
const tags = getTags(model, item, { isComponentProp, includeKind: false })
result.markdown += [
`<TitleWithSourceLink source={${JSON.stringify(source)}} tags={${JSON.stringify(tags)}}>`,
'',
heading,
'',
'</TitleWithSourceLink>',
'',
].join('\n')
} else {
result.markdown += `${heading}\n\n`
}
}
async function addDocComment(
model: TldrawApiModel,
result: Result,
member: ApiItem,
{ isComponentProp = false } = {}
) {
async function addDocComment(model: TldrawApiModel, result: Result, member: ApiItem) {
if (!(member instanceof ApiDocumentedItem)) {
return
}
@ -256,7 +283,28 @@ async function addDocComment(
member,
member.tsdocComment.summarySection
)
}
if (
!isComponent &&
(member instanceof ApiVariable ||
member instanceof ApiTypeAlias ||
member instanceof ApiProperty ||
member instanceof ApiPropertySignature ||
member instanceof ApiClass ||
member instanceof ApiFunction ||
member instanceof ApiInterface ||
member instanceof ApiEnum ||
member instanceof ApiNamespace ||
member instanceof ApiMethod)
) {
result.markdown += await excerptToMarkdown(member, member.excerpt, {
kind: member.kind,
})
result.markdown += `\n\n`
}
if (member.tsdocComment) {
const exampleBlocks = member.tsdocComment.customBlocks.filter(
(block) => block.blockTag.tagNameWithUpperCase === '@EXAMPLE'
)
@ -270,25 +318,6 @@ async function addDocComment(
}
}
if (
member instanceof ApiVariable ||
member instanceof ApiTypeAlias ||
member instanceof ApiProperty ||
member instanceof ApiPropertySignature ||
member instanceof ApiClass ||
member instanceof ApiFunction ||
member instanceof ApiInterface ||
member instanceof ApiEnum ||
member instanceof ApiNamespace ||
member instanceof ApiMethod
) {
if (!isComponentProp) result.markdown += `<ApiHeading>Signature</ApiHeading>\n\n`
result.markdown += await excerptToMarkdown(member, member.excerpt, {
kind: member.kind,
})
result.markdown += `\n\n`
}
if (isComponent) return
if (
@ -452,32 +481,37 @@ async function excerptToMarkdown(
].join('\n')
}
function addTags(
function getTags(
model: TldrawApiModel,
result: Result,
member: ApiItem,
{ isComponentProp = false } = {}
{ isComponentProp = false, includeKind = true } = {}
) {
const tags = []
if (!isComponentProp) {
if (ApiReleaseTagMixin.isBaseClassOf(member)) {
tags.push(ReleaseTag[member.releaseTag])
}
if (ApiStaticMixin.isBaseClassOf(member) && member.isStatic) {
tags.push('static')
}
if (ApiReadonlyMixin.isBaseClassOf(member) && member.isReadonly) {
if (ApiStaticMixin.isBaseClassOf(member) && member.isStatic) {
tags.push('static')
}
let kind = member.kind.toLowerCase()
if (ApiReadonlyMixin.isBaseClassOf(member) && member.isReadonly) {
if (member.kind === ApiItemKind.Variable) {
kind = 'constant'
} else if (!isComponentProp) {
tags.push('readonly')
}
}
if (member instanceof ApiPropertySignature && member.isOptional) {
if (model.isComponent(member)) {
kind = 'component'
}
if (ApiOptionalMixin.isBaseClassOf(member) && member.isOptional) {
tags.push('optional')
}
if (!isComponentProp) {
const kind = model.isComponent(member) ? 'component' : member.kind.toLowerCase()
if (includeKind) {
tags.push(kind)
}
result.markdown += `<Small>${tags.filter((t) => t.toLowerCase() !== 'none').join(' ')}</Small>\n\n`
return tags
}
function addExtends(result: Result, item: ApiItem) {
@ -515,10 +549,3 @@ function addExtends(result: Result, item: ApiItem) {
'',
].join('\n')
}
function addLinkToSource(result: Result, member: ApiItem) {
if ('_fileUrlPath' in member && member._fileUrlPath) {
result.markdown += `<ApiHeading>Source</ApiHeading>\n\n`
result.markdown += `[${member._fileUrlPath}](${REPO_URL}${member._fileUrlPath})\n\n`
}
}

View file

@ -296,6 +296,62 @@ body {
text-align: right;
}
.article__title-with-source-link {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: flex-start;
}
.article__title-with-source-link__meta {
display: flex;
align-items: center;
justify-content: center;
margin-left: auto;
padding-left: 16px;
}
.article__title-with-source-link__meta > a {
display: block;
width: 32px;
height: 32px;
border-radius: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.article__title-with-source-link .article__title-with-source-link__meta > a {
color: var(--color-text);
}
.article__title-with-source-link > a:hover {
background-color: var(--color-tint-1);
}
.article__title-with-source-link .icon {
display: block;
width: 20px;
height: 20px;
}
.article__title-with-source-link--large .article__title-with-source-link__meta > a {
width: 42px;
height: 42px;
}
.article__title-with-source-link--large .icon {
width: 24px;
height: 24px;
}
.article__tag {
display: inline-block;
padding: 4px 6px;
border-radius: var(--border-radius-menu);
background-color: var(--color-tint-0);
color: var(--color-tint-5);
font-size: 12px;
margin-right: 8px;
line-height: 1;
}
.article__title-with-source-link--large .article__tag {
font-size: 14px;
}
/* Prev / Next Links */
.article__links {
@ -472,6 +528,9 @@ body {
text-decoration: none;
cursor: pointer;
}
.article a.anchor {
color: inherit;
}
@media (hover: hover) {
.article a:hover {

View file

@ -114,6 +114,8 @@ export interface Article extends ContentPage {
componentCode: string | null
/** The article's code example files, JSON stringified (optional). */
componentCodeFiles: string | null
/** Tags for this item if it's a reference page */
apiTags: string | null
}
export enum ArticleStatus {

View file

@ -38,9 +38,10 @@ export async function addContentToDb(
componentCode,
componentCodeFiles,
keywords,
apiTags,
content,
path
) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
)
for (let i = 0; i < content.sections.length; i++) {
@ -98,6 +99,7 @@ export async function addContentToDb(
article.componentCode,
article.componentCodeFiles,
article.keywords.join(', '),
article.apiTags,
article.content,
article.path
)

View file

@ -191,7 +191,7 @@
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: "<projectFolder>/temp/<unscopedPackageName>.api.json"
*/
"apiJsonFilePath": "<projectFolder>/packages/<unscopedPackageName>/api/api.json"
"apiJsonFilePath": "<projectFolder>/packages/<unscopedPackageName>/api/api.json",
/**
* Whether "forgotten exports" should be included in the doc model file. Forgotten exports are declarations
* flagged with `ae-forgotten-export` warnings. See https://api-extractor.com/pages/messages/ae-forgotten-export/ to
@ -214,7 +214,7 @@
* SUPPORTED TOKENS: none
* DEFAULT VALUE: ""
*/
// "projectFolderUrl": "http://github.com/path/to/your/projectFolder"
"projectFolderUrl": "https://github.com/tldraw/tldraw/blob/main"
},
/**
* Configures how the .d.ts rollup file will be generated.