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
content.db
.env

View file

@ -3,4 +3,3 @@
content.db
node_modules
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:
- `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-api-content` to refresh just the 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-content` to generate just the regular 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",
"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",
@ -436,7 +445,7 @@
"name": "arrowShapeProps",
"variableTypeTokenRange": {
"startIndex": 1,
"endIndex": 42
"endIndex": 44
}
},
{
@ -2688,7 +2697,7 @@
},
{
"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",
@ -6018,6 +6027,33 @@
"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",
"canonicalReference": "@tldraw/tlschema!TLHandle#id:member",
@ -6100,6 +6136,33 @@
"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",
"canonicalReference": "@tldraw/tlschema!TLHandle#x:member",
@ -6618,6 +6681,42 @@
"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",
"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 isUncategorized = category.id === section.id + '_ucg'
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,
type: 'heading',
subtitle: isUncategorized ? section.title : `${section.title} / ${category.title}`,
title:
section.id === 'gen'
section.id === 'reference'
? article.title + '.' + heading.title
: article.title + ': ' + heading.title,
url: isUncategorized
@ -115,7 +115,7 @@ export async function GET(req: NextRequest) {
article.sectionId
)
const isUncategorized = category.id === section.id + '_ucg'
results[section.id === 'gen' ? 'apiDocs' : 'articles'].push({
results[section.id === 'reference' ? 'apiDocs' : 'articles'].push({
id: article.id,
type: 'article',
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) => {
for (const article of queryResults) {
const isApiDoc = article.sectionId === 'gen'
const isApiDoc = article.sectionId === 'reference'
const section = await db.getSection(article.sectionId)
const category = await db.getCategory(article.categoryId)
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
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 category = await db.getCategory(article.categoryId)
const isUncategorized = category.id === section.id + '_ucg'
@ -105,7 +105,7 @@ export async function GET(req: NextRequest) {
type: 'heading',
subtitle: isUncategorized ? section.title : `${section.title} / ${category.title}`,
title:
section.id === 'gen'
section.id === 'reference'
? article.title + '.' + heading.title
: article.title + ': ' + heading.title,
url: isUncategorized

View file

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

View file

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

View file

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

View file

@ -4,7 +4,7 @@ import { notFound } from 'next/navigation'
export default async function HomePage() {
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} />
throw notFound()
}

View file

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

View file

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

View file

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

View file

@ -1,20 +1,24 @@
'use client'
import * as NavigationMenu from '@radix-ui/react-navigation-menu'
import Link from 'next/link'
import { Icon } from './Icon'
import { Chevron } from './Icons'
import { Search } from './Search'
import { ThemeSwitcher } from './ThemeSwitcher'
export function Header({
activeId,
searchQuery,
searchType,
sectionId,
}: {
activeId: string | null
searchQuery?: string
searchType?: string
sectionId?: string
}) {
return (
<div className="layout__header">
<Link href="/">
<Link href="/quick-start">
<div
className="lockup"
style={{
@ -23,14 +27,16 @@ export function Header({
}}
/>
</Link>
<Search activeId={activeId} prevQuery={searchQuery} prevType={searchType} />
<div className="layout__header__socials">
<Search prevQuery={searchQuery} prevType={searchType} />
<div className="layout__header__sections_and_socials">
<SectionLinks sectionId={sectionId} />
<ThemeSwitcher />
<a
href="https://twitter.com/tldraw"
href="https://discord.com/invite/SBBEVCA4PG"
className="sidebar__button icon-button"
title="twitter"
title="discord"
>
<Icon icon="twitter" />
<Icon icon="discord" />
</a>
<a
href="https://github.com/tldraw/tldraw"
@ -39,15 +45,84 @@ export function Header({
>
<Icon icon="github" />
</a>
<a
href="https://discord.com/invite/SBBEVCA4PG"
className="sidebar__button icon-button"
title="discord"
>
<Icon icon="discord" />
</a>
<ThemeSwitcher />
</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',
prevQuery = '',
}: {
activeId: string | null
prevType?: 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 Link from 'next/link'
import { Header } from './Header'
import { Sidebar } from './Sidebar'
export async function SectionDocsPage({ section }: { section: Section }) {
const db = await getDb()
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 (
<>
<Header activeId={section.id} />
<Header sectionId={section.id} />
<Sidebar {...sidebar} />
<main className="article">
<div className="page-header">
<h1>{section.title}</h1>
</div>
{articles.length > 0 && (
<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>
)}
Choose your adventure on the left.
</main>
</>
)

View file

@ -1,65 +1,97 @@
'use client'
import {
ArticleHeadings,
SidebarContentArticleLink,
SidebarContentCategoryLink,
SidebarContentLink,
SidebarContentList,
SidebarContentSectionLink,
} from '@/types/content-types'
import * as Accordion from '@radix-ui/react-accordion'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import { createContext, useContext, useEffect } from 'react'
import { SectionLinks } from './Header'
import { Chevron } from './Icons'
import { Search } from './Search'
import { SidebarCloseButton } from './SidebarCloseButton'
import { ToggleMenuButton } from './ToggleMenuButton'
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 pathName = usePathname()
useEffect(() => {
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])
return (
<>
<activeLinkContext.Provider value={activeId}>
<linkContext.Provider value={{ activeId, articleId, categoryId, sectionId }}>
<div className="sidebar" onScroll={(e) => e.stopPropagation()}>
<Search activeId={activeId} />
<SidebarLinks links={links} />
<Search prevQuery={searchQuery} prevType={searchType} />
<div className="sidebar__section__links">
<SectionLinks sectionId={sectionId} />
</div>
<SidebarLinks headings={headings} links={links} />
<SidebarCloseButton />
</div>
<ToggleMenuButton />
</activeLinkContext.Provider>
</linkContext.Provider>
</>
)
}
export function SidebarLinks({ links }: { links: SidebarContentLink[] }) {
export function SidebarLinks({
headings,
links,
}: {
headings?: ArticleHeadings
links: SidebarContentLink[]
}) {
return (
<nav className="sidebar__nav">
<ul className="sidebar__list sidebar__sections__list">
{links.map((link) => (
<SidebarLink key={link.url} {...link} />
<SidebarLink key={link.url} headings={headings} {...link} />
))}
</ul>
</nav>
)
}
function SidebarLink(props: SidebarContentLink) {
function SidebarLink({ headings, ...props }: SidebarContentLink & { headings?: ArticleHeadings }) {
switch (props.type) {
case 'section': {
return <SidebarSection {...props} />
return <SidebarSection headings={headings} {...props} />
}
case 'article': {
return <SidebarArticle {...props} />
return <SidebarArticle headings={headings} {...props} />
}
case 'category': {
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
return (
<li className="sidebar__section">
{title && (
<Link href={children[0].url} title={title} className="sidebar__section__title">
{title}
</Link>
)}
{title && <span className="sidebar__section__title">{title}</span>}
<ul className="sidebar__list">
{children.map((link) => (
<SidebarLink key={link.url} {...link} />
<SidebarLink key={link.url} headings={headings} {...link} />
))}
</ul>
</li>
@ -87,10 +119,56 @@ function SidebarSection({ title, children }: SidebarContentSectionLink) {
}
function SidebarCategory({ title, children }: SidebarContentCategoryLink) {
const linkCtx = useContext(linkContext)
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 (
<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">
{title}
</Link>
@ -99,21 +177,44 @@ function SidebarCategory({ title, children }: SidebarContentCategoryLink) {
<SidebarLink key={link.url} {...link} />
))}
</ul>
</>
)}
<hr />
</li>
)
}
function SidebarArticle({ title, url, articleId }: SidebarContentArticleLink) {
const isActive = useContext(activeLinkContext) === articleId
function SidebarArticle({
title,
url,
articleId,
headings,
}: SidebarContentArticleLink & { headings?: ArticleHeadings }) {
const isActive = useContext(linkContext)?.activeId === articleId
return (
<li className="sidebar__article">
<Link href={url}>
<div className="sidebar__link" data-active={isActive}>
<Link href={url} className="sidebar__link" data-active={isActive}>
{title}
</div>
</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>
)
}

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
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
---
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">
<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.
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

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 file

@ -296,6 +296,8 @@ n/a
#### Select locked shapes on long press ([#1529](https://github.com/tldraw/tldraw/pull/1529))
#### highlighter fixes ([#1530](https://github.com/tldraw/tldraw/pull/1530))
[aq bug fixes]
@ -453,8 +455,8 @@ Highlighter pen is here! 🎉🎉🎉
#### 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 an old CodeSandbox link to the new StackBlitz.
* [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.
#### 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)
#### 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.

View file

@ -9,6 +9,7 @@ status: published
[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))
- 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
<img
width="300"
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"
/>
<img width="300" 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))
@ -54,14 +49,8 @@ Before/After
Before/After
<image
width="350"
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"
/>
<image width="350" 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))
@ -82,14 +71,8 @@ Before/After
Before & After:
<image
width="250"
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"
/>
<image width="250" 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))
@ -105,7 +88,7 @@ Before & After:
#### 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))
@ -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`
- Migrate snapshot [#1843](https://github.com/tldraw/tldraw/pull/1843) ([@steveruizok](https://github.com/steveruizok))
- `@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] 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`
- 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))

View file

@ -9,6 +9,7 @@ status: published
[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))
- Fixes opacity of shapes while erasing in a group or frame.
@ -23,15 +24,9 @@ status: published
Before/after:
<image
width="250"
src="https://github.com/tldraw/tldraw/assets/98838967/763a93eb-ffaa-405c-9255-e68ba88ed9a2"
/>
<image width="250" src="https://github.com/tldraw/tldraw/assets/98838967/763a93eb-ffaa-405c-9255-e68ba88ed9a2" />
<image
width="250"
src="https://github.com/tldraw/tldraw/assets/98838967/dc9d3f77-c1c5-40f2-a9fe-10c723b6a21c"
/>
<image 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))

View file

@ -9,6 +9,7 @@ status: published
[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 Mobile: Fixed a bug where you couldn't open some style dropdown options.

View file

@ -9,6 +9,7 @@ status: published
[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))
- 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)
#### zoom to affected shapes after undo/redo ([#2293](https://github.com/tldraw/tldraw/pull/2293))
- 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)
#### 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.

View file

@ -9,6 +9,7 @@ status: published
[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))
- Fixes url validations.

View file

@ -1,14 +1,14 @@
[
{
"id": "getting-started",
"title": "Getting Started",
"title": "Get Started",
"description": "Introduction articles for tldraw.",
"categories": [],
"sidebar_behavior": "show-links"
},
{
"id": "docs",
"title": "Documentation",
"title": "Learn tldraw",
"description": "Developer documentation for tldraw.",
"categories": [],
"sidebar_behavior": "show-links"
@ -18,10 +18,10 @@
"title": "Community",
"description": "Guides for contributing to tldraw's open source project.",
"categories": [],
"sidebar_behavior": "show-links"
"sidebar_behavior": "hidden"
},
{
"id": "gen",
"id": "reference",
"title": "API Reference",
"description": "Reference for the tldraw package's APIs (generated).",
"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
source: '/docs/introduction',
destination: '/introduction',
destination: '/quick-start',
permanent: true,
},
{
// For reverse compatibility with old links
source: '/introduction',
destination: '/quick-start',
permanent: true,
},
{
@ -31,30 +37,6 @@ const nextConfig = {
destination: '/usage',
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
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",
"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-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",
"format": "yarn run -T prettier --write .",
"watch-content": "tsx ./watcher.ts"
},
"dependencies": {
"@codesandbox/sandpack-react": "^2.11.3",
"@microsoft/api-extractor-model": "^7.26.4",
"@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/node": "^18.7.3",
"@types/sqlite3": "^3.1.9",
"@types/ws": "^8.5.9",
"@vercel/analytics": "^1.1.1",
"broken-link-checker": "^0.7.8",
"classnames": "^2.3.2",
"concurrently": "^8.2.2",
"dotenv": "^16.3.1",
"eslint": "^8.37.0",

View file

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

View file

@ -8,17 +8,17 @@ import { getApiMarkdown } from './getApiMarkdown'
export async function createApiMarkdown() {
const apiInputSection: InputSection = {
id: 'gen' as string,
id: 'reference' as string,
title: 'API Reference',
description: "Reference for the tldraw package's APIs (generated).",
categories: [],
sidebar_behavior: 'show-title',
sidebar_behavior: 'reference',
}
const addedCategories = new Set<string>()
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)) {
fs.rmSync(OUTPUT_DIR, { recursive: true })
@ -85,7 +85,7 @@ export async function createApiMarkdown() {
const sectionsJsonPath = path.join(CONTENT_DIR, 'sections.json')
const sectionsJson = JSON.parse(fs.readFileSync(sectionsJsonPath, 'utf8')) as InputSection[]
sectionsJson.splice(
sectionsJson.findIndex((s) => s.id === 'gen'),
sectionsJson.findIndex((s) => s.id === 'reference'),
1
)
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[]
try {
const inputApiSection = sections.find((s) => s.id === 'gen')
if (!inputApiSection) throw new Error(`Could not find section with id 'gen'`)
const inputApiSection = sections.find((s) => s.id === 'reference')
if (!inputApiSection) throw new Error(`Could not find section with id 'reference'`)
const outputApiSection = generateSection(inputApiSection, articles, 999999) // always at the end!
const contentComplete = { sections: [outputApiSection], articles }

View file

@ -13,7 +13,7 @@ export async function generateContent() {
}
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))
}

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
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 isGenerated = section.id === 'gen'
const isGenerated = section.id === 'reference'
for (const file of files) {
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 articleId = filename.replace(extension, '')
const parsed = matter({ content: fileContent }, {})
// 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
}
@ -48,6 +56,38 @@ export function generateSection(section: InputSection, articles: Articles, index
const isUncategorized = categoryId === section.id + '_ucg'
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 = {
id: articleId,
@ -55,7 +95,7 @@ export function generateSection(section: InputSection, articles: Articles, index
sectionIndex: 0,
groupIndex: -1,
groupId: parsed.data.group ?? null,
categoryIndex: parsed.data.order ?? -1,
categoryIndex: parsed.data.order ?? parsed.data.priority ?? -1,
sectionId: section.id,
author,
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
? parsed.data.sourceUrl ?? null
: `${section.id}/${articleId}${extension}`,
componentCode,
componentCodeFiles: componentCode ? JSON.stringify(componentCodeFiles) : null,
content: parsed.content,
path:
section.id === 'getting-started'
@ -166,14 +208,10 @@ export function generateSection(section: InputSection, articles: Articles, index
}
const sortArticles = (articleA: Article, articleB: Article) => {
const { categoryIndex: categoryIndexA, date: dateA = '01/01/1970' } = articleA
const { categoryIndex: categoryIndexB, date: dateB = '01/01/1970' } = articleB
const { categoryIndex: categoryIndexA, title: titleA } = articleA
const { categoryIndex: categoryIndexB, title: titleB } = articleB
return categoryIndexA === categoryIndexB
? new Date(dateB!).getTime() > new Date(dateA!).getTime()
? 1
: -1
: categoryIndexA < categoryIndexB
? -1
: 1
? titleA.localeCompare(titleB)
: categoryIndexA - categoryIndexB
}

View file

@ -5,6 +5,7 @@ import { nicelog } from '@/utils/nicelog'
import { connect } from './connect'
import { generateApiContent } from './generateApiContent'
import { generateContent } from './generateContent'
import { generateExamplesContent } from './generateExamplesContent'
export async function refreshContent(opts = {} as { silent: boolean }) {
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...')
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...')
await addContentToDb(db, await generateApiContent())

View file

@ -21,6 +21,7 @@
/* Light theme */
--color-text: #1d1d1d;
--color-text-secondary: #999;
--color-background: #ffffff;
--color-contrast: #ffffff;
--color-accent: #2f80ed;
@ -46,6 +47,7 @@
[data-theme='dark'] {
/* Dark theme */
--color-text: #fafafa;
--color-text-secondary: #999;
--color-background: hsl(240, 5%, 8%);
--color-contrast: #000;
--color-accent: #74b0ff;
@ -129,7 +131,7 @@ body {
top: 0px;
display: grid;
padding: 16px;
grid-template-columns: 264px 1fr auto;
grid-template-columns: auto 1fr auto;
gap: 16px;
justify-content: center;
align-items: center;
@ -145,12 +147,34 @@ body {
margin-bottom: 8px;
}
.layout__header__socials {
.layout__header__sections_and_socials {
display: flex;
align-items: center;
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 {
display: block;
position: relative;
@ -170,7 +194,7 @@ body {
position: absolute;
display: block;
content: '';
inset: 4px;
inset: 0;
background-color: var(--bg);
border-radius: 4px;
transition: background-color 0.1s ease-in-out;
@ -209,7 +233,7 @@ body {
justify-self: center;
width: 100%;
min-height: calc(100vh - 64px);
padding: 24px 0px 96px 0px;
padding: 0px 0px 96px 0px;
font-weight: 400;
overflow-x: hidden;
overflow-y: visible;
@ -444,6 +468,10 @@ body {
overflow-x: auto;
}
.article.article__example pre {
margin: 0px;
}
.article__api-docs pre {
margin: 24px 0px;
}
@ -491,6 +519,10 @@ body {
border: none;
}
.article.article__example hr {
margin: 0;
}
.article table {
margin: 20px 0px;
border-radius: 4px;
@ -734,7 +766,7 @@ body {
align-self: start;
top: 72px;
margin-left: -12px;
padding: 24px 12px 120px 28px;
padding: 0px 12px 120px 28px;
width: calc(100% + 24px);
max-height: calc(100vh);
z-index: 800;
@ -742,10 +774,10 @@ body {
font-size: 14px;
}
.sidebar a {
color: inherit;
.sidebar a,
.sidebar .sidebar__link {
font-weight: bold;
text-decoration: none;
cursor: pointer;
}
.sidebar hr {
@ -757,22 +789,32 @@ body {
border: none;
}
.sidebar__section__links {
display: none;
}
.sidebar__list {
padding: 0px;
margin: 0px;
list-style-type: none;
}
.sidebar__list li .sidebar__article {
height: 44px;
.sidebar__list li .sidebar__article,
.sidebar__list li .sidebar__article li {
margin-top: -6px;
margin-bottom: -6px;
width: 100%;
}
.sidebar__list li .sidebar__category li a,
.sidebar__list li .sidebar__article li a {
padding-left: 8px;
font-size: 13px;
}
.sidebar__link {
position: relative;
height: 40px;
padding: 6px 0;
display: flex;
align-items: center;
justify-content: flex-start;
@ -815,33 +857,51 @@ body {
.sidebar__section__title {
font-size: 14px;
font-weight: bold;
color: var(--color-text);
font-weight: normal;
margin-bottom: 4px;
position: relative;
height: 40px;
display: flex;
align-items: center;
justify-content: flex-start;
color: var(--color-text);
color: var(--color-text-secondary);
text-transform: uppercase;
--bg: transparent;
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;
margin-bottom: 12px;
border-bottom: 1px solid var(--color-tint-2);
}
@media (hover: hover) {
.sidebar__link:hover {
a.sidebar__link:hover {
cursor: pointer;
color: var(--color-text);
transition: background-color 0.12s ease-in-out;
transition-delay: 0.1s;
}
.sidebar__link:not([data-active='true']):hover {
a.sidebar__link:not([data-active='true']):hover {
--bg: var(--color-tint-1);
}
}
@ -1073,6 +1133,23 @@ body {
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 {
display: flex;
pointer-events: all;
@ -1257,3 +1334,93 @@ html[data-theme='light'] .hero__dark {
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
description: string
categories: InputCategory[]
sidebar_behavior: 'show-links' | 'show-title'
sidebar_behavior: 'show-links' | 'show-title' | 'hidden' | 'reference'
}
export type InputGroup = {
@ -55,7 +55,7 @@ export interface Section extends ContentPage {
/** An array of this section's categories. */
categories: Category[]
/** 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 {
@ -104,6 +104,10 @@ export interface Article extends ContentPage {
keywords: string[]
/** The URL where the article's source can be found. */
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 {
@ -155,7 +159,8 @@ export interface SidebarContentCategoryLink extends BaseSidebarLink {
export interface SidebarContentArticleLink extends BaseSidebarLink {
type: 'article'
articleId: string
articleId: string | null
groupId: string | null
}
export type SidebarContentLink =
@ -164,10 +169,14 @@ export type SidebarContentLink =
| SidebarContentArticleLink
export type SidebarContentList = {
headings?: ArticleHeadings
sectionId: string | null
categoryId: string | null
articleId: string | null
links: SidebarContentLink[]
activeId?: string | null
searchQuery?: string
searchType?: string
}
/* ---------- Finished / generated content ---------- */

View file

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

View file

@ -76,7 +76,7 @@ export class ContentVectorDatabase {
// This is the content that we'll create the embedding for
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.
// 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.
@ -217,8 +217,8 @@ export async function getVectorDb(
INCLUDE_API_CONTENT && INCLUDE_CONTENT
? await db.all('SELECT * FROM articles')
: INCLUDE_API_CONTENT
? await db.all('SELECT * FROM articles WHERE articles.sectionId = ?', 'gen')
: 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 != ?', 'reference')
nicelog(`Adding articles to index`)
const max = Math.min(articles.length, MAX_ARTICLES)

View file

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

View file

@ -6,7 +6,10 @@ import sqlite3 from 'sqlite3'
export async function autoLinkDocs(db: Database<sqlite3.Database, sqlite3.Statement>) {
// replace [TLEditor](?) with [TLEditor](/gen/editor/TLEditor)?
// 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)))
}
@ -30,7 +33,7 @@ export async function autoLinkDocsForArticle(
const article = await db.get(
'SELECT id, sectionId, categoryId FROM articles WHERE title = ? AND sectionId = ?',
title,
'gen'
'reference'
)
if (!article) throw Error(`Could not find article for ${_title} (${title})`)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,4 +5,4 @@ category: basic
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
component: ./OnlyEditor.tsx
category: editor
category: editor-api
priority: 3
---

View file

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

View file

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

View file

@ -1,7 +1,7 @@
---
title: UI events
component: ./UiEventsExample.tsx
category: editor
category: editor-api
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
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":
version: 0.8.1
resolution: "@cspotcode/source-map-support@npm:0.8.1"
@ -3761,6 +3932,64 @@ __metadata:
languageName: node
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":
version: 1.0.11
resolution: "@mapbox/node-pre-gyp@npm:1.0.11"
@ -4521,6 +4750,13 @@ __metadata:
languageName: node
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":
version: 2.3.8
resolution: "@peculiar/asn1-schema@npm:2.3.8"
@ -5002,6 +5238,39 @@ __metadata:
languageName: node
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":
version: 1.0.7
resolution: "@radix-ui/react-popover@npm:1.0.7"
@ -5458,6 +5727,27 @@ __metadata:
languageName: node
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":
version: 1.15.0
resolution: "@vercel/remix-run-dev@npm:1.15.0"
@ -6611,6 +6901,13 @@ __metadata:
languageName: node
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":
version: 0.2.6
resolution: "@supabase/auth-helpers-remix@npm:0.2.6"
@ -6939,14 +7236,18 @@ __metadata:
version: 0.0.0-use.local
resolution: "@tldraw/docs@workspace:apps/docs"
dependencies:
"@codesandbox/sandpack-react": "npm:^2.11.3"
"@microsoft/api-extractor-model": "npm:^7.26.4"
"@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/node": "npm:^18.7.3"
"@types/sqlite3": "npm:^3.1.9"
"@types/ws": "npm:^8.5.9"
"@vercel/analytics": "npm:^1.1.1"
broken-link-checker: "npm:^0.7.8"
classnames: "npm:^2.3.2"
concurrently: "npm:^8.2.2"
dotenv: "npm:^16.3.1"
eslint: "npm:^8.37.0"
@ -8964,6 +9265,13 @@ __metadata:
languageName: node
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":
version: 4.1.1
resolution: "ansi-colors@npm:4.1.1"
@ -9988,6 +10296,16 @@ __metadata:
languageName: node
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":
version: 1.6.0
resolution: "busboy@npm:1.6.0"
@ -10403,6 +10721,13 @@ __metadata:
languageName: node
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":
version: 2.2.0
resolution: "clean-stack@npm:2.2.0"
@ -10542,6 +10867,13 @@ __metadata:
languageName: node
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":
version: 1.0.2
resolution: "collect-v8-coverage@npm:1.0.2"
@ -10911,6 +11243,13 @@ __metadata:
languageName: node
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":
version: 7.0.3
resolution: "cross-env@npm:7.0.3"
@ -11061,6 +11400,16 @@ __metadata:
languageName: node
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":
version: 1.0.8
resolution: "damerau-levenshtein@npm:1.0.8"
@ -11350,7 +11699,7 @@ __metadata:
languageName: node
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
resolution: "dequal@npm:2.0.3"
checksum: 6ff05a7561f33603df87c45e389c9ac0a95e3c056be3da1a0c4702149e3a7f6fe5ffbb294478687ba51a9e95f3a60e8b6b9005993acd79c292c7d15f71964b6b
@ -11626,10 +11975,10 @@ __metadata:
languageName: unknown
linkType: soft
"dotenv@npm:^16.0.0, dotenv@npm:^16.3.1":
version: 16.3.1
resolution: "dotenv@npm:16.3.1"
checksum: dbb778237ef8750e9e3cd1473d3c8eaa9cc3600e33a75c0e36415d0fa0848197f56c3800f77924c70e7828f0b03896818cd52f785b07b9ad4d88dba73fbba83f
"dotenv@npm:^16.0.0, dotenv@npm:^16.0.3, dotenv@npm:^16.3.1":
version: 16.4.1
resolution: "dotenv@npm:16.4.1"
checksum: 8da20250633703686961004df3ea81b1f81e16fbe873372050676f54ca4053172d0589aae902e683eb575884d56b6bc89fe48bbac5e1e0bef606a061389ca33c
languageName: node
linkType: hard
@ -12038,6 +12387,38 @@ __metadata:
languageName: node
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":
version: 0.14.47
resolution: "esbuild-android-64@npm:0.14.47"
@ -12644,6 +13025,13 @@ __metadata:
languageName: node
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":
version: 1.0.3
resolution: "escape-html@npm:1.0.3"
@ -13325,6 +13713,15 @@ __metadata:
languageName: node
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":
version: 2.0.1
resolution: "extend-shallow@npm:2.0.1"
@ -15020,7 +15417,7 @@ __metadata:
languageName: node
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
resolution: "ieee754@npm:1.2.1"
checksum: d9f2557a59036f16c282aaeb107832dc957a93d73397d89bbad4eb1130560560eb695060145e8e6b3b498b15ab95510226649a0b8f52ae06583575419fe10fc4
@ -15181,6 +15578,13 @@ __metadata:
languageName: node
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":
version: 2.2.4
resolution: "invariant@npm:2.2.4"
@ -18405,7 +18809,7 @@ __metadata:
languageName: node
linkType: hard
"mime-db@npm:1.52.0":
"mime-db@npm:1.52.0, mime-db@npm:^1.52.0":
version: 1.52.0
resolution: "mime-db@npm:1.52.0"
checksum: 54bb60bf39e6f8689f6622784e668a3d7f8bed6b0d886f5c3c446cb3284be28b30bf707ed05d0fe44a036f8469976b2629bbea182684977b084de9da274694d7
@ -18902,6 +19306,13 @@ __metadata:
languageName: node
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":
version: 14.0.4
resolution: "next@npm:14.0.4"
@ -19530,6 +19941,20 @@ __metadata:
languageName: node
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":
version: 2.1.1
resolution: "p-cancelable@npm:2.1.1"
@ -20638,6 +21063,15 @@ __metadata:
languageName: node
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":
version: 18.2.0
resolution: "react-dom@npm:18.2.0"
@ -20697,7 +21131,7 @@ __metadata:
languageName: node
linkType: hard
"react-is@npm:^17.0.1":
"react-is@npm:^17.0.1, react-is@npm:^17.0.2":
version: 17.0.2
resolution: "react-is@npm:17.0.2"
checksum: 73b36281e58eeb27c9cc6031301b6ae19ecdc9f18ae2d518bdb39b0ac564e65c5779405d623f1df9abf378a13858b79442480244bd579968afc1faf9a2ce5e05
@ -22421,6 +22855,18 @@ __metadata:
languageName: node
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":
version: 2.0.1
resolution: "statuses@npm:2.0.1"
@ -22494,6 +22940,13 @@ __metadata:
languageName: node
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":
version: 0.3.2
resolution: "string-argv@npm:0.3.2"
@ -22759,6 +23212,13 @@ __metadata:
languageName: node
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":
version: 0.4.4
resolution: "style-to-object@npm:0.4.4"
@ -23585,6 +24045,20 @@ __metadata:
languageName: node
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":
version: 1.0.0
resolution: "typed-array-buffer@npm:1.0.0"
@ -24601,6 +25075,13 @@ __metadata:
languageName: node
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":
version: 4.0.0
resolution: "w3c-xmlserializer@npm:4.0.0"