diff --git a/apps/docs/components/ArticleReferenceDocsPage.tsx b/apps/docs/components/ArticleReferenceDocsPage.tsx index 34cff4a26..922fd0b90 100644 --- a/apps/docs/components/ArticleReferenceDocsPage.tsx +++ b/apps/docs/components/ArticleReferenceDocsPage.tsx @@ -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 }
-

{article.title}

+ +

{article.title}

+
{article.hero && hero} {article.content && } diff --git a/apps/docs/components/mdx-components/api-docs.tsx b/apps/docs/components/mdx-components/api-docs.tsx index 1b0356c23..6ddd2867f 100644 --- a/apps/docs/components/mdx-components/api-docs.tsx +++ b/apps/docs/components/mdx-components/api-docs.tsx @@ -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 {children} } + +export function TitleWithSourceLink({ + children, + source, + large, + tags, +}: { + children: ReactNode + source?: string | null + large?: boolean + tags?: string[] +}) { + return ( +
+ {children} +
+ {tags?.map((tag) => {tag})} + {source && ( + + + + )} +
+
+ ) +} + +export function Tag({ children }: { children: string }) { + return {children} +} diff --git a/apps/docs/public/icons/code.svg b/apps/docs/public/icons/code.svg new file mode 100644 index 000000000..8386abe1d --- /dev/null +++ b/apps/docs/public/icons/code.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/docs/scripts/functions/connect.ts b/apps/docs/scripts/functions/connect.ts index 82ff044e8..c730f1b4e 100644 --- a/apps/docs/scripts/functions/connect.ts +++ b/apps/docs/scripts/functions/connect.ts @@ -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), diff --git a/apps/docs/scripts/functions/generateSection.ts b/apps/docs/scripts/functions/generateSection.ts index 9d9ca7d6b..fb4c0729f 100644 --- a/apps/docs/scripts/functions/generateSection.ts +++ b/apps/docs/scripts/functions/generateSection.ts @@ -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) { diff --git a/apps/docs/scripts/functions/getApiMarkdown.ts b/apps/docs/scripts/functions/getApiMarkdown.ts index 738a04fc8..d5c2038b4 100644 --- a/apps/docs/scripts/functions/getApiMarkdown.ts +++ b/apps/docs/scripts/functions/getApiMarkdown.ts @@ -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 += `\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 = { + 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 += [ + ``, + '', + heading, + '', + '', + '', + ].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 += `Signature\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 += `${tags.filter((t) => t.toLowerCase() !== 'none').join(' ')}\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 += `Source\n\n` - result.markdown += `[${member._fileUrlPath}](${REPO_URL}${member._fileUrlPath})\n\n` - } -} diff --git a/apps/docs/styles/globals.css b/apps/docs/styles/globals.css index 062a1fd9c..6a454d535 100644 --- a/apps/docs/styles/globals.css +++ b/apps/docs/styles/globals.css @@ -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 { diff --git a/apps/docs/types/content-types.ts b/apps/docs/types/content-types.ts index 81101bf93..fa77f366f 100644 --- a/apps/docs/types/content-types.ts +++ b/apps/docs/types/content-types.ts @@ -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 { diff --git a/apps/docs/utils/addContent.ts b/apps/docs/utils/addContent.ts index 3da123ada..d829fae0c 100644 --- a/apps/docs/utils/addContent.ts +++ b/apps/docs/utils/addContent.ts @@ -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 ) diff --git a/config/api-extractor.json b/config/api-extractor.json index 83b1a515c..69a0dbbda 100644 --- a/config/api-extractor.json +++ b/config/api-extractor.json @@ -191,7 +191,7 @@ * SUPPORTED TOKENS: , , * DEFAULT VALUE: "/temp/.api.json" */ - "apiJsonFilePath": "/packages//api/api.json" + "apiJsonFilePath": "/packages//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.