Document inherited members in reference (#3956)
Our reference docs don't currently include members inherited through the `extends` keyword. These extended items are barely referenced at all - you have to find them in the signature. This diff adds a clearer note to the docs saying which type has been extended, and if possible brings the extended items through onto the current documentation page (with a note saying where they're from) ![image](https://github.com/tldraw/tldraw/assets/1489520/0349252d-e8bc-406b-bf47-636da424ebe0) ### Change Type - [x] `docs` — Changes to the documentation, examples, or templates. - [x] `improvement` — Improving existing features
This commit is contained in:
parent
12aea7ed68
commit
c4b9ea30f4
15 changed files with 312 additions and 188 deletions
|
@ -1,4 +1,5 @@
|
|||
import classNames from 'classnames'
|
||||
import Link from 'next/link'
|
||||
import { ReactNode } from 'react'
|
||||
import { Icon } from '../Icon'
|
||||
|
||||
|
@ -35,32 +36,56 @@ export function TitleWithSourceLink({
|
|||
source,
|
||||
large,
|
||||
tags,
|
||||
inherited,
|
||||
}: {
|
||||
children: ReactNode
|
||||
source?: string | null
|
||||
large?: boolean
|
||||
tags?: string[]
|
||||
inherited?: { name: string; link: 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
|
||||
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}>
|
||||
{tag}
|
||||
</Tag>
|
||||
))}
|
||||
{source && (
|
||||
<a
|
||||
href={source}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title="Source code"
|
||||
className="article__title-with-source-link__source"
|
||||
>
|
||||
<Icon icon="code" />
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{inherited && (
|
||||
<div className="article__title-with-source-link__from">
|
||||
from{' '}
|
||||
<code className="hljs">
|
||||
<Link href={inherited.link} className="code-link">
|
||||
{inherited.name}
|
||||
</Link>
|
||||
</code>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function Tag({ children }: { children: string }) {
|
||||
return <span className={classNames(`article__tag`, `article__tag--${children}`)}>{children}</span>
|
||||
export function Tag({ children, tag }: { children: ReactNode; tag: string }) {
|
||||
return <span className={classNames(`article__tag`, `article__tag--${tag}`)}>{children}</span>
|
||||
}
|
||||
|
|
|
@ -85,4 +85,6 @@ export async function createApiMarkdown() {
|
|||
)
|
||||
sectionsJson.push(apiInputSection)
|
||||
fs.writeFileSync(sectionsJsonPath, JSON.stringify(sectionsJson, null, '\t') + '\n')
|
||||
|
||||
model.throwEncounteredErrors()
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
ApiTypeAlias,
|
||||
ApiVariable,
|
||||
Excerpt,
|
||||
HeritageType,
|
||||
} from '@microsoft/api-extractor-model'
|
||||
import { MarkdownWriter, formatWithPrettier, getPath, getSlug } from '../utils'
|
||||
|
||||
|
@ -36,6 +37,11 @@ const date = new Intl.DateTimeFormat('en-US', {
|
|||
day: '2-digit',
|
||||
}).format(new Date())
|
||||
|
||||
interface Member {
|
||||
item: ApiItem
|
||||
inheritedFrom: ApiItem | null
|
||||
}
|
||||
|
||||
export async function getApiMarkdown(
|
||||
model: TldrawApiModel,
|
||||
categoryName: string,
|
||||
|
@ -49,100 +55,59 @@ export async function getApiMarkdown(
|
|||
const isComponent = model.isComponent(item)
|
||||
const componentProps = isComponent ? model.getReactPropsItem(item) : null
|
||||
|
||||
const members = componentProps?.members ?? item.members
|
||||
if (members) {
|
||||
const constructors = []
|
||||
const properties = []
|
||||
const methods = []
|
||||
for (const member of members) {
|
||||
switch (member.kind) {
|
||||
case ApiItemKind.Constructor:
|
||||
case ApiItemKind.ConstructSignature:
|
||||
constructors.push(member)
|
||||
break
|
||||
case ApiItemKind.Variable:
|
||||
case ApiItemKind.Property:
|
||||
case ApiItemKind.PropertySignature:
|
||||
properties.push(member)
|
||||
break
|
||||
case ApiItemKind.Method:
|
||||
case ApiItemKind.Function:
|
||||
case ApiItemKind.MethodSignature:
|
||||
if (isComponent) {
|
||||
properties.push(member)
|
||||
} else {
|
||||
methods.push(member)
|
||||
}
|
||||
|
||||
break
|
||||
case ApiItemKind.EnumMember:
|
||||
case ApiItemKind.Class:
|
||||
case ApiItemKind.TypeAlias:
|
||||
case ApiItemKind.Interface:
|
||||
// TODO: document these
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unknown member kind: ${member.kind} ${member.displayName}`)
|
||||
}
|
||||
}
|
||||
const contents = collectMembersAndExtends(model, item)
|
||||
|
||||
if (contents.constructors.length) {
|
||||
const constructorResult: Result = { markdown: '', keywords: [] }
|
||||
for (const member of contents.constructors) {
|
||||
await addMarkdownForMember(model, constructorResult, member)
|
||||
addHorizontalRule(constructorResult)
|
||||
}
|
||||
addMarkdown(membersResult, constructorResult.markdown)
|
||||
}
|
||||
|
||||
if (contents.properties.length || isComponent) {
|
||||
const propertiesResult: Result = { markdown: '', keywords: [] }
|
||||
if (componentProps) addExtends(propertiesResult, item, contents.heritage)
|
||||
for (const member of contents.properties) {
|
||||
const slug = getSlug(member.item)
|
||||
addMarkdown(toc, ` - [${member.item.displayName}](#${slug})\n`)
|
||||
await addMarkdownForMember(model, propertiesResult, member, {
|
||||
isComponentProp: isComponent,
|
||||
})
|
||||
addHorizontalRule(propertiesResult)
|
||||
}
|
||||
if (
|
||||
componentProps &&
|
||||
componentProps instanceof ApiDeclaredItem &&
|
||||
componentProps?.kind !== 'Interface'
|
||||
) {
|
||||
propertiesResult.markdown += await excerptToMarkdown(componentProps, componentProps.excerpt, {
|
||||
kind: componentProps.kind,
|
||||
})
|
||||
}
|
||||
|
||||
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`)
|
||||
}
|
||||
}
|
||||
|
||||
if (contents.methods.length) {
|
||||
const methodsResult: Result = { markdown: '', keywords: [] }
|
||||
|
||||
if (constructors.length) {
|
||||
for (const member of constructors) {
|
||||
await addMarkdownForMember(model, constructorResult, member)
|
||||
addHorizontalRule(constructorResult)
|
||||
}
|
||||
addMarkdown(membersResult, constructorResult.markdown)
|
||||
}
|
||||
|
||||
if (properties.length || isComponent) {
|
||||
if (componentProps) addExtends(propertiesResult, componentProps)
|
||||
for (const member of properties) {
|
||||
const slug = getSlug(member)
|
||||
addMarkdown(toc, ` - [${member.displayName}](#${slug})\n`)
|
||||
await addMarkdownForMember(model, propertiesResult, member, {
|
||||
isComponentProp: isComponent,
|
||||
})
|
||||
addHorizontalRule(propertiesResult)
|
||||
}
|
||||
if (
|
||||
componentProps &&
|
||||
componentProps instanceof ApiDeclaredItem &&
|
||||
componentProps?.kind !== 'Interface'
|
||||
) {
|
||||
propertiesResult.markdown += await excerptToMarkdown(
|
||||
componentProps,
|
||||
componentProps.excerpt,
|
||||
{
|
||||
kind: componentProps.kind,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
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`)
|
||||
}
|
||||
}
|
||||
|
||||
if (methods.length) {
|
||||
addMarkdown(toc, `- [Methods](#methods)\n`)
|
||||
addMarkdown(methodsResult, `## Methods\n\n`)
|
||||
for (const member of methods) {
|
||||
const slug = getSlug(member)
|
||||
addMarkdown(toc, ` - [${member.displayName}](#${slug})\n`)
|
||||
await addMarkdownForMember(model, methodsResult, member)
|
||||
addHorizontalRule(methodsResult)
|
||||
}
|
||||
addMarkdown(membersResult, methodsResult.markdown)
|
||||
addMarkdown(toc, `- [Methods](#methods)\n`)
|
||||
addMarkdown(methodsResult, `## Methods\n\n`)
|
||||
for (const member of contents.methods) {
|
||||
const slug = getSlug(member.item)
|
||||
addMarkdown(toc, ` - [${member.item.displayName}](#${slug})\n`)
|
||||
await addMarkdownForMember(model, methodsResult, member)
|
||||
addHorizontalRule(methodsResult)
|
||||
}
|
||||
addMarkdown(membersResult, methodsResult.markdown)
|
||||
}
|
||||
|
||||
await addFrontmatter(model, result, item, categoryName, j)
|
||||
|
@ -153,6 +118,10 @@ export async function getApiMarkdown(
|
|||
result.markdown += `</details>\n\n`
|
||||
}
|
||||
|
||||
if (!isComponent) {
|
||||
addExtends(result, item, contents.heritage)
|
||||
}
|
||||
|
||||
await addDocComment(model, result, item)
|
||||
|
||||
if (membersResult.markdown.length) {
|
||||
|
@ -165,6 +134,108 @@ export async function getApiMarkdown(
|
|||
|
||||
/* --------------------- Helpers -------------------- */
|
||||
|
||||
function sortMembers(members: Member[]) {
|
||||
return members.sort((a, b) => {
|
||||
const aIsStatic = ApiStaticMixin.isBaseClassOf(a.item) && a.item.isStatic
|
||||
const bIsStatic = ApiStaticMixin.isBaseClassOf(b.item) && b.item.isStatic
|
||||
if (aIsStatic && !bIsStatic) return -1
|
||||
if (!aIsStatic && bIsStatic) return 1
|
||||
return a.item.displayName.localeCompare(b.item.displayName)
|
||||
})
|
||||
}
|
||||
|
||||
function collectMembersAndExtends(model: TldrawApiModel, item: ApiItem) {
|
||||
const isComponent = model.isComponent(item)
|
||||
|
||||
const constructors: Member[] = []
|
||||
const properties: Member[] = []
|
||||
const methods: Member[] = []
|
||||
const heritage: HeritageType[] = []
|
||||
|
||||
function visit(item: ApiItem, inheritedFrom: ApiItem | null) {
|
||||
if (item.members) {
|
||||
for (const member of item.members) {
|
||||
switch (member.kind) {
|
||||
case ApiItemKind.Constructor:
|
||||
case ApiItemKind.ConstructSignature:
|
||||
addMember(constructors, member, inheritedFrom)
|
||||
break
|
||||
case ApiItemKind.Variable:
|
||||
case ApiItemKind.Property:
|
||||
case ApiItemKind.PropertySignature:
|
||||
addMember(properties, member, inheritedFrom)
|
||||
break
|
||||
case ApiItemKind.Method:
|
||||
case ApiItemKind.Function:
|
||||
case ApiItemKind.MethodSignature:
|
||||
if (isComponent) {
|
||||
addMember(properties, member, inheritedFrom)
|
||||
} else {
|
||||
addMember(methods, member, inheritedFrom)
|
||||
}
|
||||
|
||||
break
|
||||
case ApiItemKind.EnumMember:
|
||||
case ApiItemKind.Class:
|
||||
case ApiItemKind.TypeAlias:
|
||||
case ApiItemKind.Interface:
|
||||
// TODO: document these
|
||||
break
|
||||
default:
|
||||
model.nonBlockingError(
|
||||
member,
|
||||
`Unknown member kind: ${member.kind} in ${item.displayName}`
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (model.isComponent(item)) {
|
||||
const componentProps = model.getReactPropsItem(item)
|
||||
if (componentProps) {
|
||||
visit(componentProps, null)
|
||||
}
|
||||
}
|
||||
|
||||
const extendsTypes =
|
||||
item instanceof ApiClass && item.extendsType
|
||||
? [item.extendsType]
|
||||
: item instanceof ApiInterface
|
||||
? item.extendsTypes
|
||||
: []
|
||||
|
||||
for (const extendsType of extendsTypes) {
|
||||
if (!inheritedFrom) {
|
||||
heritage.push(extendsType)
|
||||
}
|
||||
|
||||
const tokens = extendsType.excerpt.spannedTokens
|
||||
let extendedItem = null
|
||||
if (tokens[0].kind === 'Reference') {
|
||||
extendedItem = model.tryResolveToken(item, tokens[0])
|
||||
}
|
||||
|
||||
if (extendedItem) {
|
||||
visit(extendedItem, extendedItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visit(item, null)
|
||||
|
||||
sortMembers(constructors)
|
||||
sortMembers(properties)
|
||||
sortMembers(methods)
|
||||
|
||||
return { constructors, properties, methods, heritage }
|
||||
}
|
||||
|
||||
function addMember(members: Member[], item: ApiItem, inheritedFrom: ApiItem | null) {
|
||||
if (members.some((m) => m.item.displayName === item.displayName)) return
|
||||
members.push({ item, inheritedFrom })
|
||||
}
|
||||
|
||||
function addMarkdown(result: Result, markdown: string) {
|
||||
result.markdown += markdown
|
||||
}
|
||||
|
@ -172,12 +243,12 @@ function addMarkdown(result: Result, markdown: string) {
|
|||
async function addMarkdownForMember(
|
||||
model: TldrawApiModel,
|
||||
result: Result,
|
||||
member: ApiItem,
|
||||
{ item, inheritedFrom }: Member,
|
||||
{ isComponentProp = false } = {}
|
||||
) {
|
||||
if (member.displayName.startsWith('_')) return
|
||||
addMemberNameAndMeta(result, model, member, { isComponentProp })
|
||||
await addDocComment(model, result, member)
|
||||
if (item.displayName.startsWith('_')) return
|
||||
addMemberNameAndMeta(result, model, item, { isComponentProp, inheritedFrom })
|
||||
await addDocComment(model, result, item)
|
||||
}
|
||||
|
||||
async function addFrontmatter(
|
||||
|
@ -251,24 +322,27 @@ function addMemberNameAndMeta(
|
|||
result: Result,
|
||||
model: TldrawApiModel,
|
||||
item: ApiItem,
|
||||
{ level = 3, isComponentProp = false } = {}
|
||||
{
|
||||
level = 3,
|
||||
isComponentProp = false,
|
||||
inheritedFrom = null,
|
||||
}: { level?: number; isComponentProp?: boolean; inheritedFrom?: ApiItem | null } = {}
|
||||
) {
|
||||
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`
|
||||
}
|
||||
const inherited = inheritedFrom
|
||||
? { name: inheritedFrom.displayName, link: `/reference/${getPath(inheritedFrom)}` }
|
||||
: null
|
||||
|
||||
const tags = getTags(model, item, { isComponentProp, includeKind: false })
|
||||
result.markdown += [
|
||||
`<TitleWithSourceLink tags={${JSON.stringify(tags)}} inherited={${JSON.stringify(inherited)}}>`,
|
||||
'',
|
||||
heading,
|
||||
'',
|
||||
'</TitleWithSourceLink>',
|
||||
'',
|
||||
].join('\n')
|
||||
}
|
||||
|
||||
async function addDocComment(model: TldrawApiModel, result: Result, member: ApiItem) {
|
||||
|
@ -395,7 +469,7 @@ async function addDocComment(model: TldrawApiModel, result: Result, member: ApiI
|
|||
result.markdown += '</ParametersTable>\n\n'
|
||||
}
|
||||
} else {
|
||||
model.error(member, `Unknown member kind: ${member.kind}`)
|
||||
model.nonBlockingError(member, `Unknown member kind: ${member.kind}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -514,18 +588,11 @@ function getTags(
|
|||
return tags
|
||||
}
|
||||
|
||||
function addExtends(result: Result, item: ApiItem) {
|
||||
const extendsTypes =
|
||||
item instanceof ApiClass && item.extendsType
|
||||
? [item.extendsType]
|
||||
: item instanceof ApiInterface
|
||||
? item.extendsTypes
|
||||
: []
|
||||
|
||||
if (!extendsTypes.length) return
|
||||
function addExtends(result: Result, item: ApiItem, heritage: HeritageType[]) {
|
||||
if (!heritage.length) return
|
||||
|
||||
const links = {} as Record<string, string>
|
||||
for (const type of extendsTypes) {
|
||||
for (const type of heritage) {
|
||||
for (const token of type.excerpt.spannedTokens) {
|
||||
if (!token.canonicalReference) continue
|
||||
|
||||
|
@ -543,7 +610,7 @@ function addExtends(result: Result, item: ApiItem) {
|
|||
result.markdown += [
|
||||
`<CodeLinkProvider links={${JSON.stringify(links)}}>`,
|
||||
'',
|
||||
`Extends \`${extendsTypes.map((type) => type.excerpt.text).join(', ')}\`.`,
|
||||
`Extends \`${heritage.map((type) => type.excerpt.text).join(', ')}\`.`,
|
||||
'',
|
||||
'</CodeLinkProvider>',
|
||||
'',
|
||||
|
|
|
@ -309,7 +309,7 @@ body {
|
|||
margin-left: auto;
|
||||
padding-left: 16px;
|
||||
}
|
||||
.article__title-with-source-link__meta > a {
|
||||
.article__title-with-source-link__source {
|
||||
display: block;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
@ -318,11 +318,11 @@ body {
|
|||
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__source {
|
||||
color: var(--color-text) !important;
|
||||
}
|
||||
|
||||
.article__title-with-source-link > a:hover {
|
||||
.article__title-with-source-link__source:hover {
|
||||
background-color: var(--color-tint-1);
|
||||
}
|
||||
.article__title-with-source-link .icon {
|
||||
|
@ -330,7 +330,7 @@ body {
|
|||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
.article__title-with-source-link--large .article__title-with-source-link__meta > a {
|
||||
.article__title-with-source-link--large .article__title-with-source-link__source {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
}
|
||||
|
@ -342,15 +342,21 @@ body {
|
|||
display: inline-block;
|
||||
padding: 4px 6px;
|
||||
border-radius: var(--border-radius-menu);
|
||||
background-color: var(--color-tint-0);
|
||||
background-color: var(--color-tint-1);
|
||||
color: var(--color-tint-5);
|
||||
font-size: 12px;
|
||||
margin-right: 8px;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.article__title-with-source-link--large .article__tag {
|
||||
font-size: 14px;
|
||||
}
|
||||
.article__title-with-source-link__from {
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: var(--color-tint-5);
|
||||
}
|
||||
|
||||
/* Prev / Next Links */
|
||||
|
||||
|
|
|
@ -83,8 +83,8 @@
|
|||
}
|
||||
a.code-link::before {
|
||||
content: '';
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
width: 1.0714em; /* 15px when font-size is 14px */
|
||||
height: 1.0714em;
|
||||
display: inline-block;
|
||||
background: currentColor;
|
||||
-webkit-mask: url('data:image/svg+xml;utf8,<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M6.5 2.5H3.5C2.94772 2.5 2.5 2.94772 2.5 3.5V11.5C2.5 12.0523 2.94772 12.5 3.5 12.5H11.5C12.0523 12.5 12.5 12.0523 12.5 11.5V8.5M9.5 2.5H12.5M12.5 2.5V5.5M12.5 2.5L6.5 8.5" stroke="black" stroke-linecap="round" stroke-linejoin="round"/></svg>')
|
||||
|
@ -93,6 +93,6 @@ a.code-link::before {
|
|||
no-repeat 50% 50%;
|
||||
-webkit-mask-size: cover;
|
||||
mask-size: cover;
|
||||
vertical-align: -2px;
|
||||
margin-right: 2px;
|
||||
vertical-align: -0.142em; /* 2px when font-size is 14px */
|
||||
margin-right: 0.142em;
|
||||
}
|
||||
|
|
|
@ -15,8 +15,9 @@ export class TldrawApiModel extends ApiModel {
|
|||
private reactComponents = new Set<ApiItem>()
|
||||
private reactComponentProps = new Set<ApiItem>()
|
||||
|
||||
nonBlockingErrors: Error[] = []
|
||||
|
||||
async preprocessReactComponents() {
|
||||
const errors = []
|
||||
for (const packageModel of this.members) {
|
||||
assert(packageModel instanceof ApiPackage)
|
||||
if (packageModel.name !== 'tldraw') continue
|
||||
|
@ -37,22 +38,18 @@ export class TldrawApiModel extends ApiModel {
|
|||
props.tsdocComment.summarySection
|
||||
)
|
||||
if (markdown.trim()) {
|
||||
this.error(
|
||||
this.nonBlockingError(
|
||||
props,
|
||||
"Component props should not contain documentation as it won't be included in the docs site. Add it to the component instead."
|
||||
)
|
||||
}
|
||||
}
|
||||
if (props) this.reactComponentProps.add(props)
|
||||
} catch (e) {
|
||||
errors.push(e)
|
||||
} catch (e: any) {
|
||||
this.nonBlockingErrors.push(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new Error(errors.map((e) => (e as any).message).join('\n\n'))
|
||||
}
|
||||
}
|
||||
|
||||
resolveToken(origin: ApiItem, token: ExcerptToken) {
|
||||
|
@ -63,6 +60,14 @@ export class TldrawApiModel extends ApiModel {
|
|||
return apiItemResult.resolvedApiItem!
|
||||
}
|
||||
|
||||
tryResolveToken(origin: ApiItem, token: ExcerptToken) {
|
||||
const apiItemResult = this.resolveDeclarationReference(token.canonicalReference!, origin)
|
||||
if (apiItemResult.errorMessage) {
|
||||
return null
|
||||
}
|
||||
return apiItemResult.resolvedApiItem!
|
||||
}
|
||||
|
||||
getReactPropsItem(component: ApiItem): ApiItem | null {
|
||||
if (component instanceof ApiFunction) {
|
||||
if (component.parameters.length === 0) return null
|
||||
|
@ -84,10 +89,11 @@ export class TldrawApiModel extends ApiModel {
|
|||
return this.resolveToken(component, tokens[0])
|
||||
}
|
||||
|
||||
this.error(
|
||||
this.nonBlockingError(
|
||||
component,
|
||||
`Expected props parameter to be a simple reference. Rewrite this to use a \`${component.displayName}Props\` interface.\nFound: ${propsParam.parameterTypeExcerpt.text}`
|
||||
)
|
||||
return null
|
||||
} else if (component instanceof ApiVariable) {
|
||||
const tokens = component.variableTypeExcerpt.spannedTokens
|
||||
if (
|
||||
|
@ -130,12 +136,16 @@ export class TldrawApiModel extends ApiModel {
|
|||
return null
|
||||
}
|
||||
|
||||
this.error(
|
||||
this.nonBlockingError(
|
||||
component,
|
||||
`Expected a simple props interface for react component. Got: ${component.variableTypeExcerpt.text}`
|
||||
)
|
||||
|
||||
return null
|
||||
} else {
|
||||
this.error(component, `Unknown item kind for @react component: ${component.kind}`)
|
||||
this.nonBlockingError(component, `Unknown item kind for @react component: ${component.kind}`)
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -147,12 +157,26 @@ export class TldrawApiModel extends ApiModel {
|
|||
return this.reactComponentProps.has(item)
|
||||
}
|
||||
|
||||
error(item: ApiItem, message: string): never {
|
||||
private createError(item: ApiItem, message: string) {
|
||||
const suffix =
|
||||
'_fileUrlPath' in item && typeof item._fileUrlPath === 'string'
|
||||
? `\nin ${item._fileUrlPath}`
|
||||
: ''
|
||||
throw new Error(`${item.displayName}: ${message}${suffix}`)
|
||||
return new Error(`${item.displayName}: ${message}${suffix}`)
|
||||
}
|
||||
|
||||
nonBlockingError(item: ApiItem, message: string) {
|
||||
this.nonBlockingErrors.push(this.createError(item, message))
|
||||
}
|
||||
|
||||
throwEncounteredErrors() {
|
||||
if (this.nonBlockingErrors.length > 0) {
|
||||
throw new Error(this.nonBlockingErrors.map((e) => (e as any).message).join('\n\n'))
|
||||
}
|
||||
}
|
||||
|
||||
error(item: ApiItem, message: string): never {
|
||||
throw this.createError(item, message)
|
||||
}
|
||||
|
||||
assert(item: ApiItem, condition: unknown, message: string): asserts condition {
|
||||
|
|
|
@ -1045,13 +1045,13 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
getStateDescendant<T extends StateNode>(path: string): T | undefined;
|
||||
getStyleForNextShape<T>(style: StyleProp<T>): T;
|
||||
// @deprecated (undocumented)
|
||||
getSvg(shapes: TLShape[] | TLShapeId[], opts?: Partial<TLSvgOptions>): Promise<SVGSVGElement | undefined>;
|
||||
getSvgElement(shapes: TLShape[] | TLShapeId[], opts?: Partial<TLSvgOptions>): Promise<{
|
||||
getSvg(shapes: TLShape[] | TLShapeId[], opts?: TLSvgOptions): Promise<SVGSVGElement | undefined>;
|
||||
getSvgElement(shapes: TLShape[] | TLShapeId[], opts?: TLSvgOptions): Promise<{
|
||||
height: number;
|
||||
svg: SVGSVGElement;
|
||||
width: number;
|
||||
} | undefined>;
|
||||
getSvgString(shapes: TLShape[] | TLShapeId[], opts?: Partial<TLSvgOptions>): Promise<{
|
||||
getSvgString(shapes: TLShape[] | TLShapeId[], opts?: TLSvgOptions): Promise<{
|
||||
height: number;
|
||||
svg: string;
|
||||
width: number;
|
||||
|
@ -3299,17 +3299,17 @@ export type TLStoreWithStatus = {
|
|||
// @public (undocumented)
|
||||
export interface TLSvgOptions {
|
||||
// (undocumented)
|
||||
background: boolean;
|
||||
background?: boolean;
|
||||
// (undocumented)
|
||||
bounds: Box;
|
||||
bounds?: Box;
|
||||
// (undocumented)
|
||||
darkMode?: boolean;
|
||||
// (undocumented)
|
||||
padding: number;
|
||||
padding?: number;
|
||||
// (undocumented)
|
||||
preserveAspectRatio: React.SVGAttributes<SVGSVGElement>['preserveAspectRatio'];
|
||||
preserveAspectRatio?: React.SVGAttributes<SVGSVGElement>['preserveAspectRatio'];
|
||||
// (undocumented)
|
||||
scale: number;
|
||||
scale?: number;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
|
|
|
@ -8224,7 +8224,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
*
|
||||
* @public
|
||||
*/
|
||||
async getSvgElement(shapes: TLShapeId[] | TLShape[], opts = {} as Partial<TLSvgOptions>) {
|
||||
async getSvgElement(shapes: TLShapeId[] | TLShape[], opts: TLSvgOptions = {}) {
|
||||
const result = await getSvgJsx(this, shapes, opts)
|
||||
if (!result) return undefined
|
||||
|
||||
|
@ -8251,7 +8251,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
*
|
||||
* @public
|
||||
*/
|
||||
async getSvgString(shapes: TLShapeId[] | TLShape[], opts = {} as Partial<TLSvgOptions>) {
|
||||
async getSvgString(shapes: TLShapeId[] | TLShape[], opts: TLSvgOptions = {}) {
|
||||
const result = await this.getSvgElement(shapes, opts)
|
||||
if (!result) return undefined
|
||||
|
||||
|
@ -8264,7 +8264,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
}
|
||||
|
||||
/** @deprecated Use {@link Editor.getSvgString} or {@link Editor.getSvgElement} instead. */
|
||||
async getSvg(shapes: TLShapeId[] | TLShape[], opts = {} as Partial<TLSvgOptions>) {
|
||||
async getSvg(shapes: TLShapeId[] | TLShape[], opts: TLSvgOptions = {}) {
|
||||
const result = await this.getSvgElement(shapes, opts)
|
||||
if (!result) return undefined
|
||||
return result.svg
|
||||
|
|
|
@ -13,7 +13,7 @@ import { TLSvgOptions } from './types/misc-types'
|
|||
export async function getSvgJsx(
|
||||
editor: Editor,
|
||||
shapes: TLShapeId[] | TLShape[],
|
||||
opts = {} as Partial<TLSvgOptions>
|
||||
opts: TLSvgOptions = {}
|
||||
) {
|
||||
const ids =
|
||||
typeof shapes[0] === 'string' ? (shapes as TLShapeId[]) : (shapes as TLShape[]).map((s) => s.id)
|
||||
|
|
|
@ -9,12 +9,12 @@ export type OptionalKeys<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>
|
|||
|
||||
/** @public */
|
||||
export interface TLSvgOptions {
|
||||
bounds: Box
|
||||
scale: number
|
||||
background: boolean
|
||||
padding: number
|
||||
bounds?: Box
|
||||
scale?: number
|
||||
background?: boolean
|
||||
padding?: number
|
||||
darkMode?: boolean
|
||||
preserveAspectRatio: React.SVGAttributes<SVGSVGElement>['preserveAspectRatio']
|
||||
preserveAspectRatio?: React.SVGAttributes<SVGSVGElement>['preserveAspectRatio']
|
||||
}
|
||||
|
||||
/** @public */
|
||||
|
|
|
@ -359,7 +359,7 @@ export function ConvertToBookmarkMenuItem(): JSX_2.Element | null;
|
|||
export function ConvertToEmbedMenuItem(): JSX_2.Element | null;
|
||||
|
||||
// @public
|
||||
export function copyAs(editor: Editor, ids: TLShapeId[], format?: TLCopyType, opts?: Partial<TLSvgOptions>): Promise<void>;
|
||||
export function copyAs(editor: Editor, ids: TLShapeId[], format?: TLCopyType, opts?: TLSvgOptions): Promise<void>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function CopyAsMenuGroup(): JSX_2.Element;
|
||||
|
@ -634,7 +634,7 @@ export interface ExampleDialogProps {
|
|||
}
|
||||
|
||||
// @public
|
||||
export function exportAs(editor: Editor, ids: TLShapeId[], format: TLExportType | undefined, name: string | undefined, opts?: Partial<TLSvgOptions>): Promise<void>;
|
||||
export function exportAs(editor: Editor, ids: TLShapeId[], format: TLExportType | undefined, name: string | undefined, opts?: TLSvgOptions): Promise<void>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function ExportFileContentSubMenu(): JSX_2.Element;
|
||||
|
@ -644,7 +644,7 @@ export function exportToBlob({ editor, ids, format, opts, }: {
|
|||
editor: Editor;
|
||||
format: TLExportType;
|
||||
ids: TLShapeId[];
|
||||
opts?: Partial<TLSvgOptions>;
|
||||
opts?: TLSvgOptions;
|
||||
}): Promise<Blob>;
|
||||
|
||||
// @public (undocumented)
|
||||
|
@ -1660,7 +1660,7 @@ export function TldrawHandles({ children }: TLHandlesProps): JSX_2.Element | nul
|
|||
export const TldrawImage: NamedExoticComponent<TldrawImageProps>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface TldrawImageProps extends Partial<TLSvgOptions> {
|
||||
export interface TldrawImageProps extends TLSvgOptions {
|
||||
bindingUtils?: readonly TLAnyBindingUtilConstructor[];
|
||||
format?: 'png' | 'svg';
|
||||
pageId?: TLPageId;
|
||||
|
|
|
@ -20,7 +20,7 @@ import { getSvgAsImage } from './utils/export/export'
|
|||
import { useDefaultEditorAssetsWithOverrides } from './utils/static-assets/assetUrls'
|
||||
|
||||
/** @public */
|
||||
export interface TldrawImageProps extends Partial<TLSvgOptions> {
|
||||
export interface TldrawImageProps extends TLSvgOptions {
|
||||
/**
|
||||
* The snapshot to display.
|
||||
*/
|
||||
|
|
|
@ -18,7 +18,7 @@ export function copyAs(
|
|||
editor: Editor,
|
||||
ids: TLShapeId[],
|
||||
format: TLCopyType = 'svg',
|
||||
opts = {} as Partial<TLSvgOptions>
|
||||
opts: TLSvgOptions = {}
|
||||
): Promise<void> {
|
||||
// Note: it's important that this function itself isn't async and doesn't really use promises -
|
||||
// we need to create the relevant `ClipboardItem`s and call window.navigator.clipboard.write
|
||||
|
|
|
@ -96,7 +96,7 @@ export async function getSvgAsImage(
|
|||
}
|
||||
}
|
||||
|
||||
async function getSvgString(editor: Editor, ids: TLShapeId[], opts: Partial<TLSvgOptions>) {
|
||||
async function getSvgString(editor: Editor, ids: TLShapeId[], opts: TLSvgOptions) {
|
||||
const svg = await editor.getSvgString(ids?.length ? ids : [...editor.getCurrentPageShapeIds()], {
|
||||
scale: 1,
|
||||
background: editor.getInstanceState().exportBackground,
|
||||
|
@ -112,7 +112,7 @@ export async function exportToString(
|
|||
editor: Editor,
|
||||
ids: TLShapeId[],
|
||||
format: 'svg' | 'json',
|
||||
opts = {} as Partial<TLSvgOptions>
|
||||
opts: TLSvgOptions = {}
|
||||
) {
|
||||
switch (format) {
|
||||
case 'svg': {
|
||||
|
@ -141,12 +141,12 @@ export async function exportToBlob({
|
|||
editor,
|
||||
ids,
|
||||
format,
|
||||
opts = {} as Partial<TLSvgOptions>,
|
||||
opts = {},
|
||||
}: {
|
||||
editor: Editor
|
||||
ids: TLShapeId[]
|
||||
format: TLExportType
|
||||
opts?: Partial<TLSvgOptions>
|
||||
opts?: TLSvgOptions
|
||||
}): Promise<Blob> {
|
||||
switch (format) {
|
||||
case 'svg':
|
||||
|
@ -188,7 +188,7 @@ export function exportToBlobPromise(
|
|||
editor: Editor,
|
||||
ids: TLShapeId[],
|
||||
format: TLExportType,
|
||||
opts = {} as Partial<TLSvgOptions>
|
||||
opts: TLSvgOptions = {}
|
||||
): { blobPromise: Promise<Blob>; mimeType: string } {
|
||||
return {
|
||||
blobPromise: exportToBlob({ editor, ids, format, opts }),
|
||||
|
|
|
@ -20,7 +20,7 @@ export async function exportAs(
|
|||
ids: TLShapeId[],
|
||||
format: TLExportType = 'png',
|
||||
name: string | undefined,
|
||||
opts = {} as Partial<TLSvgOptions>
|
||||
opts: TLSvgOptions = {}
|
||||
) {
|
||||
// If we don't get name then use a predefined one
|
||||
if (!name) {
|
||||
|
|
Loading…
Reference in a new issue