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:
parent
012e54959d
commit
fba82ed924
10 changed files with 219 additions and 82 deletions
|
@ -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} />
|
||||
<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} />}
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
|
|
5
apps/docs/public/icons/code.svg
Normal file
5
apps/docs/public/icons/code.svg
Normal 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 |
|
@ -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),
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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')
|
||||
}
|
||||
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`
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue