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

@ -2,5 +2,4 @@
.lazy/*
content.db
node_modules
utils/vector-db/index.json
**/*.api.json
utils/vector-db/index.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 it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

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) => (
<Link key={article.id} href={`/${section.id}/${category.id}/${article.id}`}>
<li>{article.title}</li>
</Link>
<li>
<Link key={article.id} href={`/${section.id}/${category.id}/${article.id}`}>
{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,33 +119,102 @@ 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">
<Link href={children[0].url} title={title} className="sidebar__link">
{title}
</Link>
<ul className="sidebar__list">
{children.map((link) => (
<SidebarLink key={link.url} {...link} />
))}
</ul>
{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>
<ul className="sidebar__list">
{children.map((link) => (
<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}>
{title}
</div>
<Link href={url} className="sidebar__link" data-active={isActive}>
{title}
</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)

File diff suppressed because it is too large Load diff

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.
@ -111,4 +112,4 @@ status: published
- David Sheldrick ([@ds300](https://github.com/ds300))
- Lu Wilson ([@TodePond](https://github.com/TodePond))
- Mitja Bezenšek ([@MitjaBezensek](https://github.com/MitjaBezensek))
- Steve Ruiz ([@steveruizok](https://github.com/steveruizok))
- Steve Ruiz ([@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.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))
@ -149,7 +132,7 @@ Before & After:
#### :recycle: fix: editing is not terminated after the conversion is confirmed. ([#1885](https://github.com/tldraw/tldraw/pull/1885))
- fix: editing is not terminated after the conversion is confirmed.
- fix: editing is not terminated after the conversion is confirmed.
#### Update community translations ([#1889](https://github.com/tldraw/tldraw/pull/1889))
@ -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))
@ -658,4 +634,4 @@ Removed a feature to reset the viewport back to a shape that is being edited.
- Ricardo Crespo ([@ricardo-crespo](https://github.com/ricardo-crespo))
- Steve Ruiz ([@steveruizok](https://github.com/steveruizok))
- Taha ([@Taha-Hassan-Git](https://github.com/Taha-Hassan-Git))
- Takuto Mori Gump ([@mr04vv](https://github.com/mr04vv))
- Takuto Mori Gump ([@mr04vv](https://github.com/mr04vv))

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))
@ -102,4 +97,4 @@ Before/after:
- Mitja Bezenšek ([@MitjaBezensek](https://github.com/MitjaBezensek))
- Prince Mendiratta ([@Prince-Mendiratta](https://github.com/Prince-Mendiratta))
- Steve Ruiz ([@steveruizok](https://github.com/steveruizok))
- Taha ([@Taha-Hassan-Git](https://github.com/Taha-Hassan-Git))
- Taha ([@Taha-Hassan-Git](https://github.com/Taha-Hassan-Git))

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.
@ -48,8 +49,8 @@ status: published
#### [fix] locked shape of opacity problem with eraser.pointing ([#2073](https://github.com/tldraw/tldraw/pull/2073))
- locked shape of opacity problem with eraser.pointing
Before/after:
![A](https://github.com/tldraw/tldraw/assets/59823089/7483506c-72ac-45cc-93aa-f2a794ea8ff0) ![B](https://github.com/tldraw/tldraw/assets/59823089/ef0f988c-83f5-46a2-b891-0a391bca2f87)
Before/after:
![A](https://github.com/tldraw/tldraw/assets/59823089/7483506c-72ac-45cc-93aa-f2a794ea8ff0) ![B](https://github.com/tldraw/tldraw/assets/59823089/ef0f988c-83f5-46a2-b891-0a391bca2f87)
---
@ -106,4 +107,4 @@ status: published
- Lu Wilson ([@TodePond](https://github.com/TodePond))
- Mitja Bezenšek ([@MitjaBezensek](https://github.com/MitjaBezensek))
- Steve Ruiz ([@steveruizok](https://github.com/steveruizok))
- Taha ([@Taha-Hassan-Git](https://github.com/Taha-Hassan-Git))
- Taha ([@Taha-Hassan-Git](https://github.com/Taha-Hassan-Git))

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.
@ -183,4 +184,4 @@ status: published
- Lu Wilson ([@TodePond](https://github.com/TodePond))
- Mitja Bezenšek ([@MitjaBezensek](https://github.com/MitjaBezensek))
- Steve Ruiz ([@steveruizok](https://github.com/steveruizok))
- Taha ([@Taha-Hassan-Git](https://github.com/Taha-Hassan-Git))
- Taha ([@Taha-Hassan-Git](https://github.com/Taha-Hassan-Git))

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
@ -247,4 +248,4 @@ status: published
- Mitja Bezenšek ([@MitjaBezensek](https://github.com/MitjaBezensek))
- Steve Ruiz ([@steveruizok](https://github.com/steveruizok))
- Sugit ([@sugitlab](https://github.com/sugitlab))
- Taha ([@Taha-Hassan-Git](https://github.com/Taha-Hassan-Git))
- Taha ([@Taha-Hassan-Git](https://github.com/Taha-Hassan-Git))

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.
@ -148,4 +149,4 @@ Updated translations for German, Korean, Russian, Ukrainian, Traditional Chinese
- MinhoPark ([@Lennon57](https://github.com/Lennon57))
- Mitja Bezenšek ([@MitjaBezensek](https://github.com/MitjaBezensek))
- Steve Ruiz ([@steveruizok](https://github.com/steveruizok))
- Taha ([@Taha-Hassan-Git](https://github.com/Taha-Hassan-Git))
- Taha ([@Taha-Hassan-Git](https://github.com/Taha-Hassan-Git))

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.
@ -163,4 +164,4 @@ status: published
- Mitja Bezenšek ([@MitjaBezensek](https://github.com/MitjaBezensek))
- Stan Flint ([@StanFlint](https://github.com/StanFlint))
- Steve Ruiz ([@steveruizok](https://github.com/steveruizok))
- Taha ([@Taha-Hassan-Git](https://github.com/Taha-Hassan-Git))
- Taha ([@Taha-Hassan-Git](https://github.com/Taha-Hassan-Git))

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,7 +274,14 @@ export class ContentDatabase {
links.push({ type: 'section', title: section.title, url: section.path, children })
// Cache the links structure for next time
this._sidebarContentLinks = links
if (sectionId === 'examples') {
this._sidebarExamplesContentLinks = links
}
if (sectionId === 'reference') {
this._sidebarReferenceContentLinks = links
} else {
this._sidebarContentLinks = links
}
}
}

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"