docs: rework docs site to have different sections (#2686)

This PR starts putting in place the high-level changes we want to make
to the docs site.
- It makes separate sections for Reference and Examples and Community.
- Gets rid of the secondary sidebar and integrates it into the main
sidebar.
- Groups the reference articles by type.
- Pulls in the examples alongside code and a live playground so people
don't have to visit examples.tldraw.com separately.

<img width="1458" alt="Screenshot 2024-01-30 at 09 43 46"
src="https://github.com/tldraw/tldraw/assets/469604/4f5aa339-3a69-4d9b-9b9f-dfdddea623e8">

Again, this is the top-level changes and there's more to be done for the
next PR(s):
  - create quick start page
  - clean up installation page
  - add accordion to Examples page prbly
  - put fun stuff in header (from footer)
  - landing page
  - something for landing page of API
  - search cmd-k and border
  - cleanup _sidebarReferenceContentLinks
  - external links _blank
  - address potential skew issue with code examples
  - have a link to other examples (next.js, etc.)

### Change Type

- [x] `documentation` — Changes to the documentation only[^2]

### Test Plan

1. Make sure examples work!

### Release Notes

- Rework our docs site to pull together the examples app and reference
section more cohesively.

---------

Co-authored-by: Taha <98838967+Taha-Hassan-Git@users.noreply.github.com>
Co-authored-by: Steve Ruiz <steveruizok@gmail.com>
Co-authored-by: Mitja Bezenšek <mitja.bezensek@gmail.com>
Co-authored-by: alex <alex@dytry.ch>
Co-authored-by: Lu Wilson <l2wilson94@gmail.com>
Co-authored-by: Dan Groshev <git@dgroshev.com>
This commit is contained in:
Mime Čuvalo 2024-01-30 14:19:25 +00:00 committed by GitHub
parent a43b172b64
commit 3ae48af67c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
68 changed files with 94415 additions and 92577 deletions

View file

@ -1,4 +1,4 @@
content/gen content/reference
api-content.json api-content.json
content.db content.db
.env .env

View file

@ -3,4 +3,3 @@
content.db content.db
node_modules node_modules
utils/vector-db/index.json utils/vector-db/index.json
**/*.api.json

View file

@ -25,8 +25,7 @@ The files are also provided in this repo.
You can build the markdown and API content using the following scripts: You can build the markdown and API content using the following scripts:
- `yarn refresh-all` to reset the database, generate the markdown from the API docs, and populate the database with articles from both the regular content and the generated API content - `yarn refresh-everything` to reset the database, generate the markdown from the API docs, and populate the database with articles from both the regular content and the generated API content
- `yarn refresh-api-content` to refresh just the API content
- `yarn refresh-content` to generate just the regular content - `yarn refresh-content` to generate just the regular content
# Content # Content

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -427,7 +427,16 @@
}, },
{ {
"kind": "Content", "kind": "Content",
"text": "<string>;\n}" "text": "<string>;\n labelPosition: "
},
{
"kind": "Reference",
"text": "T.Validator",
"canonicalReference": "@tldraw/validate!Validator:class"
},
{
"kind": "Content",
"text": "<number>;\n}"
} }
], ],
"fileUrlPath": "packages/tlschema/src/shapes/TLArrowShape.ts", "fileUrlPath": "packages/tlschema/src/shapes/TLArrowShape.ts",
@ -436,7 +445,7 @@
"name": "arrowShapeProps", "name": "arrowShapeProps",
"variableTypeTokenRange": { "variableTypeTokenRange": {
"startIndex": 1, "startIndex": 1,
"endIndex": 42 "endIndex": 44
} }
}, },
{ {
@ -2688,7 +2697,7 @@
}, },
{ {
"kind": "Content", "kind": "Content",
"text": "readonly [{\n readonly locale: \"ar\";\n readonly label: \"عربي\";\n}, {\n readonly locale: \"ca\";\n readonly label: \"Català\";\n}, {\n readonly locale: \"cs\";\n readonly label: \"Čeština\";\n}, {\n readonly locale: \"da\";\n readonly label: \"Danish\";\n}, {\n readonly locale: \"de\";\n readonly label: \"Deutsch\";\n}, {\n readonly locale: \"en\";\n readonly label: \"English\";\n}, {\n readonly locale: \"es\";\n readonly label: \"Español\";\n}, {\n readonly locale: \"fa\";\n readonly label: \"فارسی\";\n}, {\n readonly locale: \"fi\";\n readonly label: \"Suomi\";\n}, {\n readonly locale: \"fr\";\n readonly label: \"Français\";\n}, {\n readonly locale: \"gl\";\n readonly label: \"Galego\";\n}, {\n readonly locale: \"he\";\n readonly label: \"עברית\";\n}, {\n readonly locale: \"it\";\n readonly label: \"Italiano\";\n}, {\n readonly locale: \"ja\";\n readonly label: \"日本語\";\n}, {\n readonly locale: \"ko-kr\";\n readonly label: \"한국어\";\n}, {\n readonly locale: \"ku\";\n readonly label: \"کوردی\";\n}, {\n readonly locale: \"hi-in\";\n readonly label: \"हिन्दी\";\n}, {\n readonly locale: \"hu\";\n readonly label: \"Magyar\";\n}, {\n readonly locale: \"my\";\n readonly label: \"မြန်မာစာ\";\n}, {\n readonly locale: \"ne\";\n readonly label: \"नेपाली\";\n}, {\n readonly locale: \"no\";\n readonly label: \"Norwegian\";\n}, {\n readonly locale: \"pl\";\n readonly label: \"Polski\";\n}, {\n readonly locale: \"pt-br\";\n readonly label: \"Português - Brasil\";\n}, {\n readonly locale: \"pt-pt\";\n readonly label: \"Português - Europeu\";\n}, {\n readonly locale: \"ro\";\n readonly label: \"Română\";\n}, {\n readonly locale: \"ru\";\n readonly label: \"Russian\";\n}, {\n readonly locale: \"sv\";\n readonly label: \"Svenska\";\n}, {\n readonly locale: \"te\";\n readonly label: \"తెలుగు\";\n}, {\n readonly locale: \"th\";\n readonly label: \"ภาษาไทย\";\n}, {\n readonly locale: \"tr\";\n readonly label: \"Türkçe\";\n}, {\n readonly locale: \"uk\";\n readonly label: \"Ukrainian\";\n}, {\n readonly locale: \"vi\";\n readonly label: \"Tiếng Việt\";\n}, {\n readonly locale: \"zh-cn\";\n readonly label: \"简体中文\";\n}, {\n readonly locale: \"zh-tw\";\n readonly label: \"繁體中文 (台灣)\";\n}]" "text": "readonly [{\n readonly locale: \"ca\";\n readonly label: \"Català\";\n}, {\n readonly locale: \"cs\";\n readonly label: \"Čeština\";\n}, {\n readonly locale: \"da\";\n readonly label: \"Danish\";\n}, {\n readonly locale: \"de\";\n readonly label: \"Deutsch\";\n}, {\n readonly locale: \"en\";\n readonly label: \"English\";\n}, {\n readonly locale: \"es\";\n readonly label: \"Español\";\n}, {\n readonly locale: \"fr\";\n readonly label: \"Français\";\n}, {\n readonly locale: \"gl\";\n readonly label: \"Galego\";\n}, {\n readonly locale: \"it\";\n readonly label: \"Italiano\";\n}, {\n readonly locale: \"hu\";\n readonly label: \"Magyar\";\n}, {\n readonly locale: \"no\";\n readonly label: \"Norwegian\";\n}, {\n readonly locale: \"pl\";\n readonly label: \"Polski\";\n}, {\n readonly locale: \"pt-br\";\n readonly label: \"Português - Brasil\";\n}, {\n readonly locale: \"pt-pt\";\n readonly label: \"Português - Europeu\";\n}, {\n readonly locale: \"ro\";\n readonly label: \"Română\";\n}, {\n readonly locale: \"ru\";\n readonly label: \"Russian\";\n}, {\n readonly locale: \"fi\";\n readonly label: \"Suomi\";\n}, {\n readonly locale: \"sv\";\n readonly label: \"Svenska\";\n}, {\n readonly locale: \"vi\";\n readonly label: \"Tiếng Việt\";\n}, {\n readonly locale: \"tr\";\n readonly label: \"Türkçe\";\n}, {\n readonly locale: \"uk\";\n readonly label: \"Ukrainian\";\n}, {\n readonly locale: \"he\";\n readonly label: \"עברית\";\n}, {\n readonly locale: \"ar\";\n readonly label: \"عربي\";\n}, {\n readonly locale: \"fa\";\n readonly label: \"فارسی\";\n}, {\n readonly locale: \"ku\";\n readonly label: \"کوردی\";\n}, {\n readonly locale: \"ne\";\n readonly label: \"नेपाली\";\n}, {\n readonly locale: \"hi-in\";\n readonly label: \"हिन्दी\";\n}, {\n readonly locale: \"te\";\n readonly label: \"తెలుగు\";\n}, {\n readonly locale: \"th\";\n readonly label: \"ภาษาไทย\";\n}, {\n readonly locale: \"my\";\n readonly label: \"မြန်မာစာ\";\n}, {\n readonly locale: \"ko-kr\";\n readonly label: \"한국어\";\n}, {\n readonly locale: \"ja\";\n readonly label: \"日本語\";\n}, {\n readonly locale: \"zh-cn\";\n readonly label: \"简体中文\";\n}, {\n readonly locale: \"zh-tw\";\n readonly label: \"繁體中文 (台灣)\";\n}]"
} }
], ],
"fileUrlPath": "packages/tlschema/src/translations/languages.ts", "fileUrlPath": "packages/tlschema/src/translations/languages.ts",
@ -6018,6 +6027,33 @@
"endIndex": 2 "endIndex": 2
} }
}, },
{
"kind": "PropertySignature",
"canonicalReference": "@tldraw/tlschema!TLHandle#h:member",
"docComment": "",
"excerptTokens": [
{
"kind": "Content",
"text": "h?: "
},
{
"kind": "Content",
"text": "number"
},
{
"kind": "Content",
"text": ";"
}
],
"isReadonly": false,
"isOptional": true,
"releaseTag": "Public",
"name": "h",
"propertyTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
}
},
{ {
"kind": "PropertySignature", "kind": "PropertySignature",
"canonicalReference": "@tldraw/tlschema!TLHandle#id:member", "canonicalReference": "@tldraw/tlschema!TLHandle#id:member",
@ -6100,6 +6136,33 @@
"endIndex": 2 "endIndex": 2
} }
}, },
{
"kind": "PropertySignature",
"canonicalReference": "@tldraw/tlschema!TLHandle#w:member",
"docComment": "",
"excerptTokens": [
{
"kind": "Content",
"text": "w?: "
},
{
"kind": "Content",
"text": "number"
},
{
"kind": "Content",
"text": ";"
}
],
"isReadonly": false,
"isOptional": true,
"releaseTag": "Public",
"name": "w",
"propertyTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
}
},
{ {
"kind": "PropertySignature", "kind": "PropertySignature",
"canonicalReference": "@tldraw/tlschema!TLHandle#x:member", "canonicalReference": "@tldraw/tlschema!TLHandle#x:member",
@ -6618,6 +6681,42 @@
"endIndex": 2 "endIndex": 2
} }
}, },
{
"kind": "PropertySignature",
"canonicalReference": "@tldraw/tlschema!TLInstance#duplicateProps:member",
"docComment": "",
"excerptTokens": [
{
"kind": "Content",
"text": "duplicateProps: "
},
{
"kind": "Content",
"text": "{\n shapeIds: "
},
{
"kind": "Reference",
"text": "TLShapeId",
"canonicalReference": "@tldraw/tlschema!TLShapeId:type"
},
{
"kind": "Content",
"text": "[];\n offset: {\n x: number;\n y: number;\n };\n } | null"
},
{
"kind": "Content",
"text": ";"
}
],
"isReadonly": false,
"isOptional": false,
"releaseTag": "Public",
"name": "duplicateProps",
"propertyTypeTokenRange": {
"startIndex": 1,
"endIndex": 4
}
},
{ {
"kind": "PropertySignature", "kind": "PropertySignature",
"canonicalReference": "@tldraw/tlschema!TLInstance#exportBackground:member", "canonicalReference": "@tldraw/tlschema!TLInstance#exportBackground:member",

View file

@ -80,12 +80,12 @@ export async function GET(req: NextRequest) {
const { category, section, article, heading, score } = result const { category, section, article, heading, score } = result
const isUncategorized = category.id === section.id + '_ucg' const isUncategorized = category.id === section.id + '_ucg'
if (BANNED_HEADINGS.some((h) => heading.slug.endsWith(h))) continue if (BANNED_HEADINGS.some((h) => heading.slug.endsWith(h))) continue
results[section.id === 'gen' ? 'apiDocs' : 'articles'].push({ results[section.id === 'reference' ? 'apiDocs' : 'articles'].push({
id: result.id, id: result.id,
type: 'heading', type: 'heading',
subtitle: isUncategorized ? section.title : `${section.title} / ${category.title}`, subtitle: isUncategorized ? section.title : `${section.title} / ${category.title}`,
title: title:
section.id === 'gen' section.id === 'reference'
? article.title + '.' + heading.title ? article.title + '.' + heading.title
: article.title + ': ' + heading.title, : article.title + ': ' + heading.title,
url: isUncategorized url: isUncategorized
@ -115,7 +115,7 @@ export async function GET(req: NextRequest) {
article.sectionId article.sectionId
) )
const isUncategorized = category.id === section.id + '_ucg' const isUncategorized = category.id === section.id + '_ucg'
results[section.id === 'gen' ? 'apiDocs' : 'articles'].push({ results[section.id === 'reference' ? 'apiDocs' : 'articles'].push({
id: article.id, id: article.id,
type: 'article', type: 'article',
subtitle: isUncategorized ? section.title : `${section.title} / ${category.title}`, subtitle: isUncategorized ? section.title : `${section.title} / ${category.title}`,

View file

@ -61,7 +61,7 @@ export async function GET(req: NextRequest) {
await searchForArticle.all(query).then(async (queryResults) => { await searchForArticle.all(query).then(async (queryResults) => {
for (const article of queryResults) { for (const article of queryResults) {
const isApiDoc = article.sectionId === 'gen' const isApiDoc = article.sectionId === 'reference'
const section = await db.getSection(article.sectionId) const section = await db.getSection(article.sectionId)
const category = await db.getCategory(article.categoryId) const category = await db.getCategory(article.categoryId)
const isUncategorized = category.id === section.id + '_ucg' const isUncategorized = category.id === section.id + '_ucg'
@ -95,7 +95,7 @@ export async function GET(req: NextRequest) {
if (BANNED_HEADINGS.some((h) => heading.slug.endsWith(h))) continue if (BANNED_HEADINGS.some((h) => heading.slug.endsWith(h))) continue
const article = await db.getArticle(heading.articleId) const article = await db.getArticle(heading.articleId)
const isApiDoc = article.sectionId === 'gen' const isApiDoc = article.sectionId === 'reference'
const section = await db.getSection(article.sectionId) const section = await db.getSection(article.sectionId)
const category = await db.getCategory(article.categoryId) const category = await db.getCategory(article.categoryId)
const isUncategorized = category.id === section.id + '_ucg' const isUncategorized = category.id === section.id + '_ucg'
@ -105,7 +105,7 @@ export async function GET(req: NextRequest) {
type: 'heading', type: 'heading',
subtitle: isUncategorized ? section.title : `${section.title} / ${category.title}`, subtitle: isUncategorized ? section.title : `${section.title} / ${category.title}`,
title: title:
section.id === 'gen' section.id === 'reference'
? article.title + '.' + heading.title ? article.title + '.' + heading.title
: article.title + ': ' + heading.title, : article.title + ': ' + heading.title,
url: isUncategorized url: isUncategorized

View file

@ -8,7 +8,7 @@ export default async function ClaPage() {
return ( return (
<> <>
<Header activeId={null} /> <Header />
<Sidebar {...sidebar} /> <Sidebar {...sidebar} />
<main className="article"> <main className="article">
<div className="page-header"> <div className="page-header">

View file

@ -8,7 +8,7 @@ export default async function LicensePage() {
return ( return (
<> <>
<Header activeId={null} /> <Header />
<Sidebar {...sidebar} /> <Sidebar {...sidebar} />
<main className="article"> <main className="article">
<div className="page-header"> <div className="page-header">

View file

@ -8,7 +8,7 @@ export default async function NotFound() {
return ( return (
<> <>
<Header activeId={null} /> <Header />
<Sidebar {...sidebar} /> <Sidebar {...sidebar} />
<main className="article"> <main className="article">
<div className="page-header"> <div className="page-header">

View file

@ -4,7 +4,7 @@ import { notFound } from 'next/navigation'
export default async function HomePage() { export default async function HomePage() {
const db = await getDb() const db = await getDb()
const article = await db.db.get(`SELECT * FROM articles WHERE articles.path = ?`, `/introduction`) const article = await db.db.get(`SELECT * FROM articles WHERE articles.path = ?`, `/quick-start`)
if (article) return <ArticleDocsPage article={article} /> if (article) return <ArticleDocsPage article={article} />
throw notFound() throw notFound()
} }

View file

@ -45,8 +45,8 @@ export default async function SearchResultsPage({
return ( return (
<> <>
<Header activeId={null} searchQuery={query} searchType={type} /> <Header searchQuery={query} searchType={type} />
<Sidebar {...sidebar} /> <Sidebar {...sidebar} searchQuery={query} searchType={type} />
<main className="article list"> <main className="article list">
<div className="page-header"> <div className="page-header">
<h2>{`Found ${ <h2>{`Found ${

View file

@ -3,8 +3,8 @@ import { getDb } from '@/utils/ContentDatabase'
import { ArticleDetails } from './ArticleDetails' import { ArticleDetails } from './ArticleDetails'
import { ArticleNavLinks } from './ArticleNavLinks' import { ArticleNavLinks } from './ArticleNavLinks'
import { Breadcrumb } from './Breadcrumb' import { Breadcrumb } from './Breadcrumb'
import ExampleCodeBlock from './ExampleCodeBlock'
import { Header } from './Header' import { Header } from './Header'
import { HeadingLinks } from './HeadingLinks'
import { Mdx } from './Mdx' import { Mdx } from './Mdx'
import { Sidebar } from './Sidebar' import { Sidebar } from './Sidebar'
import { Image } from './mdx-components/generic' import { Image } from './mdx-components/generic'
@ -21,23 +21,34 @@ export async function ArticleDocsPage({ article }: { article: Article }) {
articleId: article.id, articleId: article.id,
}) })
const isGenerated = article.sectionId === 'gen' const isGenerated = article.sectionId === 'reference'
return ( return (
<> <>
<Header activeId={article.id} /> <Header sectionId={section.id} />
<Sidebar {...sidebar} /> <Sidebar headings={headings} {...sidebar} />
<main className={`article${isGenerated ? ' article__api-docs' : ''}`}> <main
className={`article${isGenerated ? ' article__api-docs' : ''}${article.componentCode ? ' article__example' : ''}`}
>
<div className="page-header"> <div className="page-header">
<Breadcrumb section={section} category={category} /> <Breadcrumb section={section} category={category} />
<h1>{article.title}</h1> <h1>{article.title}</h1>
</div> </div>
{article.hero && <Image alt="hero" title={article.title} src={`images/${article.hero}`} />} {article.hero && <Image alt="hero" title={article.title} src={`images/${article.hero}`} />}
{article.content && <Mdx content={article.content} />} {article.content && <Mdx content={article.content} />}
{article.componentCode && (
<ExampleCodeBlock
articleId={article.id}
files={{
'App.tsx': article.componentCode,
...(article.componentCodeFiles ? JSON.parse(article.componentCodeFiles) : null),
}}
activeFile={'App.tsx'}
/>
)}
{isGenerated ? null : <ArticleDetails article={article} />} {isGenerated ? null : <ArticleDetails article={article} />}
{links && <ArticleNavLinks links={links} />} {links && <ArticleNavLinks links={links} />}
</main> </main>
{headings.length > 0 ? <HeadingLinks article={article} headingLinks={headings} /> : null}
</> </>
) )
} }

View file

@ -13,7 +13,7 @@ export async function CategoryDocsPage({ category }: { category: Category }) {
return ( return (
<> <>
<Header activeId={category.id} /> <Header sectionId={section.id} />
<Sidebar {...sidebar} /> <Sidebar {...sidebar} />
<main className={'article'}> <main className={'article'}>
<div className="page-header"> <div className="page-header">
@ -23,9 +23,11 @@ export async function CategoryDocsPage({ category }: { category: Category }) {
{articles.length > 0 && ( {articles.length > 0 && (
<ul> <ul>
{articles.map((article) => ( {articles.map((article) => (
<li>
<Link key={article.id} href={`/${section.id}/${category.id}/${article.id}`}> <Link key={article.id} href={`/${section.id}/${category.id}/${article.id}`}>
<li>{article.title}</li> {article.title}
</Link> </Link>
</li>
))} ))}
</ul> </ul>
)} )}

View file

@ -0,0 +1,52 @@
'use client'
import { SandpackCodeEditor, SandpackFiles, SandpackProvider } from '@codesandbox/sandpack-react'
import { useTheme } from 'next-themes'
import { useEffect, useState } from 'react'
export default function ExampleCodeBlock({
articleId,
files = {},
activeFile,
}: {
articleId: string
activeFile: string
files: SandpackFiles
}) {
const [isClientSide, setIsClientSide] = useState(false)
const { theme } = useTheme()
useEffect(() => setIsClientSide(true), [])
const SERVER =
process.env.NODE_ENV === 'development' ? 'http://localhost:5420' : 'https://examples.tldraw.com'
// This is to avoid hydration mismatch between the server and the client because of the useTheme.
if (!isClientSide) {
return null
}
return (
<>
<iframe
src={`${SERVER}/${articleId}/full`}
style={{ border: 0, height: '50vh', width: '100%' }}
/>
<SandpackProvider
key={`sandpack-${theme}-${activeFile}`}
template="react-ts"
options={{ activeFile }}
customSetup={{
dependencies: {
'@tldraw/assets': 'latest',
'@tldraw/tldraw': 'latest',
},
}}
files={{
...files,
}}
theme={theme === 'dark' ? 'dark' : 'light'}
>
<SandpackCodeEditor readOnly />
</SandpackProvider>
</>
)
}

View file

@ -15,7 +15,7 @@ export function Footer() {
WebkitMask: `url(/tldraw-icon.svg) center 100% / 100% no-repeat`, WebkitMask: `url(/tldraw-icon.svg) center 100% / 100% no-repeat`,
}} }}
/> />
<p>tldraw © 2024</p> <p>tldraw © {new Date().getFullYear()}</p>
</a> </a>
<div className="footer__socials"> <div className="footer__socials">
<a <a

View file

@ -1,20 +1,24 @@
'use client'
import * as NavigationMenu from '@radix-ui/react-navigation-menu'
import Link from 'next/link' import Link from 'next/link'
import { Icon } from './Icon' import { Icon } from './Icon'
import { Chevron } from './Icons'
import { Search } from './Search' import { Search } from './Search'
import { ThemeSwitcher } from './ThemeSwitcher' import { ThemeSwitcher } from './ThemeSwitcher'
export function Header({ export function Header({
activeId,
searchQuery, searchQuery,
searchType, searchType,
sectionId,
}: { }: {
activeId: string | null
searchQuery?: string searchQuery?: string
searchType?: string searchType?: string
sectionId?: string
}) { }) {
return ( return (
<div className="layout__header"> <div className="layout__header">
<Link href="/"> <Link href="/quick-start">
<div <div
className="lockup" className="lockup"
style={{ style={{
@ -23,14 +27,16 @@ export function Header({
}} }}
/> />
</Link> </Link>
<Search activeId={activeId} prevQuery={searchQuery} prevType={searchType} /> <Search prevQuery={searchQuery} prevType={searchType} />
<div className="layout__header__socials"> <div className="layout__header__sections_and_socials">
<SectionLinks sectionId={sectionId} />
<ThemeSwitcher />
<a <a
href="https://twitter.com/tldraw" href="https://discord.com/invite/SBBEVCA4PG"
className="sidebar__button icon-button" className="sidebar__button icon-button"
title="twitter" title="discord"
> >
<Icon icon="twitter" /> <Icon icon="discord" />
</a> </a>
<a <a
href="https://github.com/tldraw/tldraw" href="https://github.com/tldraw/tldraw"
@ -39,15 +45,84 @@ export function Header({
> >
<Icon icon="github" /> <Icon icon="github" />
</a> </a>
<a
href="https://discord.com/invite/SBBEVCA4PG"
className="sidebar__button icon-button"
title="discord"
>
<Icon icon="discord" />
</a>
<ThemeSwitcher />
</div> </div>
</div> </div>
) )
} }
export function SectionLinks({ sectionId }: { sectionId?: string | null }) {
return (
<>
<a
href="/quick-start"
title="Learn"
data-active={!['reference', 'examples'].includes(sectionId || '')}
className="layout_header__section"
>
Learn
</a>
<a
href="/reference/editor/Editor"
title="Reference"
data-active={sectionId === 'reference'}
className="layout_header__section"
>
Reference
</a>
<a
href="/examples/basic/develop"
title="Examples"
data-active={sectionId === 'examples'}
className="layout_header__section"
>
Examples
</a>
<NavigationMenu.Root className="NavigationMenuRoot">
<NavigationMenu.List className="NavigationMenuList">
<NavigationMenu.Item>
<NavigationMenu.Trigger
className="NavigationMenuTrigger"
onPointerMove={(event) => event.preventDefault()}
onPointerLeave={(event) => event.preventDefault()}
>
<span>
Community <Chevron className="CaretDown" aria-hidden />
</span>
</NavigationMenu.Trigger>
<NavigationMenu.Content
className="NavigationMenuContent"
onPointerMove={(event) => event.preventDefault()}
onPointerLeave={(event) => event.preventDefault()}
>
<ul>
<li>
<NavigationMenu.Link asChild>
<a href="/community/contributing">Contributing</a>
</NavigationMenu.Link>
</li>
<li>
<NavigationMenu.Link asChild>
<a href="/community/translations">Translations</a>
</NavigationMenu.Link>
</li>
<li>
<NavigationMenu.Link asChild>
<a href="/community/license">License</a>
</NavigationMenu.Link>
</li>
<li>
<NavigationMenu.Link asChild>
<a href="https://discord.com/invite/SBBEVCA4PG" target="_blank">
Discord
</a>
</NavigationMenu.Link>
</li>
</ul>
</NavigationMenu.Content>
</NavigationMenu.Item>
</NavigationMenu.List>
</NavigationMenu.Root>
</>
)
}

View file

@ -1,43 +0,0 @@
/* eslint-disable no-useless-escape */
import { Article, ArticleHeadings } from '@/types/content-types'
import Link from 'next/link'
export function HeadingLinks({
article,
headingLinks,
}: {
article: Article
headingLinks: ArticleHeadings
}) {
return (
<nav className="layout__headings">
<ul className="sidebar__list sidebar__sections__list" key={article.id}>
<li className="sidebar__section">
<div className="sidebar__section__title" data-active={false}>
On this page
</div>
<ul className="sidebar__list">
{headingLinks
.filter((heading) => heading.level < 4)
.map((heading) => (
<li className="sidebar__article" key={heading.slug}>
<Link href={`#${heading.slug}`} className="sidebar__link">
{heading.level > 2 ? (
<span className="sidebar__link__indent">{' '}</span>
) : null}
<span className="sidebar__link__title">
{heading.isCode ? (
<code>{heading.title.replace(/\[([^\]]+)\]\([^\)]+\)/g, '$1')}</code>
) : (
heading.title.replace(/\[([^\]]+)\]\([^\)]+\)/g, '$1')
)}
</span>
</Link>
</li>
))}
</ul>
</li>
</ul>
</nav>
)
}

View file

@ -0,0 +1,22 @@
import classNames from 'classnames'
export function Chevron({ className }: { className?: string }) {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={classNames('accordion__trigger__chevron', className)}
>
<path
d="M4 6L8 10L12 6"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}

View file

@ -9,7 +9,6 @@ export function Search({
prevType = 'n', prevType = 'n',
prevQuery = '', prevQuery = '',
}: { }: {
activeId: string | null
prevType?: string prevType?: string
prevQuery?: string prevQuery?: string
}) { }) {

View file

@ -1,65 +1,21 @@
import { Article, Category, Section } from '@/types/content-types' import { Section } from '@/types/content-types'
import { getDb } from '@/utils/ContentDatabase' import { getDb } from '@/utils/ContentDatabase'
import Link from 'next/link'
import { Header } from './Header' import { Header } from './Header'
import { Sidebar } from './Sidebar' import { Sidebar } from './Sidebar'
export async function SectionDocsPage({ section }: { section: Section }) { export async function SectionDocsPage({ section }: { section: Section }) {
const db = await getDb() const db = await getDb()
const sidebar = await db.getSidebarContentList({ sectionId: section.id }) const sidebar = await db.getSidebarContentList({ sectionId: section.id })
const categories = [] as Category[]
const articles: Article[] = []
const sectionCategories = await db.getCategoriesForSection(section.id)
for (const category of sectionCategories) {
if (category.id === section.id + '_ucg') {
const categoryArticles = await db.getCategoryArticles(section.id, category.id)
for (const article of categoryArticles) {
articles.push(article)
}
} else {
// If the count of articles for this category is greater than zero...
const articleCount = await db.getCategoryArticlesCount(section.id, category.id)
if (articleCount > 0) {
categories.push(category)
}
}
}
return ( return (
<> <>
<Header activeId={section.id} /> <Header sectionId={section.id} />
<Sidebar {...sidebar} /> <Sidebar {...sidebar} />
<main className="article"> <main className="article">
<div className="page-header"> <div className="page-header">
<h1>{section.title}</h1> <h1>{section.title}</h1>
</div> </div>
{articles.length > 0 && ( Choose your adventure on the left.
<ul>
{articles.map((articleLink) => {
return (
<Link key={articleLink.id} href={articleLink.path!}>
<li>{articleLink.title}</li>
</Link>
)
})}
</ul>
)}
{categories.length > 0 && (
<ul>
{categories.map((category) =>
category.id === 'ucg' ? null : (
<Link key={category.id} href={`/${section.id}/${category.id}`}>
{category.id === 'ucg' ? null : <li>{category.title}</li>}
</Link>
)
)}
</ul>
)}
</main> </main>
</> </>
) )

View file

@ -1,65 +1,97 @@
'use client' 'use client'
import { import {
ArticleHeadings,
SidebarContentArticleLink, SidebarContentArticleLink,
SidebarContentCategoryLink, SidebarContentCategoryLink,
SidebarContentLink, SidebarContentLink,
SidebarContentList, SidebarContentList,
SidebarContentSectionLink, SidebarContentSectionLink,
} from '@/types/content-types' } from '@/types/content-types'
import * as Accordion from '@radix-ui/react-accordion'
import Link from 'next/link' import Link from 'next/link'
import { usePathname } from 'next/navigation' import { usePathname } from 'next/navigation'
import { createContext, useContext, useEffect } from 'react' import { createContext, useContext, useEffect } from 'react'
import { SectionLinks } from './Header'
import { Chevron } from './Icons'
import { Search } from './Search' import { Search } from './Search'
import { SidebarCloseButton } from './SidebarCloseButton' import { SidebarCloseButton } from './SidebarCloseButton'
import { ToggleMenuButton } from './ToggleMenuButton' import { ToggleMenuButton } from './ToggleMenuButton'
type SidebarProps = SidebarContentList type SidebarProps = SidebarContentList
const activeLinkContext = createContext<string | null>(null) const linkContext = createContext<{
activeId: string | null
articleId: string | null
categoryId: string | null
sectionId: string | null
} | null>(null)
export function Sidebar({ links, sectionId, categoryId, articleId }: SidebarProps) { export function Sidebar({
headings,
links,
sectionId,
categoryId,
articleId,
searchQuery,
searchType,
}: SidebarProps) {
const activeId = articleId ?? categoryId ?? sectionId const activeId = articleId ?? categoryId ?? sectionId
const pathName = usePathname() const pathName = usePathname()
useEffect(() => { useEffect(() => {
document.body.classList.remove('sidebar-open') document.body.classList.remove('sidebar-open')
document.querySelector('.sidebar [data-active=true]')?.scrollIntoView({ block: 'center' })
// XXX(mime): scrolling the sidebar into position also scrolls the page to the wrong
// spot. this compensates for that but, ugh.
document.documentElement.scrollTop = 0
}, [pathName]) }, [pathName])
return ( return (
<> <>
<activeLinkContext.Provider value={activeId}> <linkContext.Provider value={{ activeId, articleId, categoryId, sectionId }}>
<div className="sidebar" onScroll={(e) => e.stopPropagation()}> <div className="sidebar" onScroll={(e) => e.stopPropagation()}>
<Search activeId={activeId} /> <Search prevQuery={searchQuery} prevType={searchType} />
<SidebarLinks links={links} /> <div className="sidebar__section__links">
<SectionLinks sectionId={sectionId} />
</div>
<SidebarLinks headings={headings} links={links} />
<SidebarCloseButton /> <SidebarCloseButton />
</div> </div>
<ToggleMenuButton /> <ToggleMenuButton />
</activeLinkContext.Provider> </linkContext.Provider>
</> </>
) )
} }
export function SidebarLinks({ links }: { links: SidebarContentLink[] }) { export function SidebarLinks({
headings,
links,
}: {
headings?: ArticleHeadings
links: SidebarContentLink[]
}) {
return ( return (
<nav className="sidebar__nav"> <nav className="sidebar__nav">
<ul className="sidebar__list sidebar__sections__list"> <ul className="sidebar__list sidebar__sections__list">
{links.map((link) => ( {links.map((link) => (
<SidebarLink key={link.url} {...link} /> <SidebarLink key={link.url} headings={headings} {...link} />
))} ))}
</ul> </ul>
</nav> </nav>
) )
} }
function SidebarLink(props: SidebarContentLink) { function SidebarLink({ headings, ...props }: SidebarContentLink & { headings?: ArticleHeadings }) {
switch (props.type) { switch (props.type) {
case 'section': { case 'section': {
return <SidebarSection {...props} /> return <SidebarSection headings={headings} {...props} />
} }
case 'article': { case 'article': {
return <SidebarArticle {...props} /> return <SidebarArticle headings={headings} {...props} />
} }
case 'category': { case 'category': {
return <SidebarCategory {...props} /> return <SidebarCategory {...props} />
@ -67,19 +99,19 @@ function SidebarLink(props: SidebarContentLink) {
} }
} }
function SidebarSection({ title, children }: SidebarContentSectionLink) { function SidebarSection({
title,
children,
headings,
}: SidebarContentSectionLink & { headings?: ArticleHeadings }) {
if (children.length === 0) return null if (children.length === 0) return null
return ( return (
<li className="sidebar__section"> <li className="sidebar__section">
{title && ( {title && <span className="sidebar__section__title">{title}</span>}
<Link href={children[0].url} title={title} className="sidebar__section__title">
{title}
</Link>
)}
<ul className="sidebar__list"> <ul className="sidebar__list">
{children.map((link) => ( {children.map((link) => (
<SidebarLink key={link.url} {...link} /> <SidebarLink key={link.url} headings={headings} {...link} />
))} ))}
</ul> </ul>
</li> </li>
@ -87,10 +119,56 @@ function SidebarSection({ title, children }: SidebarContentSectionLink) {
} }
function SidebarCategory({ title, children }: SidebarContentCategoryLink) { function SidebarCategory({ title, children }: SidebarContentCategoryLink) {
const linkCtx = useContext(linkContext)
if (children.length === 0) return null if (children.length === 0) return null
const hasGroups = children.some((child) => !!(child as SidebarContentArticleLink).groupId)
const activeArticle = children.find(
(child) => (child as SidebarContentArticleLink).articleId === linkCtx?.articleId
)
const activeGroup = activeArticle && (activeArticle as SidebarContentArticleLink).groupId
const groups = ['Class', 'Function', 'Variable', 'Interface', 'Enum', 'TypeAlias', 'Namespace']
return ( return (
<li className="sidebar__category"> <li className="sidebar__category">
{hasGroups ? (
<>
<span className="sidebar__link">{title}</span>
<Accordion.Root
type="multiple"
defaultValue={[`${linkCtx?.categoryId}-${activeGroup}-${linkCtx?.articleId}`]}
>
{groups.map((group) => {
const articles = children.filter(
(child) => (child as SidebarContentArticleLink).groupId === group
)
if (articles.length === 0) return null
return (
<Accordion.Item
key={group}
value={`${linkCtx?.categoryId}-${group}-${linkCtx?.articleId}`}
>
<Accordion.Trigger
className="sidebar__section__group__title"
style={{ marginLeft: '8px', paddingRight: '8px' }}
>
{group}
<Chevron />
</Accordion.Trigger>
<Accordion.Content>
<ul className="sidebar__list" style={{ paddingLeft: '8px' }}>
{articles.map((link) => (
<SidebarLink key={link.url} {...link} />
))}
</ul>
</Accordion.Content>
</Accordion.Item>
)
})}
</Accordion.Root>
</>
) : (
<>
<Link href={children[0].url} title={title} className="sidebar__link"> <Link href={children[0].url} title={title} className="sidebar__link">
{title} {title}
</Link> </Link>
@ -99,21 +177,44 @@ function SidebarCategory({ title, children }: SidebarContentCategoryLink) {
<SidebarLink key={link.url} {...link} /> <SidebarLink key={link.url} {...link} />
))} ))}
</ul> </ul>
</>
)}
<hr /> <hr />
</li> </li>
) )
} }
function SidebarArticle({ title, url, articleId }: SidebarContentArticleLink) { function SidebarArticle({
const isActive = useContext(activeLinkContext) === articleId title,
url,
articleId,
headings,
}: SidebarContentArticleLink & { headings?: ArticleHeadings }) {
const isActive = useContext(linkContext)?.activeId === articleId
return ( return (
<li className="sidebar__article"> <li className="sidebar__article">
<Link href={url}> <Link href={url} className="sidebar__link" data-active={isActive}>
<div className="sidebar__link" data-active={isActive}>
{title} {title}
</div>
</Link> </Link>
{isActive && (
<ul className="sidebar__list">
{headings
?.filter((heading) => heading.level < 3)
.map((heading) => (
<li key={heading.slug}>
<Link href={`#${heading.slug}`} className="sidebar__link">
{heading.isCode ? (
<code>{heading.title.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')}</code>
) : (
heading.title.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
)}
</Link>
</li>
))}
</ul>
)}
</li> </li>
) )
} }

View file

@ -1,15 +0,0 @@
---
title: Examples
status: published
author: steveruizok
date: 10/11/2023
order: 3
---
Here's a [very simple sandbox](https://codesandbox.io/s/tldraw-example-canary-2lrzmy) showing the tldraw component.
<Embed src="https://codesandbox.io/embed/2lrzmy?view=Editor+%2B+Preview&module=%2Fsrc%2Fapp.tsx" />
You can also find simple [framework-specific examples](https://github.com/tldraw/examples). For a multiplayer example using a collaboration backend, see our [Yjs example](https://github.com/tldraw/tldraw-yjs-example).
For more specific examples of how to use the tldraw component, check out the [tldraw repository's examples app](https://github.com/tldraw/tldraw/blob/main/apps/examples/src). The [tldraw repo](https://github.com/tldraw/tldraw) has more information about running the examples locally.

View file

@ -31,3 +31,169 @@ yarn add @tldraw/tldraw@canary
```bash ```bash
npm install @tldraw/tldraw@canary npm install @tldraw/tldraw@canary
``` ```
## Usage
You can use the [Tldraw](?) component inside of any React component.
```tsx
import { Tldraw } from '@tldraw/tldraw'
import '@tldraw/tldraw/tldraw.css'
export default function () {
return (
<div style={{ position: 'fixed', inset: 0 }}>
<Tldraw />
</div>
)
}
```
### Wrapper
It's important that the [Tldraw](?) component is wrapped in a parent container that has an explicit size. Its height and width are set to `100%`, so it will fill its parent container.
### CSS
In addition to the [Tldraw](?) component itself, you should also import the `tldraw.css` file from the `@tldraw/tldraw` package.
```ts
import '@tldraw/tldraw/tldraw.css'
```
You can alternatively import this file inside of another CSS file using the `@import` syntax.
```css
@import url('@tldraw/tldraw/tldraw.css');
```
If you'd like to deeply change the way that tldraw looks, you can copy the `tldraw.css` file into a new CSS file, make your changes, and import that instead.
### Fonts
We also use Inter as the default tldraw font. You can import this font however you like (or use a different font!) but here's the CSS import from Google fonts that we use:
```css
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap');
```
### HTML
If you're using the [Tldraw](?) component in a full-screen app, you probably also want to update your `index.html`'s meta viewport element as shown below.
```html
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
```
This may not be critical to [Tldraw](?) performing correctly, however some features (such as safe area positioning) will only work correctly if these viewport options are set.
## Server Rendering
The [Tldraw](?) component can't be server-rendered. If you're using the component in a server-rendered framework (such as Next.js) then you need to import it dynamically.
```tsx
const Tldraw = dynamic(async () => (await import('@tldraw/tldraw')).Tldraw, { ssr: false })
```
### Using a bundler
If you're using a bundler like webpack or rollup, you can import the assets directly from the `@tldraw/assets` package. Here you can use `getAssetUrlsByMetaUrl` helper function:
```javascript
import { getAssetUrlsByMetaUrl } from '@tldraw/assets/urls'
const assetUrls = getAssetUrlsByMetaUrl()
<Tldraw assetUrls={assetUrls} />
```
## Usage in Frameworks
Visit our [framework examples repository](https://github.com/tldraw/examples) to see examples of tldraw being used in various frameworks.
## Static Assets
In order to use the [Tldraw](?) component, the app must be able to find certain assets. These are contained in the `embed-icons`, `fonts`, `icons`, and `translations` folders. We offer a few different ways of making these assets available to your app.
### Using a public CDN
By default we serve these assets from a [public CDN called unpkg](https://unpkg.com/browse/@tldraw/assets@2.0.0-alpha.12/), so everything should work out of the box and is a good way to get started.
If you would like to customize some of the assets you can pass the customizations to our [Tldraw](?) component. For example, to use a custom icon for the `hand` tool you can do the following:
```javascript
const assetUrls = {
icons: {
'tool-hand': './custom-tool-hand.svg',
},
}
<Tldraw assetUrls={assetUrls} />
```
This will use the custom icon for the `hand` tool and the default assets for everything else.
### Self-hosting static assets
If you want more flexibility you can also host these assets yourself:
1. Download the `embed-icons`, `fonts`, `icons`, and `translations` folders from the [assets folder](https://github.com/tldraw/tldraw/tree/main/assets) of the tldraw repository.
2. Place the folders in your project's public path.
3. Pass `assetUrls` prop to our `<Tldraw/>` component to let the component know where the assets live.
You can use our `getAssetUrls` helper function from the `@tldraw/assets` package to generate these urls for you.
```javascript
import { getAssetUrls } from '@tldraw/assets/selfHosted'
const assetUrls = getAssetUrls()
<Tldraw assetUrls={assetUrls} />
```
While these files must be available, you can overwrite the individual files: for example, by placing different icons under the same name or modifying / adding translations.
If you use a CDN for hosting these files you can specify the base url of your assets. To recreate the above option of serving the assets from unpkg you would do the following:
```javascript
const assetUrls = getAssetUrls({
baseUrl: 'https://unpkg.com/@tldraw/assets@2.0.0-alpha.12/',
})
```
## Subcomponents
The [Tldraw](?) component combines two lower-level components: [TldrawEditor](?) and [TldrawUi](?). If you want to have more granular control, you can use those lower-level components directly. See [this example](https://github.com/tldraw/tldraw/blob/main/apps/examples/src/examples/ExplodedExample.tsx) for reference.
### Customize the default components
You can customize the appearance of the tldraw editor using the [Tldraw](?) (or [TldrawEditor](?) component's `components` prop.
```ts
<Tldraw
components={{
Background: YourCustomBackground,
SvgDefs: YourCustomSvgDefs,
Brush: YourCustomBrush,
ZoomBrush: YourCustomBrush,
CollaboratorBrush: YourCustomBrush,
Cursor: YourCustomCursor,
CollaboratorCursor: YourCustomCursor,
CollaboratorHint: YourCustomCollaboratorHint,
CollaboratorShapeIndicator: YourCustomdicator,
Grid: YourCustomGrid,
Scribble: YourCustomScribble,
SnapLine: YourCustomSnapLine,
Handles: YourCustomHandles,
Handle: YourCustomHandle,
CollaboratorScribble: YourCustomScribble,
ErrorFallback: YourCustomErrorFallback,
ShapeErrorFallback: YourCustomShapeErrorFallback,
ShapeIndicatorErrorFallback: YourCustomShapeIndicatorErrorFallback,
Spinner: YourCustomSpinner,
SelectionBackground: YourCustomSelectionBackground,
SelectionForeground: YourCustomSelectionForeground,
HoveredShapeIndicator: YourCustomHoveredShapeIndicator,
}}
/>
```

View file

@ -6,7 +6,7 @@ date: 3/22/2023
order: 0 order: 0
--- ---
Welcome to the developer docs for tldraw, a React library for creating whiteboards and other infinite canvas experiences. These docs are for the 2.0 version which is currently in alpha. Welcome to the developer docs for tldraw, a React library for creating whiteboards and other infinite canvas experiences. These docs are for the 2.0 version which is currently in beta.
<a className="hero__images" href="https://examples.tldraw.com"> <a className="hero__images" href="https://examples.tldraw.com">
<img src={'/images/hero_light.png'} alt="tldraw demo" className="article__image hero__light" /> <img src={'/images/hero_light.png'} alt="tldraw demo" className="article__image hero__light" />
@ -31,7 +31,7 @@ If you want to go even deeper, you can use the [TldrawEditor](?) component as a
In addition to the docs on this website, we provide [many examples](https://github.com/tldraw/tldraw/tree/main/apps/examples/src/examples) in the tldraw repo that demonstrate different ways of using the tldraw library. You can view them running [here](https://examples.tldraw.com/); or else you can clone the tldraw repo and start a local development server to see them in action. In addition to the docs on this website, we provide [many examples](https://github.com/tldraw/tldraw/tree/main/apps/examples/src/examples) in the tldraw repo that demonstrate different ways of using the tldraw library. You can view them running [here](https://examples.tldraw.com/); or else you can clone the tldraw repo and start a local development server to see them in action.
You can ask questions and get help on our [Discord](https://discord.gg/JMbeb96jsh) channel or in our Github [issues](https://github.com/tldraw/tldraw/issues). You can ask questions and get help on our [Discord](https://discord.com/invite/SBBEVCA4PG) channel or in our Github [issues](https://github.com/tldraw/tldraw/issues).
## Community ## Community

View file

@ -1,171 +0,0 @@
---
title: Usage
status: published
author: steveruizok
date: 3/22/2023
order: 2
---
You can use the [Tldraw](?) component inside of any React component.
```tsx
import { Tldraw } from '@tldraw/tldraw'
import '@tldraw/tldraw/tldraw.css'
export default function () {
return (
<div style={{ position: 'fixed', inset: 0 }}>
<Tldraw />
</div>
)
}
```
### Wrapper
It's important that the [Tldraw](?) component is wrapped in a parent container that has an explicit size. Its height and width are set to `100%`, so it will fill its parent container.
### CSS
In addition to the [Tldraw](?) component itself, you should also import the `tldraw.css` file from the `@tldraw/tldraw` package.
```ts
import '@tldraw/tldraw/tldraw.css'
```
You can alternatively import this file inside of another CSS file using the `@import` syntax.
```css
@import url('@tldraw/tldraw/tldraw.css');
```
If you'd like to deeply change the way that tldraw looks, you can copy the `tldraw.css` file into a new CSS file, make your changes, and import that instead.
### Fonts
We also use Inter as the default tldraw font. You can import this font however you like (or use a different font!) but here's the CSS import from Google fonts that we use:
```css
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap');
```
### HTML
If you're using the [Tldraw](?) component in a full-screen app, you probably also want to update your `index.html`'s meta viewport element as shown below.
```html
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
```
This may not be critical to [Tldraw](?) performing correctly, however some features (such as safe area positioning) will only work correctly if these viewport options are set.
## Server Rendering
The [Tldraw](?) component can't be server-rendered. If you're using the component in a server-rendered framework (such as Next.js) then you need to import it dynamically.
```tsx
const Tldraw = dynamic(async () => (await import('@tldraw/tldraw')).Tldraw, { ssr: false })
```
### Using a bundler
If you're using a bundler like webpack or rollup, you can import the assets directly from the `@tldraw/assets` package. Here you can use `getAssetUrlsByMetaUrl` helper function:
```javascript
import { getAssetUrlsByMetaUrl } from '@tldraw/assets/urls'
const assetUrls = getAssetUrlsByMetaUrl()
<Tldraw assetUrls={assetUrls} />
```
## Usage in Frameworks
Visit our [framework examples repository](https://github.com/tldraw/examples) to see examples of tldraw being used in various frameworks.
## Static Assets
In order to use the [Tldraw](?) component, the app must be able to find certain assets. These are contained in the `embed-icons`, `fonts`, `icons`, and `translations` folders. We offer a few different ways of making these assets available to your app.
### Using a public CDN
By default we serve these assets from a [public CDN called unpkg](https://unpkg.com/browse/@tldraw/assets@2.0.0-alpha.12/), so everything should work out of the box and is a good way to get started.
If you would like to customize some of the assets you can pass the customizations to our [Tldraw](?) component. For example, to use a custom icon for the `hand` tool you can do the following:
```javascript
const assetUrls = {
icons: {
'tool-hand': './custom-tool-hand.svg',
},
}
<Tldraw assetUrls={assetUrls} />
```
This will use the custom icon for the `hand` tool and the default assets for everything else.
### Self-hosting static assets
If you want more flexibility you can also host these assets yourself:
1. Download the `embed-icons`, `fonts`, `icons`, and `translations` folders from the [assets folder](https://github.com/tldraw/tldraw/tree/main/assets) of the tldraw repository.
2. Place the folders in your project's public path.
3. Pass `assetUrls` prop to our `<Tldraw/>` component to let the component know where the assets live.
You can use our `getAssetUrls` helper function from the `@tldraw/assets` package to generate these urls for you.
```javascript
import { getAssetUrls } from '@tldraw/assets/selfHosted'
const assetUrls = getAssetUrls()
<Tldraw assetUrls={assetUrls} />
```
While these files must be available, you can overwrite the individual files: for example, by placing different icons under the same name or modifying / adding translations.
If you use a CDN for hosting these files you can specify the base url of your assets. To recreate the above option of serving the assets from unpkg you would do the following:
```javascript
const assetUrls = getAssetUrls({
baseUrl: 'https://unpkg.com/@tldraw/assets@2.0.0-alpha.12/',
})
```
## Subcomponents
The [Tldraw](?) component combines two lower-level components: [TldrawEditor](?) and [TldrawUi](?). If you want to have more granular control, you can use those lower-level components directly. See [this example](https://github.com/tldraw/tldraw/blob/main/apps/examples/src/examples/ExplodedExample.tsx) for reference.
### Customize the default components
You can customize the appearance of the tldraw editor using the [Tldraw](?) (or [TldrawEditor](?) component's `components` prop.
```ts
<Tldraw
components={{
Background: YourCustomBackground,
SvgDefs: YourCustomSvgDefs,
Brush: YourCustomBrush,
ZoomBrush: YourCustomBrush,
CollaboratorBrush: YourCustomBrush,
Cursor: YourCustomCursor,
CollaboratorCursor: YourCustomCursor,
CollaboratorHint: YourCustomCollaboratorHint,
CollaboratorShapeIndicator: YourCustomdicator,
Grid: YourCustomGrid,
Scribble: YourCustomScribble,
SnapLine: YourCustomSnapLine,
Handles: YourCustomHandles,
Handle: YourCustomHandle,
CollaboratorScribble: YourCustomScribble,
ErrorFallback: YourCustomErrorFallback,
ShapeErrorFallback: YourCustomShapeErrorFallback,
ShapeIndicatorErrorFallback: YourCustomShapeIndicatorErrorFallback,
Spinner: YourCustomSpinner,
SelectionBackground: YourCustomSelectionBackground,
SelectionForeground: YourCustomSelectionForeground,
HoveredShapeIndicator: YourCustomHoveredShapeIndicator,
}}
/>
```

View file

@ -8,3 +8,4 @@ status: published
--- ---
[View on GitHub](https://github.com/tldraw/tldraw/releases/tag/v2.0.0-alpha.12) [View on GitHub](https://github.com/tldraw/tldraw/releases/tag/v2.0.0-alpha.12)

View file

@ -296,6 +296,8 @@ n/a
#### Select locked shapes on long press ([#1529](https://github.com/tldraw/tldraw/pull/1529)) #### Select locked shapes on long press ([#1529](https://github.com/tldraw/tldraw/pull/1529))
#### highlighter fixes ([#1530](https://github.com/tldraw/tldraw/pull/1530)) #### highlighter fixes ([#1530](https://github.com/tldraw/tldraw/pull/1530))
[aq bug fixes] [aq bug fixes]
@ -453,8 +455,8 @@ Highlighter pen is here! 🎉🎉🎉
#### Update docs links + guides + build ([#1422](https://github.com/tldraw/tldraw/pull/1422)) #### Update docs links + guides + build ([#1422](https://github.com/tldraw/tldraw/pull/1422))
- [docs] Updated guides to get assets from the new `tldraw/tldraw` repo instead of the old `tldraw/tldraw-examples`. * [docs] Updated guides to get assets from the new `tldraw/tldraw` repo instead of the old `tldraw/tldraw-examples`.
- [docs] Updated an old CodeSandbox link to the new StackBlitz. * [docs] Updated an old CodeSandbox link to the new StackBlitz.
#### Create @tldraw/indices package ([#1426](https://github.com/tldraw/tldraw/pull/1426)) #### Create @tldraw/indices package ([#1426](https://github.com/tldraw/tldraw/pull/1426))

View file

@ -9,6 +9,7 @@ status: published
[View on GitHub](https://github.com/tldraw/tldraw/releases/tag/v2.0.0-alpha.14) [View on GitHub](https://github.com/tldraw/tldraw/releases/tag/v2.0.0-alpha.14)
#### Disable styles panel button on mobile when using the laser tool. ([#1704](https://github.com/tldraw/tldraw/pull/1704)) #### Disable styles panel button on mobile when using the laser tool. ([#1704](https://github.com/tldraw/tldraw/pull/1704))
- Disable the styles panel button for laser tool on mobile. - Disable the styles panel button for laser tool on mobile.

View file

@ -9,6 +9,7 @@ status: published
[View on GitHub](https://github.com/tldraw/tldraw/releases/tag/v2.0.0-alpha.15) [View on GitHub](https://github.com/tldraw/tldraw/releases/tag/v2.0.0-alpha.15)
#### frame label fix ([#2016](https://github.com/tldraw/tldraw/pull/2016)) #### frame label fix ([#2016](https://github.com/tldraw/tldraw/pull/2016))
- Add a brief release note for your PR here. - Add a brief release note for your PR here.
@ -35,14 +36,8 @@ This pr add the custom defined shapes that's being passed to Tldraw
Before/After Before/After
<img <img width="300" src="https://github.com/tldraw/tldraw/assets/98838967/91ea55c8-0fcc-4f73-b61e-565829a5f25e" />
width="300" <img width="300" src="https://github.com/tldraw/tldraw/assets/98838967/ee4070fe-e236-4818-8fb4-43520210102b" />
src="https://github.com/tldraw/tldraw/assets/98838967/91ea55c8-0fcc-4f73-b61e-565829a5f25e"
/>
<img
width="300"
src="https://github.com/tldraw/tldraw/assets/98838967/ee4070fe-e236-4818-8fb4-43520210102b"
/>
#### [fix] pinch events ([#1979](https://github.com/tldraw/tldraw/pull/1979)) #### [fix] pinch events ([#1979](https://github.com/tldraw/tldraw/pull/1979))
@ -54,14 +49,8 @@ Before/After
Before/After Before/After
<image <image width="350" src="https://github.com/tldraw/tldraw/assets/98838967/320171b4-61e0-4a41-b8d3-830bd90bea65" />
width="350" <image width="350" src="https://github.com/tldraw/tldraw/assets/98838967/b42d7156-0ce9-4894-9692-9338dc931b79" />
src="https://github.com/tldraw/tldraw/assets/98838967/320171b4-61e0-4a41-b8d3-830bd90bea65"
/>
<image
width="350"
src="https://github.com/tldraw/tldraw/assets/98838967/b42d7156-0ce9-4894-9692-9338dc931b79"
/>
#### Remove focus management ([#1953](https://github.com/tldraw/tldraw/pull/1953)) #### Remove focus management ([#1953](https://github.com/tldraw/tldraw/pull/1953))
@ -82,14 +71,8 @@ Before/After
Before & After: Before & After:
<image <image width="250" src="https://github.com/tldraw/tldraw/assets/98838967/e0ca7d54-506f-4014-b65a-6b61a98e3665" />
width="250" <image width="250" src="https://github.com/tldraw/tldraw/assets/98838967/90c9fa12-1bcb-430d-80c7-97e1faacea16" />
src="https://github.com/tldraw/tldraw/assets/98838967/e0ca7d54-506f-4014-b65a-6b61a98e3665"
/>
<image
width="250"
src="https://github.com/tldraw/tldraw/assets/98838967/90c9fa12-1bcb-430d-80c7-97e1faacea16"
/>
#### Allow right clicking selection backgrounds ([#1968](https://github.com/tldraw/tldraw/pull/1968)) #### Allow right clicking selection backgrounds ([#1968](https://github.com/tldraw/tldraw/pull/1968))
@ -105,7 +88,7 @@ Before & After:
#### Lokalise: Translations update ([#1964](https://github.com/tldraw/tldraw/pull/1964)) #### Lokalise: Translations update ([#1964](https://github.com/tldraw/tldraw/pull/1964))
- Updated community translations for German and Galician * Updated community translations for German and Galician
#### [improvement] improve arrows (for real) ([#1957](https://github.com/tldraw/tldraw/pull/1957)) #### [improvement] improve arrows (for real) ([#1957](https://github.com/tldraw/tldraw/pull/1957))
@ -467,17 +450,10 @@ Removed a feature to reset the viewport back to a shape that is being edited.
- `@tldraw/editor`, `@tldraw/store`, `@tldraw/tldraw`, `@tldraw/tlschema` - `@tldraw/editor`, `@tldraw/store`, `@tldraw/tldraw`, `@tldraw/tlschema`
- Migrate snapshot [#1843](https://github.com/tldraw/tldraw/pull/1843) ([@steveruizok](https://github.com/steveruizok)) - Migrate snapshot [#1843](https://github.com/tldraw/tldraw/pull/1843) ([@steveruizok](https://github.com/steveruizok))
- `@tldraw/tldraw` - `@tldraw/tldraw`
- export asset stuff [#1829](https://github.com/tldraw/tldraw/pull/1829) ([@steveruizok](https://github.com/steveruizok))
- export asset stuff [#1829](https://github.com/tldraw/tldraw/pull/1829) ([@steveruizok](https://github.com/steveruizok)
)
- [feature] Asset props [#1824](https://github.com/tldraw/tldraw/pull/1824) ([@steveruizok](https://github.com/steveruizok)) - [feature] Asset props [#1824](https://github.com/tldraw/tldraw/pull/1824) ([@steveruizok](https://github.com/steveruizok))
- [feature] unlock all action [#1820](https://github.com/tldraw/tldraw/pull/1820) ([@steveruizok](https://github.com/steveruizok)) - [feature] unlock all action [#1820](https://github.com/tldraw/tldraw/pull/1820) ([@steveruizok](https://github.com/steveruizok))
- export `UiEventsProvider` [#1774](https://github.com/tldraw/tldraw/pull/1774) ([@steveruizok](https://github.com/steveruizok) - export `UiEventsProvider` [#1774](https://github.com/tldraw/tldraw/pull/1774) ([@steveruizok](https://github.com/steveruizok))
)
- `@tldraw/editor` - `@tldraw/editor`
- Add className as prop to Canvas [#1827](https://github.com/tldraw/tldraw/pull/1827) ([@steveruizok](https://github.com/steveruizok)) - Add className as prop to Canvas [#1827](https://github.com/tldraw/tldraw/pull/1827) ([@steveruizok](https://github.com/steveruizok))
- refactor `parentsToChildrenWithIndexes` [#1764](https://github.com/tldraw/tldraw/pull/1764) ([@steveruizok](https://github.com/steveruizok)) - refactor `parentsToChildrenWithIndexes` [#1764](https://github.com/tldraw/tldraw/pull/1764) ([@steveruizok](https://github.com/steveruizok))

View file

@ -9,6 +9,7 @@ status: published
[View on GitHub](https://github.com/tldraw/tldraw/releases/tag/v2.0.0-alpha.16) [View on GitHub](https://github.com/tldraw/tldraw/releases/tag/v2.0.0-alpha.16)
#### Fix shape opacity when erasing ([#2055](https://github.com/tldraw/tldraw/pull/2055)) #### Fix shape opacity when erasing ([#2055](https://github.com/tldraw/tldraw/pull/2055))
- Fixes opacity of shapes while erasing in a group or frame. - Fixes opacity of shapes while erasing in a group or frame.
@ -23,15 +24,9 @@ status: published
Before/after: Before/after:
<image <image width="250" src="https://github.com/tldraw/tldraw/assets/98838967/763a93eb-ffaa-405c-9255-e68ba88ed9a2" />
width="250"
src="https://github.com/tldraw/tldraw/assets/98838967/763a93eb-ffaa-405c-9255-e68ba88ed9a2"
/>
<image <image width="250" src="https://github.com/tldraw/tldraw/assets/98838967/dc9d3f77-c1c5-40f2-a9fe-10c723b6a21c" />
width="250"
src="https://github.com/tldraw/tldraw/assets/98838967/dc9d3f77-c1c5-40f2-a9fe-10c723b6a21c"
/>
#### fix: proper label for opacity tooltip on hover ([#2044](https://github.com/tldraw/tldraw/pull/2044)) #### fix: proper label for opacity tooltip on hover ([#2044](https://github.com/tldraw/tldraw/pull/2044))

View file

@ -9,6 +9,7 @@ status: published
[View on GitHub](https://github.com/tldraw/tldraw/releases/tag/v2.0.0-alpha.17) [View on GitHub](https://github.com/tldraw/tldraw/releases/tag/v2.0.0-alpha.17)
#### Firefox, Touch: Fix not being able to open style dropdowns ([#2092](https://github.com/tldraw/tldraw/pull/2092)) #### Firefox, Touch: Fix not being able to open style dropdowns ([#2092](https://github.com/tldraw/tldraw/pull/2092))
- Firefox Mobile: Fixed a bug where you couldn't open some style dropdown options. - Firefox Mobile: Fixed a bug where you couldn't open some style dropdown options.
@ -48,8 +49,8 @@ status: published
#### [fix] locked shape of opacity problem with eraser.pointing ([#2073](https://github.com/tldraw/tldraw/pull/2073)) #### [fix] locked shape of opacity problem with eraser.pointing ([#2073](https://github.com/tldraw/tldraw/pull/2073))
- locked shape of opacity problem with eraser.pointing - locked shape of opacity problem with eraser.pointing
Before/after: Before/after:
![A](https://github.com/tldraw/tldraw/assets/59823089/7483506c-72ac-45cc-93aa-f2a794ea8ff0) ![B](https://github.com/tldraw/tldraw/assets/59823089/ef0f988c-83f5-46a2-b891-0a391bca2f87) ![A](https://github.com/tldraw/tldraw/assets/59823089/7483506c-72ac-45cc-93aa-f2a794ea8ff0) ![B](https://github.com/tldraw/tldraw/assets/59823089/ef0f988c-83f5-46a2-b891-0a391bca2f87)
--- ---

View file

@ -9,6 +9,7 @@ status: published
[View on GitHub](https://github.com/tldraw/tldraw/releases/tag/v2.0.0-alpha.18) [View on GitHub](https://github.com/tldraw/tldraw/releases/tag/v2.0.0-alpha.18)
#### Fix an error when using context menu. ([#2186](https://github.com/tldraw/tldraw/pull/2186)) #### Fix an error when using context menu. ([#2186](https://github.com/tldraw/tldraw/pull/2186))
- Fixes the console error when opening the context menu for the first time. - Fixes the console error when opening the context menu for the first time.

View file

@ -9,6 +9,7 @@ status: published
[View on GitHub](https://github.com/tldraw/tldraw/releases/tag/v2.0.0-alpha.19) [View on GitHub](https://github.com/tldraw/tldraw/releases/tag/v2.0.0-alpha.19)
#### zoom to affected shapes after undo/redo ([#2293](https://github.com/tldraw/tldraw/pull/2293)) #### zoom to affected shapes after undo/redo ([#2293](https://github.com/tldraw/tldraw/pull/2293))
- Make sure affected shapes are visible after undo/redo - Make sure affected shapes are visible after undo/redo

View file

@ -9,6 +9,7 @@ status: published
[View on GitHub](https://github.com/tldraw/tldraw/releases/tag/v2.0.0-beta.1) [View on GitHub](https://github.com/tldraw/tldraw/releases/tag/v2.0.0-beta.1)
#### add speech bubble example ([#2362](https://github.com/tldraw/tldraw/pull/2362)) #### add speech bubble example ([#2362](https://github.com/tldraw/tldraw/pull/2362))
- Add an example for making a custom shape with handles, this one is a speech bubble with a movable tail. - Add an example for making a custom shape with handles, this one is a speech bubble with a movable tail.

View file

@ -9,6 +9,7 @@ status: published
[View on GitHub](https://github.com/tldraw/tldraw/releases/tag/v2.0.0-beta.2) [View on GitHub](https://github.com/tldraw/tldraw/releases/tag/v2.0.0-beta.2)
#### Fix validation when pasting images. ([#2436](https://github.com/tldraw/tldraw/pull/2436)) #### Fix validation when pasting images. ([#2436](https://github.com/tldraw/tldraw/pull/2436))
- Fixes url validations. - Fixes url validations.

View file

@ -1,14 +1,14 @@
[ [
{ {
"id": "getting-started", "id": "getting-started",
"title": "Getting Started", "title": "Get Started",
"description": "Introduction articles for tldraw.", "description": "Introduction articles for tldraw.",
"categories": [], "categories": [],
"sidebar_behavior": "show-links" "sidebar_behavior": "show-links"
}, },
{ {
"id": "docs", "id": "docs",
"title": "Documentation", "title": "Learn tldraw",
"description": "Developer documentation for tldraw.", "description": "Developer documentation for tldraw.",
"categories": [], "categories": [],
"sidebar_behavior": "show-links" "sidebar_behavior": "show-links"
@ -18,10 +18,10 @@
"title": "Community", "title": "Community",
"description": "Guides for contributing to tldraw's open source project.", "description": "Guides for contributing to tldraw's open source project.",
"categories": [], "categories": [],
"sidebar_behavior": "show-links" "sidebar_behavior": "hidden"
}, },
{ {
"id": "gen", "id": "reference",
"title": "API Reference", "title": "API Reference",
"description": "Reference for the tldraw package's APIs (generated).", "description": "Reference for the tldraw package's APIs (generated).",
"categories": [ "categories": [
@ -201,6 +201,6 @@
] ]
} }
], ],
"sidebar_behavior": "show-title" "sidebar_behavior": "reference"
} }
] ]

View file

@ -16,7 +16,13 @@ const nextConfig = {
{ {
// For reverse compatibility with old links // For reverse compatibility with old links
source: '/docs/introduction', source: '/docs/introduction',
destination: '/introduction', destination: '/quick-start',
permanent: true,
},
{
// For reverse compatibility with old links
source: '/introduction',
destination: '/quick-start',
permanent: true, permanent: true,
}, },
{ {
@ -31,30 +37,6 @@ const nextConfig = {
destination: '/usage', destination: '/usage',
permanent: true, permanent: true,
}, },
{
// For reverse compatibility with old links
source: '/docs/introduction',
destination: '/introduction',
permanent: true,
},
{
// For reverse compatibility with old links
source: '/docs/installation',
destination: '/installation',
permanent: true,
},
{
// For reverse compatibility with old links
source: '/docs/usage',
destination: '/usage',
permanent: true,
},
{
// To reflect that these are at the top level of the sidebar
source: '/getting-started/:childId',
destination: '/:childId',
permanent: true,
},
{ {
// To reflect that these are at the top level of the sidebar // To reflect that these are at the top level of the sidebar
source: '/getting-started/:childId', source: '/getting-started/:childId',

View file

@ -37,19 +37,24 @@
"create-api-markdown": "yarn run -T tsx --tsconfig ./tsconfig.content.json ./scripts/create-api-markdown.ts", "create-api-markdown": "yarn run -T tsx --tsconfig ./tsconfig.content.json ./scripts/create-api-markdown.ts",
"refresh-content": "yarn run -T tsx --tsconfig ./tsconfig.content.json ./scripts/refresh-content.ts", "refresh-content": "yarn run -T tsx --tsconfig ./tsconfig.content.json ./scripts/refresh-content.ts",
"refresh-embeddings": "yarn run -T tsx --tsconfig ./tsconfig.content.json ./scripts/refresh-embeddings.ts", "refresh-embeddings": "yarn run -T tsx --tsconfig ./tsconfig.content.json ./scripts/refresh-embeddings.ts",
"refresh-everything": "yarn fetch-api-source && yarn fetch-releases && yarn create-api-markdown && yarn refresh-content && yarn refresh-embeddings", "refresh-everything": "yarn fetch-api-source && yarn fetch-releases && yarn create-api-markdown && yarn refresh-content && yarn refresh-embeddings && yarn format",
"clean": "rm -rf node_modules .yarn", "clean": "rm -rf node_modules .yarn",
"format": "yarn run -T prettier --write .",
"watch-content": "tsx ./watcher.ts" "watch-content": "tsx ./watcher.ts"
}, },
"dependencies": { "dependencies": {
"@codesandbox/sandpack-react": "^2.11.3",
"@microsoft/api-extractor-model": "^7.26.4", "@microsoft/api-extractor-model": "^7.26.4",
"@microsoft/tsdoc": "^0.14.2", "@microsoft/tsdoc": "^0.14.2",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-navigation-menu": "^1.1.4",
"@types/broken-link-checker": "^0.7.1", "@types/broken-link-checker": "^0.7.1",
"@types/node": "^18.7.3", "@types/node": "^18.7.3",
"@types/sqlite3": "^3.1.9", "@types/sqlite3": "^3.1.9",
"@types/ws": "^8.5.9", "@types/ws": "^8.5.9",
"@vercel/analytics": "^1.1.1", "@vercel/analytics": "^1.1.1",
"broken-link-checker": "^0.7.8", "broken-link-checker": "^0.7.8",
"classnames": "^2.3.2",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"eslint": "^8.37.0", "eslint": "^8.37.0",

View file

@ -65,6 +65,8 @@ export async function connect(opts = {} as { reset?: boolean }) {
status TEXT NOT NULL, status TEXT NOT NULL,
date TEXT, date TEXT,
sourceUrl TEXT, sourceUrl TEXT,
componentCode TEXT,
componentCodeFiles TEXT,
keywords TEXT, keywords TEXT,
content TEXT NOT NULL, content TEXT NOT NULL,
path TEXT, path TEXT,

View file

@ -8,17 +8,17 @@ import { getApiMarkdown } from './getApiMarkdown'
export async function createApiMarkdown() { export async function createApiMarkdown() {
const apiInputSection: InputSection = { const apiInputSection: InputSection = {
id: 'gen' as string, id: 'reference' as string,
title: 'API Reference', title: 'API Reference',
description: "Reference for the tldraw package's APIs (generated).", description: "Reference for the tldraw package's APIs (generated).",
categories: [], categories: [],
sidebar_behavior: 'show-title', sidebar_behavior: 'reference',
} }
const addedCategories = new Set<string>() const addedCategories = new Set<string>()
const INPUT_DIR = path.join(process.cwd(), 'api') const INPUT_DIR = path.join(process.cwd(), 'api')
const OUTPUT_DIR = path.join(CONTENT_DIR, 'gen') const OUTPUT_DIR = path.join(CONTENT_DIR, 'reference')
if (fs.existsSync(OUTPUT_DIR)) { if (fs.existsSync(OUTPUT_DIR)) {
fs.rmSync(OUTPUT_DIR, { recursive: true }) fs.rmSync(OUTPUT_DIR, { recursive: true })
@ -85,7 +85,7 @@ export async function createApiMarkdown() {
const sectionsJsonPath = path.join(CONTENT_DIR, 'sections.json') const sectionsJsonPath = path.join(CONTENT_DIR, 'sections.json')
const sectionsJson = JSON.parse(fs.readFileSync(sectionsJsonPath, 'utf8')) as InputSection[] const sectionsJson = JSON.parse(fs.readFileSync(sectionsJsonPath, 'utf8')) as InputSection[]
sectionsJson.splice( sectionsJson.splice(
sectionsJson.findIndex((s) => s.id === 'gen'), sectionsJson.findIndex((s) => s.id === 'reference'),
1 1
) )
sectionsJson.push(apiInputSection) sectionsJson.push(apiInputSection)

View file

@ -11,8 +11,8 @@ export async function generateApiContent(): Promise<GeneratedContent> {
const sections = require(path.join(CONTENT_DIRECTORY, 'sections.json')) as InputSection[] const sections = require(path.join(CONTENT_DIRECTORY, 'sections.json')) as InputSection[]
try { try {
const inputApiSection = sections.find((s) => s.id === 'gen') const inputApiSection = sections.find((s) => s.id === 'reference')
if (!inputApiSection) throw new Error(`Could not find section with id 'gen'`) if (!inputApiSection) throw new Error(`Could not find section with id 'reference'`)
const outputApiSection = generateSection(inputApiSection, articles, 999999) // always at the end! const outputApiSection = generateSection(inputApiSection, articles, 999999) // always at the end!
const contentComplete = { sections: [outputApiSection], articles } const contentComplete = { sections: [outputApiSection], articles }

View file

@ -13,7 +13,7 @@ export async function generateContent() {
} }
for (let i = 0; i < sections.length; i++) { for (let i = 0; i < sections.length; i++) {
if (sections[i].id === 'gen') continue if (sections[i].id === 'reference') continue
result.sections.push(generateSection(sections[i], result.articles, i)) result.sections.push(generateSection(sections[i], result.articles, i))
} }

View file

@ -0,0 +1,34 @@
import { Articles, GeneratedContent, InputSection } from '../../types/content-types'
import { generateSection } from './generateSection'
const { log: nicelog } = console
const section: InputSection = {
id: 'examples',
title: 'Examples',
description: 'Code recipes for bending tldraw to your will.',
categories: [
{ id: 'basic', title: 'Getting Started', description: '', groups: [] },
{ id: 'ui', title: 'UI/Theming', description: '', groups: [] },
{ id: 'shapes/tools', title: 'Shapes & Tools', description: '', groups: [] },
{ id: 'data/assets', title: 'Data & Assets', description: '', groups: [] },
{ id: 'editor-api', title: 'Editor API', description: '', groups: [] },
{ id: 'collaboration', title: 'Collaboration', description: '', groups: [] },
],
sidebar_behavior: 'show-links',
}
export async function generateExamplesContent(): Promise<GeneratedContent> {
const articles: Articles = {}
try {
const outputExamplesSection = generateSection(section, articles, 0)
const contentComplete = { sections: [outputExamplesSection], articles }
return contentComplete
} catch (error) {
nicelog(`x Could not generate Examples content`)
throw error
}
}

View file

@ -21,21 +21,29 @@ export function generateSection(section: InputSection, articles: Articles, index
) )
// The file directory for this section // The file directory for this section
const dir = path.join(CONTENT_DIR, section.id) const isExamples = section.id === 'examples'
const dir = isExamples
? path.join(process.cwd(), '..', 'examples', 'src', 'examples')
: path.join(CONTENT_DIR, section.id)
const files = fs.readdirSync(dir, { withFileTypes: false }) const files = fs.readdirSync(dir, { withFileTypes: false })
const isGenerated = section.id === 'gen' const isGenerated = section.id === 'reference'
for (const file of files) { for (const file of files) {
const filename = file.toString() const filename = file.toString()
const fileContent = fs.readFileSync(path.join(dir, filename)).toString() const pathname = isExamples ? path.join(dir, filename, 'README.md') : path.join(dir, filename)
const fileContent = fs.readFileSync(pathname).toString()
const extension = path.extname(filename) const extension = path.extname(filename)
const articleId = filename.replace(extension, '') const articleId = filename.replace(extension, '')
const parsed = matter({ content: fileContent }, {}) const parsed = matter({ content: fileContent }, {})
// If we're in prod and the article isn't published, skip it // If we're in prod and the article isn't published, skip it
if (process.env.NODE_ENV !== 'development' && parsed.data.status !== 'published') { if (
process.env.NODE_ENV !== 'development' &&
!isExamples &&
parsed.data.status !== 'published'
) {
continue continue
} }
@ -48,6 +56,38 @@ export function generateSection(section: InputSection, articles: Articles, index
const isUncategorized = categoryId === section.id + '_ucg' const isUncategorized = categoryId === section.id + '_ucg'
const isIndex = articleId === section.id const isIndex = articleId === section.id
const componentCode = parsed.data.component
? fs
.readFileSync(
path.join(
dir,
filename,
`${parsed.data.component}${parsed.data.component.endsWith('.tsx') ? '' : '.tsx'}`
)
)
.toString()
: null
const componentCodeFiles: { [key: string]: string } = {}
if (parsed.data.component) {
const exampleParentDirectory = path.join(dir, filename)
const codeFilenames = fs.readdirSync(exampleParentDirectory, {
withFileTypes: true,
recursive: true,
})
codeFilenames
.filter(
(file) =>
!file.isDirectory() &&
file.name !== 'README.md' &&
file.name.replace('.tsx', '') !==
parsed.data.component.replace('./', '').replace('.tsx', '')
)
.forEach((file) => {
componentCodeFiles[file.name] = fs
.readFileSync(path.join(file.path, file.name))
.toString()
})
}
const article: Article = { const article: Article = {
id: articleId, id: articleId,
@ -55,7 +95,7 @@ export function generateSection(section: InputSection, articles: Articles, index
sectionIndex: 0, sectionIndex: 0,
groupIndex: -1, groupIndex: -1,
groupId: parsed.data.group ?? null, groupId: parsed.data.group ?? null,
categoryIndex: parsed.data.order ?? -1, categoryIndex: parsed.data.order ?? parsed.data.priority ?? -1,
sectionId: section.id, sectionId: section.id,
author, author,
categoryId, categoryId,
@ -68,6 +108,8 @@ export function generateSection(section: InputSection, articles: Articles, index
sourceUrl: isGenerated // if it's a generated API doc, then we don't have a link sourceUrl: isGenerated // if it's a generated API doc, then we don't have a link
? parsed.data.sourceUrl ?? null ? parsed.data.sourceUrl ?? null
: `${section.id}/${articleId}${extension}`, : `${section.id}/${articleId}${extension}`,
componentCode,
componentCodeFiles: componentCode ? JSON.stringify(componentCodeFiles) : null,
content: parsed.content, content: parsed.content,
path: path:
section.id === 'getting-started' section.id === 'getting-started'
@ -166,14 +208,10 @@ export function generateSection(section: InputSection, articles: Articles, index
} }
const sortArticles = (articleA: Article, articleB: Article) => { const sortArticles = (articleA: Article, articleB: Article) => {
const { categoryIndex: categoryIndexA, date: dateA = '01/01/1970' } = articleA const { categoryIndex: categoryIndexA, title: titleA } = articleA
const { categoryIndex: categoryIndexB, date: dateB = '01/01/1970' } = articleB const { categoryIndex: categoryIndexB, title: titleB } = articleB
return categoryIndexA === categoryIndexB return categoryIndexA === categoryIndexB
? new Date(dateB!).getTime() > new Date(dateA!).getTime() ? titleA.localeCompare(titleB)
? 1 : categoryIndexA - categoryIndexB
: -1
: categoryIndexA < categoryIndexB
? -1
: 1
} }

View file

@ -5,6 +5,7 @@ import { nicelog } from '@/utils/nicelog'
import { connect } from './connect' import { connect } from './connect'
import { generateApiContent } from './generateApiContent' import { generateApiContent } from './generateApiContent'
import { generateContent } from './generateContent' import { generateContent } from './generateContent'
import { generateExamplesContent } from './generateExamplesContent'
export async function refreshContent(opts = {} as { silent: boolean }) { export async function refreshContent(opts = {} as { silent: boolean }) {
if (!opts.silent) nicelog('◦ Resetting database...') if (!opts.silent) nicelog('◦ Resetting database...')
@ -16,6 +17,9 @@ export async function refreshContent(opts = {} as { silent: boolean }) {
if (!opts.silent) nicelog('◦ Generating / adding regular content to db...') if (!opts.silent) nicelog('◦ Generating / adding regular content to db...')
await addContentToDb(db, await generateContent()) await addContentToDb(db, await generateContent())
if (!opts.silent) nicelog('◦ Generating / adding Examples content to db...')
await addContentToDb(db, await generateExamplesContent())
if (!opts.silent) nicelog('◦ Generating / adding API content to db...') if (!opts.silent) nicelog('◦ Generating / adding API content to db...')
await addContentToDb(db, await generateApiContent()) await addContentToDb(db, await generateApiContent())

View file

@ -21,6 +21,7 @@
/* Light theme */ /* Light theme */
--color-text: #1d1d1d; --color-text: #1d1d1d;
--color-text-secondary: #999;
--color-background: #ffffff; --color-background: #ffffff;
--color-contrast: #ffffff; --color-contrast: #ffffff;
--color-accent: #2f80ed; --color-accent: #2f80ed;
@ -46,6 +47,7 @@
[data-theme='dark'] { [data-theme='dark'] {
/* Dark theme */ /* Dark theme */
--color-text: #fafafa; --color-text: #fafafa;
--color-text-secondary: #999;
--color-background: hsl(240, 5%, 8%); --color-background: hsl(240, 5%, 8%);
--color-contrast: #000; --color-contrast: #000;
--color-accent: #74b0ff; --color-accent: #74b0ff;
@ -129,7 +131,7 @@ body {
top: 0px; top: 0px;
display: grid; display: grid;
padding: 16px; padding: 16px;
grid-template-columns: 264px 1fr auto; grid-template-columns: auto 1fr auto;
gap: 16px; gap: 16px;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@ -145,12 +147,34 @@ body {
margin-bottom: 8px; margin-bottom: 8px;
} }
.layout__header__socials { .layout__header__sections_and_socials {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
} }
.layout_header__section {
text-decoration: none;
margin: 0 4px;
padding: 8px 12px;
font-size: 14px;
color: var(--color-text);
background-color: transparent;
transition: background-color 0.12s ease-in-out;
transition-delay: 0.1s;
border-radius: 4px;
}
.layout_header__section[data-active='true'] {
background-color: var(--color-tint-1);
}
@media (hover: hover) {
.layout_header__section:not([data-active='true']):hover {
background-color: var(--color-tint-1);
}
}
.icon-button { .icon-button {
display: block; display: block;
position: relative; position: relative;
@ -170,7 +194,7 @@ body {
position: absolute; position: absolute;
display: block; display: block;
content: ''; content: '';
inset: 4px; inset: 0;
background-color: var(--bg); background-color: var(--bg);
border-radius: 4px; border-radius: 4px;
transition: background-color 0.1s ease-in-out; transition: background-color 0.1s ease-in-out;
@ -209,7 +233,7 @@ body {
justify-self: center; justify-self: center;
width: 100%; width: 100%;
min-height: calc(100vh - 64px); min-height: calc(100vh - 64px);
padding: 24px 0px 96px 0px; padding: 0px 0px 96px 0px;
font-weight: 400; font-weight: 400;
overflow-x: hidden; overflow-x: hidden;
overflow-y: visible; overflow-y: visible;
@ -444,6 +468,10 @@ body {
overflow-x: auto; overflow-x: auto;
} }
.article.article__example pre {
margin: 0px;
}
.article__api-docs pre { .article__api-docs pre {
margin: 24px 0px; margin: 24px 0px;
} }
@ -491,6 +519,10 @@ body {
border: none; border: none;
} }
.article.article__example hr {
margin: 0;
}
.article table { .article table {
margin: 20px 0px; margin: 20px 0px;
border-radius: 4px; border-radius: 4px;
@ -734,7 +766,7 @@ body {
align-self: start; align-self: start;
top: 72px; top: 72px;
margin-left: -12px; margin-left: -12px;
padding: 24px 12px 120px 28px; padding: 0px 12px 120px 28px;
width: calc(100% + 24px); width: calc(100% + 24px);
max-height: calc(100vh); max-height: calc(100vh);
z-index: 800; z-index: 800;
@ -742,10 +774,10 @@ body {
font-size: 14px; font-size: 14px;
} }
.sidebar a { .sidebar a,
color: inherit; .sidebar .sidebar__link {
font-weight: bold;
text-decoration: none; text-decoration: none;
cursor: pointer;
} }
.sidebar hr { .sidebar hr {
@ -757,22 +789,32 @@ body {
border: none; border: none;
} }
.sidebar__section__links {
display: none;
}
.sidebar__list { .sidebar__list {
padding: 0px; padding: 0px;
margin: 0px; margin: 0px;
list-style-type: none; list-style-type: none;
} }
.sidebar__list li .sidebar__article { .sidebar__list li .sidebar__article,
height: 44px; .sidebar__list li .sidebar__article li {
margin-top: -6px; margin-top: -6px;
margin-bottom: -6px; margin-bottom: -6px;
width: 100%; width: 100%;
} }
.sidebar__list li .sidebar__category li a,
.sidebar__list li .sidebar__article li a {
padding-left: 8px;
font-size: 13px;
}
.sidebar__link { .sidebar__link {
position: relative; position: relative;
height: 40px; padding: 6px 0;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: flex-start;
@ -815,33 +857,51 @@ body {
.sidebar__section__title { .sidebar__section__title {
font-size: 14px; font-size: 14px;
font-weight: bold; font-weight: normal;
color: var(--color-text);
margin-bottom: 4px; margin-bottom: 4px;
position: relative; position: relative;
height: 40px; height: 40px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: flex-start;
color: var(--color-text); color: var(--color-text-secondary);
text-transform: uppercase;
--bg: transparent; --bg: transparent;
white-space: nowrap; white-space: nowrap;
} }
.sidebar__sections__list > *:nth-last-of-type(n + 2) .sidebar__list { .sidebar__section__group__title {
font-size: 14px;
font-weight: normal;
margin-bottom: 4px;
position: relative;
height: 40px;
display: flex;
align-items: center;
justify-content: space-between;
color: var(--color-text-secondary);
white-space: nowrap;
background: transparent;
border: 0;
width: 100%;
cursor: pointer;
}
.sidebar__sections__list > *:nth-last-of-type(n + 2) > .sidebar__list {
padding-bottom: 12px; padding-bottom: 12px;
margin-bottom: 12px; margin-bottom: 12px;
border-bottom: 1px solid var(--color-tint-2); border-bottom: 1px solid var(--color-tint-2);
} }
@media (hover: hover) { @media (hover: hover) {
.sidebar__link:hover { a.sidebar__link:hover {
cursor: pointer;
color: var(--color-text); color: var(--color-text);
transition: background-color 0.12s ease-in-out; transition: background-color 0.12s ease-in-out;
transition-delay: 0.1s; transition-delay: 0.1s;
} }
.sidebar__link:not([data-active='true']):hover { a.sidebar__link:not([data-active='true']):hover {
--bg: var(--color-tint-1); --bg: var(--color-tint-1);
} }
} }
@ -1073,6 +1133,23 @@ body {
grid-gap: 40px; grid-gap: 40px;
} }
.layout__header .layout__header__sections_and_socials .layout_header__section,
.layout__header .layout__header__sections_and_socials .NavigationMenuRoot {
display: none;
}
.sidebar__section__links {
display: flex;
justify-content: space-around;
padding: 16px 0;
border-top: 1px solid var(--color-accent);
border-bottom: 1px solid var(--color-accent);
}
.NavigationMenuTrigger {
font-weight: bold;
}
.menu__button { .menu__button {
display: flex; display: flex;
pointer-events: all; pointer-events: all;
@ -1257,3 +1334,93 @@ html[data-theme='light'] .hero__dark {
margin: 0px -16px; margin: 0px -16px;
} }
} }
/* ------------------- Menus ------------------ */
.NavigationMenuRoot {
position: relative;
}
.NavigationMenuList {
list-style: none;
margin: 0;
}
.NavigationMenuTrigger {
outline: none;
user-select: none;
border: 0;
background: none;
background-color: transparent;
transition: background-color 0.12s ease-in-out;
transition-delay: 0.1s;
border-radius: 4px;
margin: 0 4px;
padding: 8px 12px;
height: 44px;
line-height: 22px;
white-space: nowrap;
cursor: pointer;
}
.NavigationMenuTrigger span {
text-decoration: none;
font-size: 14px;
color: var(--color-text);
}
@media (hover: hover) {
.NavigationMenuTrigger:hover {
background-color: var(--color-tint-1);
}
}
.NavigationMenuContent {
position: absolute;
width: 100%;
top: calc(100% + 4px);
left: 4px;
overflow: hidden;
border-radius: 4px;
background-color: var(--color-background);
box-shadow: var(--shadow-small);
}
.NavigationMenuContent li {
list-style: none;
}
.NavigationMenuContent li a {
display: block;
text-decoration: none;
color: var(--color-text);
font-size: 14px;
padding: 8px 12px;
background-color: transparent;
}
@media (hover: hover) {
.NavigationMenuContent li a:hover {
background-color: var(--color-tint-1);
}
}
.NavigationMenuViewport {
position: relative;
width: 100%;
height: var(--radix-navigation-menu-viewport-height);
}
@media only screen and (min-width: 600px) {
.NavigationMenuViewport {
width: var(--radix-navigation-menu-viewport-width);
}
}
.CaretDown {
position: relative;
top: 3px;
transition: transform 250ms ease;
}
[data-state='open'] > .CaretDown {
transform: rotate(-180deg);
}

View file

@ -10,7 +10,7 @@ export type InputSection = {
title: string title: string
description: string description: string
categories: InputCategory[] categories: InputCategory[]
sidebar_behavior: 'show-links' | 'show-title' sidebar_behavior: 'show-links' | 'show-title' | 'hidden' | 'reference'
} }
export type InputGroup = { export type InputGroup = {
@ -55,7 +55,7 @@ export interface Section extends ContentPage {
/** An array of this section's categories. */ /** An array of this section's categories. */
categories: Category[] categories: Category[]
/** How the section should appear in the sidebar. */ /** How the section should appear in the sidebar. */
sidebar_behavior: 'show-links' | 'show-title' sidebar_behavior: 'show-links' | 'show-title' | 'hidden' | 'reference'
} }
export interface Category extends ContentPage { export interface Category extends ContentPage {
@ -104,6 +104,10 @@ export interface Article extends ContentPage {
keywords: string[] keywords: string[]
/** The URL where the article's source can be found. */ /** The URL where the article's source can be found. */
sourceUrl: string | null sourceUrl: string | null
/** The article's code example (optional). */
componentCode: string | null
/** The article's code example files, JSON stringified (optional). */
componentCodeFiles: string | null
} }
export enum ArticleStatus { export enum ArticleStatus {
@ -155,7 +159,8 @@ export interface SidebarContentCategoryLink extends BaseSidebarLink {
export interface SidebarContentArticleLink extends BaseSidebarLink { export interface SidebarContentArticleLink extends BaseSidebarLink {
type: 'article' type: 'article'
articleId: string articleId: string | null
groupId: string | null
} }
export type SidebarContentLink = export type SidebarContentLink =
@ -164,10 +169,14 @@ export type SidebarContentLink =
| SidebarContentArticleLink | SidebarContentArticleLink
export type SidebarContentList = { export type SidebarContentList = {
headings?: ArticleHeadings
sectionId: string | null sectionId: string | null
categoryId: string | null categoryId: string | null
articleId: string | null articleId: string | null
links: SidebarContentLink[] links: SidebarContentLink[]
activeId?: string | null
searchQuery?: string
searchType?: string
} }
/* ---------- Finished / generated content ---------- */ /* ---------- Finished / generated content ---------- */

View file

@ -141,7 +141,10 @@ export class ContentDatabase {
return { prev: prev ?? null, next: next ?? null } return { prev: prev ?? null, next: next ?? null }
} }
// TODO(mime): make this more generic, not per docs area
private _sidebarContentLinks: SidebarContentLink[] | undefined private _sidebarContentLinks: SidebarContentLink[] | undefined
private _sidebarReferenceContentLinks: SidebarContentLink[] | undefined
private _sidebarExamplesContentLinks: SidebarContentLink[] | undefined
async getSidebarContentList({ async getSidebarContentList({
sectionId, sectionId,
@ -154,9 +157,15 @@ export class ContentDatabase {
}): Promise<SidebarContentList> { }): Promise<SidebarContentList> {
let links: SidebarContentLink[] let links: SidebarContentLink[]
if (this._sidebarContentLinks && process.env.NODE_ENV !== 'development') { const cachedLinks =
sectionId === 'examples'
? this._sidebarExamplesContentLinks
: sectionId === 'reference'
? this._sidebarReferenceContentLinks
: this._sidebarContentLinks
if (cachedLinks && process.env.NODE_ENV !== 'development') {
// Use the previously cached sidebar links // Use the previously cached sidebar links
links = this._sidebarContentLinks links = cachedLinks
} else { } else {
// Generate sidebar links and cache them // Generate sidebar links and cache them
links = [] links = []
@ -168,12 +177,31 @@ export class ContentDatabase {
const children: SidebarContentLink[] = [] const children: SidebarContentLink[] = []
if (section.sidebar_behavior === 'hidden') {
continue
}
if (
(sectionId === 'reference' && section.id !== 'reference') ||
(sectionId !== 'reference' && section.id === 'reference')
) {
continue
}
if (
(sectionId === 'examples' && section.id !== 'examples') ||
(sectionId !== 'examples' && section.id === 'examples')
) {
continue
}
if (section.sidebar_behavior === 'show-title') { if (section.sidebar_behavior === 'show-title') {
links.push({ links.push({
type: 'article', type: 'article',
title: section.title, title: section.title,
url: section.path, url: section.path,
articleId: section.id, articleId: section.id,
groupId: null,
}) })
continue continue
} }
@ -204,6 +232,7 @@ export class ContentDatabase {
articleId: article.id, articleId: article.id,
title: article.title, title: article.title,
url: article.path, url: article.path,
groupId: article.groupId,
} }
ucg.push(sidebarArticleLink) ucg.push(sidebarArticleLink)
@ -228,6 +257,7 @@ export class ContentDatabase {
const sidebarArticleLink: SidebarContentArticleLink = { const sidebarArticleLink: SidebarContentArticleLink = {
type: 'article' as const, type: 'article' as const,
articleId: article.id, articleId: article.id,
groupId: article.groupId,
title: article.title, title: article.title,
url: article.path, url: article.path,
} }
@ -244,9 +274,16 @@ export class ContentDatabase {
links.push({ type: 'section', title: section.title, url: section.path, children }) links.push({ type: 'section', title: section.title, url: section.path, children })
// Cache the links structure for next time // Cache the links structure for next time
if (sectionId === 'examples') {
this._sidebarExamplesContentLinks = links
}
if (sectionId === 'reference') {
this._sidebarReferenceContentLinks = links
} else {
this._sidebarContentLinks = links this._sidebarContentLinks = links
} }
} }
}
return { return {
sectionId: sectionId ?? null, sectionId: sectionId ?? null,

View file

@ -76,7 +76,7 @@ export class ContentVectorDatabase {
// This is the content that we'll create the embedding for // This is the content that we'll create the embedding for
let contentToVectorize: string let contentToVectorize: string
if (article.sectionId === 'gen') { if (article.sectionId === 'reference') {
// For API docs, we'll just use the title, description, and members as the content. // For API docs, we'll just use the title, description, and members as the content.
// We'll also add a note that the content was generated from the API docs, hopefully // We'll also add a note that the content was generated from the API docs, hopefully
// so that the embedding better reflects searches for api docs. // so that the embedding better reflects searches for api docs.
@ -217,8 +217,8 @@ export async function getVectorDb(
INCLUDE_API_CONTENT && INCLUDE_CONTENT INCLUDE_API_CONTENT && INCLUDE_CONTENT
? await db.all('SELECT * FROM articles') ? await db.all('SELECT * FROM articles')
: INCLUDE_API_CONTENT : INCLUDE_API_CONTENT
? await db.all('SELECT * FROM articles WHERE articles.sectionId = ?', 'gen') ? await db.all('SELECT * FROM articles WHERE articles.sectionId = ?', 'reference')
: await db.all('SELECT * FROM articles WHERE articles.sectionId != ?', 'gen') : await db.all('SELECT * FROM articles WHERE articles.sectionId != ?', 'reference')
nicelog(`Adding articles to index`) nicelog(`Adding articles to index`)
const max = Math.min(articles.length, MAX_ARTICLES) const max = Math.min(articles.length, MAX_ARTICLES)

View file

@ -35,10 +35,12 @@ export async function addContentToDb(
status, status,
date, date,
sourceUrl, sourceUrl,
componentCode,
componentCodeFiles,
keywords, keywords,
content, content,
path path
) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
) )
for (let i = 0; i < content.sections.length; i++) { for (let i = 0; i < content.sections.length; i++) {
@ -46,7 +48,7 @@ export async function addContentToDb(
try { try {
await sectionInsert.run( await sectionInsert.run(
section.id, section.id,
section.id === 'gen' ? 99999 : i, section.id === 'reference' ? 99999 : i,
section.title, section.title,
section.description, section.description,
section.path, section.path,
@ -92,6 +94,8 @@ export async function addContentToDb(
article.status, article.status,
article.date, article.date,
article.sourceUrl, article.sourceUrl,
article.componentCode,
article.componentCodeFiles,
article.keywords.join(', '), article.keywords.join(', '),
article.content, article.content,
article.path article.path

View file

@ -6,7 +6,10 @@ import sqlite3 from 'sqlite3'
export async function autoLinkDocs(db: Database<sqlite3.Database, sqlite3.Statement>) { export async function autoLinkDocs(db: Database<sqlite3.Database, sqlite3.Statement>) {
// replace [TLEditor](?) with [TLEditor](/gen/editor/TLEditor)? // replace [TLEditor](?) with [TLEditor](/gen/editor/TLEditor)?
// not sure how we would get there but finding an article with the same title // not sure how we would get there but finding an article with the same title
const articles = await db.all('SELECT id, content FROM articles WHERE sectionId != ?', 'gen') const articles = await db.all(
'SELECT id, content FROM articles WHERE sectionId != ?',
'reference'
)
await Promise.all(articles.map((a) => autoLinkDocsForArticle(db, a))) await Promise.all(articles.map((a) => autoLinkDocsForArticle(db, a)))
} }
@ -30,7 +33,7 @@ export async function autoLinkDocsForArticle(
const article = await db.get( const article = await db.get(
'SELECT id, sectionId, categoryId FROM articles WHERE title = ? AND sectionId = ?', 'SELECT id, sectionId, categoryId FROM articles WHERE title = ? AND sectionId = ?',
title, title,
'gen' 'reference'
) )
if (!article) throw Error(`Could not find article for ${_title} (${title})`) if (!article) throw Error(`Could not find article for ${_title} (${title})`)

View file

@ -13,7 +13,7 @@ export type Example = {
loadComponent: () => Promise<ComponentType> loadComponent: () => Promise<ComponentType>
} }
type Category = 'basic' | 'editor' | 'ui' | 'collaboration' | 'data/assets' | 'shapes/tools' type Category = 'basic' | 'editor-api' | 'ui' | 'collaboration' | 'data/assets' | 'shapes/tools'
const getExamplesForCategory = (category: Category) => const getExamplesForCategory = (category: Category) =>
(Object.values(import.meta.glob('./examples/*/README.md', { eager: true })) as Example[]) (Object.values(import.meta.glob('./examples/*/README.md', { eager: true })) as Example[])
@ -28,7 +28,7 @@ const categories: Record<Category, string> = {
ui: 'UI/Theming', ui: 'UI/Theming',
'shapes/tools': 'Shapes & Tools', 'shapes/tools': 'Shapes & Tools',
'data/assets': 'Data & Assets', 'data/assets': 'Data & Assets',
editor: 'Editor API', 'editor-api': 'Editor API',
collaboration: 'Collaboration', collaboration: 'Collaboration',
} }

View file

@ -1,7 +1,7 @@
--- ---
title: Editor API title: Editor API
component: ./APIExample.tsx component: ./APIExample.tsx
category: editor category: editor-api
priority: 1 priority: 1
--- ---

View file

@ -1,7 +1,7 @@
--- ---
title: Canvas events title: Canvas events
component: ./CanvasEventsExample.tsx component: ./CanvasEventsExample.tsx
category: editor category: editor-api
priority: 2 priority: 2
--- ---

View file

@ -1,7 +1,7 @@
--- ---
title: Context Toolbar title: Context Toolbar
component: ./ContextToolbar.tsx component: ./ContextToolbar.tsx
category: UI category: ui
priority: 2 priority: 2
--- ---

View file

@ -1,7 +1,7 @@
--- ---
title: Sublibraries title: Sublibraries
component: ./ExplodedExample.tsx component: ./ExplodedExample.tsx
category: editor category: editor-api
priority: 3 priority: 3
--- ---

View file

@ -2,6 +2,6 @@
title: Floaty window title: Floaty window
hide: true hide: true
component: ./FloatyExample.tsx component: ./FloatyExample.tsx
category: UI category: ui
priority: 3 priority: 3
--- ---

View file

@ -5,4 +5,4 @@ category: basic
priority: 3 priority: 3
--- ---
Use multiple <Tldraw/> components on the same page. Use multiple `<Tldraw/>` components on the same page.

View file

@ -1,7 +1,7 @@
--- ---
title: Minimal title: Minimal
component: ./OnlyEditor.tsx component: ./OnlyEditor.tsx
category: editor category: editor-api
priority: 3 priority: 3
--- ---

View file

@ -1,7 +1,7 @@
--- ---
title: Snapshots title: Snapshots
component: ./SnapshotExample.tsx component: ./SnapshotExample.tsx
category: editor category: editor-api
priority: 1 priority: 1
--- ---

View file

@ -1,7 +1,7 @@
--- ---
title: Store events title: Store events
component: ./StoreEventsExample.tsx component: ./StoreEventsExample.tsx
category: editor category: editor-api
priority: 2 priority: 2
--- ---

View file

@ -1,7 +1,7 @@
--- ---
title: UI events title: UI events
component: ./UiEventsExample.tsx component: ./UiEventsExample.tsx
category: editor category: editor-api
priority: 2 priority: 2
--- ---

View file

@ -0,0 +1,12 @@
---
title: YJS
component: ./YjsExample.tsx
category: collaboration
priority: 2
---
TODOTODO
---
TODOTODO

497
yarn.lock
View file

@ -2356,6 +2356,177 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@codemirror/autocomplete@npm:^6.0.0, @codemirror/autocomplete@npm:^6.4.0":
version: 6.12.0
resolution: "@codemirror/autocomplete@npm:6.12.0"
dependencies:
"@codemirror/language": "npm:^6.0.0"
"@codemirror/state": "npm:^6.0.0"
"@codemirror/view": "npm:^6.17.0"
"@lezer/common": "npm:^1.0.0"
peerDependencies:
"@codemirror/language": ^6.0.0
"@codemirror/state": ^6.0.0
"@codemirror/view": ^6.0.0
"@lezer/common": ^1.0.0
checksum: d01fa7e5b965285e7c26116c716c2f85d8fc4810f69abb9432dd97d60bf3a05cac547fe9dc6eaf8c7723a8621c017ff41a0b5df0e635c5a7cc37b041c7676ce8
languageName: node
linkType: hard
"@codemirror/commands@npm:^6.1.3":
version: 6.3.3
resolution: "@codemirror/commands@npm:6.3.3"
dependencies:
"@codemirror/language": "npm:^6.0.0"
"@codemirror/state": "npm:^6.4.0"
"@codemirror/view": "npm:^6.0.0"
"@lezer/common": "npm:^1.1.0"
checksum: 4b398b102d6afcbf0e0018b426287a7458867497811c9155790a3cc679b880765cd756bdb96bf35abc28fecb85c0938e618d39469ce8bc0724d4dea5d88f6ac2
languageName: node
linkType: hard
"@codemirror/lang-css@npm:^6.0.0, @codemirror/lang-css@npm:^6.0.1":
version: 6.2.1
resolution: "@codemirror/lang-css@npm:6.2.1"
dependencies:
"@codemirror/autocomplete": "npm:^6.0.0"
"@codemirror/language": "npm:^6.0.0"
"@codemirror/state": "npm:^6.0.0"
"@lezer/common": "npm:^1.0.2"
"@lezer/css": "npm:^1.0.0"
checksum: 03c9111904850127ad69658e5e6f5d61bacc0d56e080aab17bedbc99fced10810f9930454aee812f138767ee9e65efae246301c49db55294c2a67ccf75b28731
languageName: node
linkType: hard
"@codemirror/lang-html@npm:^6.4.0":
version: 6.4.8
resolution: "@codemirror/lang-html@npm:6.4.8"
dependencies:
"@codemirror/autocomplete": "npm:^6.0.0"
"@codemirror/lang-css": "npm:^6.0.0"
"@codemirror/lang-javascript": "npm:^6.0.0"
"@codemirror/language": "npm:^6.4.0"
"@codemirror/state": "npm:^6.0.0"
"@codemirror/view": "npm:^6.17.0"
"@lezer/common": "npm:^1.0.0"
"@lezer/css": "npm:^1.1.0"
"@lezer/html": "npm:^1.3.0"
checksum: 92928806ee7d2ff570b279a6527b65fa61be44fd1b409529aead08d9d2bed0c2408efaaeb2638929ecbcd5894868312a870d24da46679bab3d136342b25a8f93
languageName: node
linkType: hard
"@codemirror/lang-javascript@npm:^6.0.0, @codemirror/lang-javascript@npm:^6.1.2":
version: 6.2.1
resolution: "@codemirror/lang-javascript@npm:6.2.1"
dependencies:
"@codemirror/autocomplete": "npm:^6.0.0"
"@codemirror/language": "npm:^6.6.0"
"@codemirror/lint": "npm:^6.0.0"
"@codemirror/state": "npm:^6.0.0"
"@codemirror/view": "npm:^6.17.0"
"@lezer/common": "npm:^1.0.0"
"@lezer/javascript": "npm:^1.0.0"
checksum: edc874d7c0563aeb50c720e3c9cb4928f08263ae2b26f72e49d58c57ed75cfbd2f4ef9f704f15747087989b37d249cbb17e9833c5281d992ab33120bd910887b
languageName: node
linkType: hard
"@codemirror/language@npm:^6.0.0, @codemirror/language@npm:^6.3.2, @codemirror/language@npm:^6.4.0, @codemirror/language@npm:^6.6.0":
version: 6.10.0
resolution: "@codemirror/language@npm:6.10.0"
dependencies:
"@codemirror/state": "npm:^6.0.0"
"@codemirror/view": "npm:^6.23.0"
"@lezer/common": "npm:^1.1.0"
"@lezer/highlight": "npm:^1.0.0"
"@lezer/lr": "npm:^1.0.0"
style-mod: "npm:^4.0.0"
checksum: 674f87a5f7cae19a5e0cbdd0e06beade7f3694809cc30b31939aa04790dd43008942e3cd3b537ba5729fd14fed81c8ff62b7dcf51800db815cbf8d19d1d6a763
languageName: node
linkType: hard
"@codemirror/lint@npm:^6.0.0":
version: 6.4.2
resolution: "@codemirror/lint@npm:6.4.2"
dependencies:
"@codemirror/state": "npm:^6.0.0"
"@codemirror/view": "npm:^6.0.0"
crelt: "npm:^1.0.5"
checksum: 5e48e4f654c0890fe226fd84dca7fcea716dc72939eb2583151cc8cb6342187a0744b8bffc10b14ad8d8e67d96549c32b054926411c3a5b65de107388cb7ff6b
languageName: node
linkType: hard
"@codemirror/state@npm:^6.0.0, @codemirror/state@npm:^6.2.0, @codemirror/state@npm:^6.4.0":
version: 6.4.0
resolution: "@codemirror/state@npm:6.4.0"
checksum: d9129c456d1589ca376594620bad10c51d3dcdb57950f34637cea0e2ea073a695d426dc1cfc9b909b07365c236a6312da1eaf740c384c853009742493b8c9935
languageName: node
linkType: hard
"@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.7.1":
version: 6.23.1
resolution: "@codemirror/view@npm:6.23.1"
dependencies:
"@codemirror/state": "npm:^6.4.0"
style-mod: "npm:^4.1.0"
w3c-keyname: "npm:^2.2.4"
checksum: 42e6b73bcad6bf5d2e9578c54d166c63c4b1c7c7c7806b6f6b4bead8683dc7fcca52201a02a1f9b8ccf120a4ad87e7dcd68f09d9d3e416304dad41a75e20da82
languageName: node
linkType: hard
"@codesandbox/nodebox@npm:0.1.8":
version: 0.1.8
resolution: "@codesandbox/nodebox@npm:0.1.8"
dependencies:
outvariant: "npm:^1.4.0"
strict-event-emitter: "npm:^0.4.3"
checksum: 3de00d306ae0236524c436b8640c442a4541f0eb696820aa5f6fc66018dc66431d8d78edd9305986aa2877befca292a3906bb4c6af2ecba5f15dbd587d1c916d
languageName: node
linkType: hard
"@codesandbox/sandpack-client@npm:^2.11.2":
version: 2.11.2
resolution: "@codesandbox/sandpack-client@npm:2.11.2"
dependencies:
"@codesandbox/nodebox": "npm:0.1.8"
buffer: "npm:^6.0.3"
dequal: "npm:^2.0.2"
outvariant: "npm:1.4.0"
static-browser-server: "npm:1.0.3"
checksum: 70f97d27414755b564e929c3a824a4811f3664c268f54f98ea5e003f4ffc8f36111d79d819e77390a5c1d43805ae5779e8100df2fa5f7c900c40e2882890e888
languageName: node
linkType: hard
"@codesandbox/sandpack-react@npm:^2.11.3":
version: 2.11.3
resolution: "@codesandbox/sandpack-react@npm:2.11.3"
dependencies:
"@codemirror/autocomplete": "npm:^6.4.0"
"@codemirror/commands": "npm:^6.1.3"
"@codemirror/lang-css": "npm:^6.0.1"
"@codemirror/lang-html": "npm:^6.4.0"
"@codemirror/lang-javascript": "npm:^6.1.2"
"@codemirror/language": "npm:^6.3.2"
"@codemirror/state": "npm:^6.2.0"
"@codemirror/view": "npm:^6.7.1"
"@codesandbox/sandpack-client": "npm:^2.11.2"
"@lezer/highlight": "npm:^1.1.3"
"@react-hook/intersection-observer": "npm:^3.1.1"
"@stitches/core": "npm:^1.2.6"
anser: "npm:^2.1.1"
clean-set: "npm:^1.1.2"
codesandbox-import-util-types: "npm:^2.2.3"
dequal: "npm:^2.0.2"
escape-carriage: "npm:^1.3.1"
lz-string: "npm:^1.4.4"
react-devtools-inline: "npm:4.4.0"
react-is: "npm:^17.0.2"
peerDependencies:
react: ^16.8.0 || ^17 || ^18
react-dom: ^16.8.0 || ^17 || ^18
checksum: c78fb334a003df1c2398c04ae988259a61626865ac25f3bf2c1f46ac72d83f03d01aa6e2adcf2ab81ff781b8bf1caa8551f1a2c2ceca53f2eb78bb4f3604ea3c
languageName: node
linkType: hard
"@cspotcode/source-map-support@npm:^0.8.0": "@cspotcode/source-map-support@npm:^0.8.0":
version: 0.8.1 version: 0.8.1
resolution: "@cspotcode/source-map-support@npm:0.8.1" resolution: "@cspotcode/source-map-support@npm:0.8.1"
@ -3761,6 +3932,64 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@lezer/common@npm:^1.0.0, @lezer/common@npm:^1.0.2, @lezer/common@npm:^1.1.0, @lezer/common@npm:^1.2.0":
version: 1.2.1
resolution: "@lezer/common@npm:1.2.1"
checksum: b362ed2e97664e4b36b3dbff49b52d1bfc5accc0152b577fefd46e585d012ff685d1fd336d75d80066e01c0505b1135d4cf69be5e330b5bfec2e2650c437bcae
languageName: node
linkType: hard
"@lezer/css@npm:^1.0.0, @lezer/css@npm:^1.1.0":
version: 1.1.7
resolution: "@lezer/css@npm:1.1.7"
dependencies:
"@lezer/common": "npm:^1.2.0"
"@lezer/highlight": "npm:^1.0.0"
"@lezer/lr": "npm:^1.0.0"
checksum: cffa5f0c65c622af3df294d6697d88ee1b5ce445250b0b95576b7c1c9dfaa1513ca34fde514507662c7d6fb9a17a33f5372d01685fe0925ea3021d86da5fa222
languageName: node
linkType: hard
"@lezer/highlight@npm:^1.0.0, @lezer/highlight@npm:^1.1.3":
version: 1.2.0
resolution: "@lezer/highlight@npm:1.2.0"
dependencies:
"@lezer/common": "npm:^1.0.0"
checksum: 14a80cbfb0cd1ce716decb4f3a045d42e7146f539cfd483b62ce46c4586a26d2f4fbdc35ace1cad81645304be4d30eafb95a2b057c34dfd471d56c7fbd82df3a
languageName: node
linkType: hard
"@lezer/html@npm:^1.3.0":
version: 1.3.8
resolution: "@lezer/html@npm:1.3.8"
dependencies:
"@lezer/common": "npm:^1.2.0"
"@lezer/highlight": "npm:^1.0.0"
"@lezer/lr": "npm:^1.0.0"
checksum: 34eb58de4bf8fba49f84d8552fd6487bd981be5f8abd702cda517acdd89dee3dd33fec5a34349431d9357fe72512d4385bdec7617079a0c748b92c2e68b66e94
languageName: node
linkType: hard
"@lezer/javascript@npm:^1.0.0":
version: 1.4.13
resolution: "@lezer/javascript@npm:1.4.13"
dependencies:
"@lezer/common": "npm:^1.2.0"
"@lezer/highlight": "npm:^1.1.3"
"@lezer/lr": "npm:^1.3.0"
checksum: 2ebdfae4968e7d0ff1a6b21b13b511cfcc78ee08f548de0133d5dc6fa4e45f089ef35368ff82ab4370f22881768958e70d3d2d5c662784add0170a0fc48c9be0
languageName: node
linkType: hard
"@lezer/lr@npm:^1.0.0, @lezer/lr@npm:^1.3.0":
version: 1.4.0
resolution: "@lezer/lr@npm:1.4.0"
dependencies:
"@lezer/common": "npm:^1.0.0"
checksum: 7391d0d08e54cd9e4f4d46e6ee6aa81fbaf079b22ed9c13d01fc9928e0ffd16d0c2d21b2cedd55675ad6c687277db28349ea8db81c9c69222cd7e7c40edd026e
languageName: node
linkType: hard
"@mapbox/node-pre-gyp@npm:^1.0.5": "@mapbox/node-pre-gyp@npm:^1.0.5":
version: 1.0.11 version: 1.0.11
resolution: "@mapbox/node-pre-gyp@npm:1.0.11" resolution: "@mapbox/node-pre-gyp@npm:1.0.11"
@ -4521,6 +4750,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@open-draft/deferred-promise@npm:^2.1.0":
version: 2.2.0
resolution: "@open-draft/deferred-promise@npm:2.2.0"
checksum: bc3bb1668a555bb87b33383cafcf207d9561e17d2ca0d9e61b7ce88e82b66e36a333d3676c1d39eb5848022c03c8145331fcdc828ba297f88cb1de9c5cef6c19
languageName: node
linkType: hard
"@peculiar/asn1-schema@npm:^2.3.6": "@peculiar/asn1-schema@npm:^2.3.6":
version: 2.3.8 version: 2.3.8
resolution: "@peculiar/asn1-schema@npm:2.3.8" resolution: "@peculiar/asn1-schema@npm:2.3.8"
@ -5002,6 +5238,39 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@radix-ui/react-navigation-menu@npm:^1.1.4":
version: 1.1.4
resolution: "@radix-ui/react-navigation-menu@npm:1.1.4"
dependencies:
"@babel/runtime": "npm:^7.13.10"
"@radix-ui/primitive": "npm:1.0.1"
"@radix-ui/react-collection": "npm:1.0.3"
"@radix-ui/react-compose-refs": "npm:1.0.1"
"@radix-ui/react-context": "npm:1.0.1"
"@radix-ui/react-direction": "npm:1.0.1"
"@radix-ui/react-dismissable-layer": "npm:1.0.5"
"@radix-ui/react-id": "npm:1.0.1"
"@radix-ui/react-presence": "npm:1.0.1"
"@radix-ui/react-primitive": "npm:1.0.3"
"@radix-ui/react-use-callback-ref": "npm:1.0.1"
"@radix-ui/react-use-controllable-state": "npm:1.0.1"
"@radix-ui/react-use-layout-effect": "npm:1.0.1"
"@radix-ui/react-use-previous": "npm:1.0.1"
"@radix-ui/react-visually-hidden": "npm:1.0.3"
peerDependencies:
"@types/react": "*"
"@types/react-dom": "*"
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
"@types/react":
optional: true
"@types/react-dom":
optional: true
checksum: ae502c3757a1a0417209a76def7fd43088d3414658b8acc2010a1c68c3f737fdd20f601eed384ddc0fdb55404f6299ae1dd23e1ae28b7e902595d98b97699825
languageName: node
linkType: hard
"@radix-ui/react-popover@npm:^1.0.7": "@radix-ui/react-popover@npm:^1.0.7":
version: 1.0.7 version: 1.0.7
resolution: "@radix-ui/react-popover@npm:1.0.7" resolution: "@radix-ui/react-popover@npm:1.0.7"
@ -5458,6 +5727,27 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@react-hook/intersection-observer@npm:^3.1.1":
version: 3.1.1
resolution: "@react-hook/intersection-observer@npm:3.1.1"
dependencies:
"@react-hook/passive-layout-effect": "npm:^1.2.0"
intersection-observer: "npm:^0.10.0"
peerDependencies:
react: ">=16.8"
checksum: dc11695baa03765d53a23753c261f74dd0f226b9abd5bf3019d87c4f5e43da1919d81b8b902cd03d08f21f6484eb66a64139307bc84b379a95b2e3b9947e935f
languageName: node
linkType: hard
"@react-hook/passive-layout-effect@npm:^1.2.0":
version: 1.2.1
resolution: "@react-hook/passive-layout-effect@npm:1.2.1"
peerDependencies:
react: ">=16.8"
checksum: 217cb8aa90fb8e677672319a9a466d7752890cf4357c76df000b207696e9cc717cf2ee88080671cc9dae238a82f92093ab4f61ab2f6032d2a8db958fc7d99b5d
languageName: node
linkType: hard
"@remix-run/dev@npm:@vercel/remix-run-dev@1.15.0": "@remix-run/dev@npm:@vercel/remix-run-dev@1.15.0":
version: 1.15.0 version: 1.15.0
resolution: "@vercel/remix-run-dev@npm:1.15.0" resolution: "@vercel/remix-run-dev@npm:1.15.0"
@ -6611,6 +6901,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@stitches/core@npm:^1.2.6":
version: 1.2.8
resolution: "@stitches/core@npm:1.2.8"
checksum: bdee1772f033b20bfb1081a3bb8c3e85f2a44f381379d4c2ae21408336025e2d2578f79eec74db26824b852ebcc72de3edfc8c51418608f189aa5ffc26df667a
languageName: node
linkType: hard
"@supabase/auth-helpers-remix@npm:^0.2.2": "@supabase/auth-helpers-remix@npm:^0.2.2":
version: 0.2.6 version: 0.2.6
resolution: "@supabase/auth-helpers-remix@npm:0.2.6" resolution: "@supabase/auth-helpers-remix@npm:0.2.6"
@ -6939,14 +7236,18 @@ __metadata:
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "@tldraw/docs@workspace:apps/docs" resolution: "@tldraw/docs@workspace:apps/docs"
dependencies: dependencies:
"@codesandbox/sandpack-react": "npm:^2.11.3"
"@microsoft/api-extractor-model": "npm:^7.26.4" "@microsoft/api-extractor-model": "npm:^7.26.4"
"@microsoft/tsdoc": "npm:^0.14.2" "@microsoft/tsdoc": "npm:^0.14.2"
"@radix-ui/react-accordion": "npm:^1.1.2"
"@radix-ui/react-navigation-menu": "npm:^1.1.4"
"@types/broken-link-checker": "npm:^0.7.1" "@types/broken-link-checker": "npm:^0.7.1"
"@types/node": "npm:^18.7.3" "@types/node": "npm:^18.7.3"
"@types/sqlite3": "npm:^3.1.9" "@types/sqlite3": "npm:^3.1.9"
"@types/ws": "npm:^8.5.9" "@types/ws": "npm:^8.5.9"
"@vercel/analytics": "npm:^1.1.1" "@vercel/analytics": "npm:^1.1.1"
broken-link-checker: "npm:^0.7.8" broken-link-checker: "npm:^0.7.8"
classnames: "npm:^2.3.2"
concurrently: "npm:^8.2.2" concurrently: "npm:^8.2.2"
dotenv: "npm:^16.3.1" dotenv: "npm:^16.3.1"
eslint: "npm:^8.37.0" eslint: "npm:^8.37.0"
@ -8964,6 +9265,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"anser@npm:^2.1.1":
version: 2.1.1
resolution: "anser@npm:2.1.1"
checksum: 7cf63f2ac34a77a410da37bd7748f9d4f1b077763b51e5eadf7b355211050e12cabbb405268cc033f6351e1e60cc00ca18431a240053ffc94c12f0c758e1e66d
languageName: node
linkType: hard
"ansi-colors@npm:4.1.1": "ansi-colors@npm:4.1.1":
version: 4.1.1 version: 4.1.1
resolution: "ansi-colors@npm:4.1.1" resolution: "ansi-colors@npm:4.1.1"
@ -9988,6 +10296,16 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"buffer@npm:^6.0.3":
version: 6.0.3
resolution: "buffer@npm:6.0.3"
dependencies:
base64-js: "npm:^1.3.1"
ieee754: "npm:^1.2.1"
checksum: b6bc68237ebf29bdacae48ce60e5e28fc53ae886301f2ad9496618efac49427ed79096750033e7eab1897a4f26ae374ace49106a5758f38fb70c78c9fda2c3b1
languageName: node
linkType: hard
"busboy@npm:1.6.0": "busboy@npm:1.6.0":
version: 1.6.0 version: 1.6.0
resolution: "busboy@npm:1.6.0" resolution: "busboy@npm:1.6.0"
@ -10403,6 +10721,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"clean-set@npm:^1.1.2":
version: 1.1.2
resolution: "clean-set@npm:1.1.2"
checksum: b460b59c82a11945052b59efa1d51405c0ba11d227f2c2140e9f7803d702b4a36354bfee0077d46a758979891aa4322211ce392c86961f11bf57e2ff774d5506
languageName: node
linkType: hard
"clean-stack@npm:^2.0.0": "clean-stack@npm:^2.0.0":
version: 2.2.0 version: 2.2.0
resolution: "clean-stack@npm:2.2.0" resolution: "clean-stack@npm:2.2.0"
@ -10542,6 +10867,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"codesandbox-import-util-types@npm:^2.2.3":
version: 2.3.0
resolution: "codesandbox-import-util-types@npm:2.3.0"
checksum: 6cbb7050fb8bd822b4bf101792052a0d8a602bb2f85bb6f9422f9b1d11d170926c44af6f73e98fbb259792a45908ff4dc122123bec86d58176f6dadfc2f01f03
languageName: node
linkType: hard
"collect-v8-coverage@npm:^1.0.0": "collect-v8-coverage@npm:^1.0.0":
version: 1.0.2 version: 1.0.2
resolution: "collect-v8-coverage@npm:1.0.2" resolution: "collect-v8-coverage@npm:1.0.2"
@ -10911,6 +11243,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"crelt@npm:^1.0.5":
version: 1.0.6
resolution: "crelt@npm:1.0.6"
checksum: 5ed326ca6bd243b1dba6b943f665b21c2c04be03271824bc48f20dba324b0f8233e221f8c67312526d24af2b1243c023dc05a41bd8bd05d1a479fd2c72fb39c3
languageName: node
linkType: hard
"cross-env@npm:^7.0.3": "cross-env@npm:^7.0.3":
version: 7.0.3 version: 7.0.3
resolution: "cross-env@npm:7.0.3" resolution: "cross-env@npm:7.0.3"
@ -11061,6 +11400,16 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"d@npm:1, d@npm:^1.0.1":
version: 1.0.1
resolution: "d@npm:1.0.1"
dependencies:
es5-ext: "npm:^0.10.50"
type: "npm:^1.0.1"
checksum: 1296e3f92e646895681c1cb564abd0eb23c29db7d62c5120a279e84e98915499a477808e9580760f09e3744c0ed7ac8f7cff98d096ba9770754f6ef0f1c97983
languageName: node
linkType: hard
"damerau-levenshtein@npm:^1.0.8": "damerau-levenshtein@npm:^1.0.8":
version: 1.0.8 version: 1.0.8
resolution: "damerau-levenshtein@npm:1.0.8" resolution: "damerau-levenshtein@npm:1.0.8"
@ -11350,7 +11699,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"dequal@npm:^2.0.0, dequal@npm:^2.0.3": "dequal@npm:^2.0.0, dequal@npm:^2.0.2, dequal@npm:^2.0.3":
version: 2.0.3 version: 2.0.3
resolution: "dequal@npm:2.0.3" resolution: "dequal@npm:2.0.3"
checksum: 6ff05a7561f33603df87c45e389c9ac0a95e3c056be3da1a0c4702149e3a7f6fe5ffbb294478687ba51a9e95f3a60e8b6b9005993acd79c292c7d15f71964b6b checksum: 6ff05a7561f33603df87c45e389c9ac0a95e3c056be3da1a0c4702149e3a7f6fe5ffbb294478687ba51a9e95f3a60e8b6b9005993acd79c292c7d15f71964b6b
@ -11626,10 +11975,10 @@ __metadata:
languageName: unknown languageName: unknown
linkType: soft linkType: soft
"dotenv@npm:^16.0.0, dotenv@npm:^16.3.1": "dotenv@npm:^16.0.0, dotenv@npm:^16.0.3, dotenv@npm:^16.3.1":
version: 16.3.1 version: 16.4.1
resolution: "dotenv@npm:16.3.1" resolution: "dotenv@npm:16.4.1"
checksum: dbb778237ef8750e9e3cd1473d3c8eaa9cc3600e33a75c0e36415d0fa0848197f56c3800f77924c70e7828f0b03896818cd52f785b07b9ad4d88dba73fbba83f checksum: 8da20250633703686961004df3ea81b1f81e16fbe873372050676f54ca4053172d0589aae902e683eb575884d56b6bc89fe48bbac5e1e0bef606a061389ca33c
languageName: node languageName: node
linkType: hard linkType: hard
@ -12038,6 +12387,38 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"es5-ext@npm:^0.10.35, es5-ext@npm:^0.10.50":
version: 0.10.62
resolution: "es5-ext@npm:0.10.62"
dependencies:
es6-iterator: "npm:^2.0.3"
es6-symbol: "npm:^3.1.3"
next-tick: "npm:^1.1.0"
checksum: 3f6a3bcdb7ff82aaf65265799729828023c687a2645da04005b8f1dc6676a0c41fd06571b2517f89dcf143e0268d3d9ef0fdfd536ab74580083204c688d6fb45
languageName: node
linkType: hard
"es6-iterator@npm:^2.0.3":
version: 2.0.3
resolution: "es6-iterator@npm:2.0.3"
dependencies:
d: "npm:1"
es5-ext: "npm:^0.10.35"
es6-symbol: "npm:^3.1.1"
checksum: dbadecf3d0e467692815c2b438dfa99e5a97cbbecf4a58720adcb467a04220e0e36282399ba297911fd472c50ae4158fffba7ed0b7d4273fe322b69d03f9e3a5
languageName: node
linkType: hard
"es6-symbol@npm:^3, es6-symbol@npm:^3.1.1, es6-symbol@npm:^3.1.3":
version: 3.1.3
resolution: "es6-symbol@npm:3.1.3"
dependencies:
d: "npm:^1.0.1"
ext: "npm:^1.1.2"
checksum: b404e5ecae1a076058aa2ba2568d87e2cb4490cb1130784b84e7b4c09c570b487d4f58ed685a08db8d350bd4916500dd3d623b26e6b3520841d30d2ebb152f8d
languageName: node
linkType: hard
"esbuild-android-64@npm:0.14.47": "esbuild-android-64@npm:0.14.47":
version: 0.14.47 version: 0.14.47
resolution: "esbuild-android-64@npm:0.14.47" resolution: "esbuild-android-64@npm:0.14.47"
@ -12644,6 +13025,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"escape-carriage@npm:^1.3.1":
version: 1.3.1
resolution: "escape-carriage@npm:1.3.1"
checksum: 6d7613a5875977b04eb4651f3fa14eb8f54c72aa198b603c823502fcfef9f5969afc4c76d27b2f2fc008d88764fbf4c3a6e04d549d6134e2ff7f705c99e3ddb9
languageName: node
linkType: hard
"escape-html@npm:~1.0.3": "escape-html@npm:~1.0.3":
version: 1.0.3 version: 1.0.3
resolution: "escape-html@npm:1.0.3" resolution: "escape-html@npm:1.0.3"
@ -13325,6 +13713,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"ext@npm:^1.1.2":
version: 1.7.0
resolution: "ext@npm:1.7.0"
dependencies:
type: "npm:^2.7.2"
checksum: 666a135980b002df0e75c8ac6c389140cdc59ac953db62770479ee2856d58ce69d2f845e5f2586716350b725400f6945e51e9159573158c39f369984c72dcd84
languageName: node
linkType: hard
"extend-shallow@npm:^2.0.1": "extend-shallow@npm:^2.0.1":
version: 2.0.1 version: 2.0.1
resolution: "extend-shallow@npm:2.0.1" resolution: "extend-shallow@npm:2.0.1"
@ -15020,7 +15417,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"ieee754@npm:^1.1.13, ieee754@npm:^1.1.4": "ieee754@npm:^1.1.13, ieee754@npm:^1.1.4, ieee754@npm:^1.2.1":
version: 1.2.1 version: 1.2.1
resolution: "ieee754@npm:1.2.1" resolution: "ieee754@npm:1.2.1"
checksum: d9f2557a59036f16c282aaeb107832dc957a93d73397d89bbad4eb1130560560eb695060145e8e6b3b498b15ab95510226649a0b8f52ae06583575419fe10fc4 checksum: d9f2557a59036f16c282aaeb107832dc957a93d73397d89bbad4eb1130560560eb695060145e8e6b3b498b15ab95510226649a0b8f52ae06583575419fe10fc4
@ -15181,6 +15578,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"intersection-observer@npm:^0.10.0":
version: 0.10.0
resolution: "intersection-observer@npm:0.10.0"
checksum: d9ffce291459a2c4ada9e45f23ef805361691f93e9a41a2afe801d29e6c9b1d0054f7faddafd79ecc341d7008e1f921a2e67cadeae9692d9831af903f23fc644
languageName: node
linkType: hard
"invariant@npm:^2.2.4": "invariant@npm:^2.2.4":
version: 2.2.4 version: 2.2.4
resolution: "invariant@npm:2.2.4" resolution: "invariant@npm:2.2.4"
@ -18405,7 +18809,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"mime-db@npm:1.52.0": "mime-db@npm:1.52.0, mime-db@npm:^1.52.0":
version: 1.52.0 version: 1.52.0
resolution: "mime-db@npm:1.52.0" resolution: "mime-db@npm:1.52.0"
checksum: 54bb60bf39e6f8689f6622784e668a3d7f8bed6b0d886f5c3c446cb3284be28b30bf707ed05d0fe44a036f8469976b2629bbea182684977b084de9da274694d7 checksum: 54bb60bf39e6f8689f6622784e668a3d7f8bed6b0d886f5c3c446cb3284be28b30bf707ed05d0fe44a036f8469976b2629bbea182684977b084de9da274694d7
@ -18902,6 +19306,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"next-tick@npm:^1.1.0":
version: 1.1.0
resolution: "next-tick@npm:1.1.0"
checksum: 83b5cf36027a53ee6d8b7f9c0782f2ba87f4858d977342bfc3c20c21629290a2111f8374d13a81221179603ffc4364f38374b5655d17b6a8f8a8c77bdea4fe8b
languageName: node
linkType: hard
"next@npm:^14.0.4": "next@npm:^14.0.4":
version: 14.0.4 version: 14.0.4
resolution: "next@npm:14.0.4" resolution: "next@npm:14.0.4"
@ -19530,6 +19941,20 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"outvariant@npm:1.4.0":
version: 1.4.0
resolution: "outvariant@npm:1.4.0"
checksum: 07b9bcb9b3a2ff1b3db02af6b07d70e663082b30ddc08ff475d7c85fc623fdcc4433a4ab5b88f6902b62dbb284eef1be386aa537e14cef0519fad887ec483054
languageName: node
linkType: hard
"outvariant@npm:^1.3.0, outvariant@npm:^1.4.0":
version: 1.4.2
resolution: "outvariant@npm:1.4.2"
checksum: f16ba035fb65d1cbe7d2e06693dd42183c46bc8456713d9ddb5182d067defa7d78217edab0a2d3e173d3bacd627b2bd692195c7087c225b82548fbf52c677b38
languageName: node
linkType: hard
"p-cancelable@npm:^2.0.0": "p-cancelable@npm:^2.0.0":
version: 2.1.1 version: 2.1.1
resolution: "p-cancelable@npm:2.1.1" resolution: "p-cancelable@npm:2.1.1"
@ -20638,6 +21063,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"react-devtools-inline@npm:4.4.0":
version: 4.4.0
resolution: "react-devtools-inline@npm:4.4.0"
dependencies:
es6-symbol: "npm:^3"
checksum: 316a03bd21eb34f511e74e4e969e6a7d75d299800a4158ff59081457f024dc3aebc0c471938fd7e1c4676823fdcb546ca0d7c2d7ab74e34a9c1bdac31f2284b2
languageName: node
linkType: hard
"react-dom@npm:^18.2.0": "react-dom@npm:^18.2.0":
version: 18.2.0 version: 18.2.0
resolution: "react-dom@npm:18.2.0" resolution: "react-dom@npm:18.2.0"
@ -20697,7 +21131,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"react-is@npm:^17.0.1": "react-is@npm:^17.0.1, react-is@npm:^17.0.2":
version: 17.0.2 version: 17.0.2
resolution: "react-is@npm:17.0.2" resolution: "react-is@npm:17.0.2"
checksum: 73b36281e58eeb27c9cc6031301b6ae19ecdc9f18ae2d518bdb39b0ac564e65c5779405d623f1df9abf378a13858b79442480244bd579968afc1faf9a2ce5e05 checksum: 73b36281e58eeb27c9cc6031301b6ae19ecdc9f18ae2d518bdb39b0ac564e65c5779405d623f1df9abf378a13858b79442480244bd579968afc1faf9a2ce5e05
@ -22421,6 +22855,18 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"static-browser-server@npm:1.0.3":
version: 1.0.3
resolution: "static-browser-server@npm:1.0.3"
dependencies:
"@open-draft/deferred-promise": "npm:^2.1.0"
dotenv: "npm:^16.0.3"
mime-db: "npm:^1.52.0"
outvariant: "npm:^1.3.0"
checksum: d047c6c8a667a054db23c3d9770cf5e9d558694f052be81886f606ccc1c6dab034eb1fc5b35d1e3bf23a8dc6ca6f9cd7e4349bfb7b5cad1c2af13058c32dea86
languageName: node
linkType: hard
"statuses@npm:2.0.1": "statuses@npm:2.0.1":
version: 2.0.1 version: 2.0.1
resolution: "statuses@npm:2.0.1" resolution: "statuses@npm:2.0.1"
@ -22494,6 +22940,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"strict-event-emitter@npm:^0.4.3":
version: 0.4.6
resolution: "strict-event-emitter@npm:0.4.6"
checksum: abdbf59b6c45b599cc2f227fa473765d1510d155ebd22533e8ecb06110dfacb2ff07aece7fd528dde2b4f9e379d60f2687eee8af3fa2877c3ed88ee5b7ed2707
languageName: node
linkType: hard
"string-argv@npm:0.3.2, string-argv@npm:~0.3.1": "string-argv@npm:0.3.2, string-argv@npm:~0.3.1":
version: 0.3.2 version: 0.3.2
resolution: "string-argv@npm:0.3.2" resolution: "string-argv@npm:0.3.2"
@ -22759,6 +23212,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"style-mod@npm:^4.0.0, style-mod@npm:^4.1.0":
version: 4.1.0
resolution: "style-mod@npm:4.1.0"
checksum: e0bf199d699f15d382c31ae7f18b1508f426b35346002dd1b9072db2cd32fdb0ba3ce7a4629e2fa867a79ffe4830ebf11cb9bce6500815f6a0534fac763e94f4
languageName: node
linkType: hard
"style-to-object@npm:^0.4.1": "style-to-object@npm:^0.4.1":
version: 0.4.4 version: 0.4.4
resolution: "style-to-object@npm:0.4.4" resolution: "style-to-object@npm:0.4.4"
@ -23585,6 +24045,20 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"type@npm:^1.0.1":
version: 1.2.0
resolution: "type@npm:1.2.0"
checksum: b4d4b27d1926028be45fc5baaca205896e2a1fe9e5d24dc892046256efbe88de6acd0149e7353cd24dad596e1483e48ec60b0912aa47ca078d68cdd198b09885
languageName: node
linkType: hard
"type@npm:^2.7.2":
version: 2.7.2
resolution: "type@npm:2.7.2"
checksum: 602f1b369fba60687fa4d0af6fcfb814075bcaf9ed3a87637fb384d9ff849e2ad15bc244a431f341374562e51a76c159527ffdb1f1f24b0f1f988f35a301c41d
languageName: node
linkType: hard
"typed-array-buffer@npm:^1.0.0": "typed-array-buffer@npm:^1.0.0":
version: 1.0.0 version: 1.0.0
resolution: "typed-array-buffer@npm:1.0.0" resolution: "typed-array-buffer@npm:1.0.0"
@ -24601,6 +25075,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"w3c-keyname@npm:^2.2.4":
version: 2.2.8
resolution: "w3c-keyname@npm:2.2.8"
checksum: 95bafa4c04fa2f685a86ca1000069c1ec43ace1f8776c10f226a73296caeddd83f893db885c2c220ebeb6c52d424e3b54d7c0c1e963bbf204038ff1a944fbb07
languageName: node
linkType: hard
"w3c-xmlserializer@npm:^4.0.0": "w3c-xmlserializer@npm:^4.0.0":
version: 4.0.0 version: 4.0.0
resolution: "w3c-xmlserializer@npm:4.0.0" resolution: "w3c-xmlserializer@npm:4.0.0"