An initial pass at sync docs

### Change type

- [x] `other`

---------

Co-authored-by: Adam Wiggins <a@adamwiggins.com>
Co-authored-by: David Sheldrick <d.j.sheldrick@gmail.com>
pull/4259/head
alex 2024-07-23 12:21:28 +01:00 zatwierdzone przez GitHub
rodzic 9740f865a5
commit ff8657fd0e
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
31 zmienionych plików z 441 dodań i 177 usunięć

Wyświetl plik

@ -4,7 +4,6 @@ import { SandpackCodeViewer, SandpackFiles, SandpackProvider } from '@codesandbo
import { getOwnProperty } from '@tldraw/utils'
import { useTheme } from 'next-themes'
import React, {
Fragment,
ReactElement,
ReactNode,
cloneElement,
@ -19,7 +18,7 @@ import { A } from './generic'
const CodeLinksContext = createContext<Record<string, string>>({})
export function CodeLinkProvider({
export function CodeLinks({
children,
links,
}: {
@ -29,8 +28,14 @@ export function CodeLinkProvider({
return <CodeLinksContext.Provider value={links}>{children}</CodeLinksContext.Provider>
}
const FocusLinesContext = createContext<null | number[]>(null)
export function FocusLines({ children, lines }: { children: ReactNode; lines: number[] }) {
return <FocusLinesContext.Provider value={lines}>{children}</FocusLinesContext.Provider>
}
export function Code({ children, ...props }: React.ComponentProps<'code'>) {
const codeLinks = useContext(CodeLinksContext)
const focusLines = useContext(FocusLinesContext)
const newChildren = useMemo(() => {
// to linkify code, we have to do quite a lot of work. we need to take the output of
@ -88,6 +93,9 @@ export function Code({ children, ...props }: React.ComponentProps<'code'>) {
return tokens
}
// what line are we on?
let lineNumber = 1
// recursively process the children array
function processChildrenArray(children: ReactNode): ReactNode {
if (!Array.isArray(children)) {
@ -121,15 +129,29 @@ export function Code({ children, ...props }: React.ComponentProps<'code'>) {
}
function pushInCurrentSpan(content: ReactNode) {
const lineProps = {
'data-line': lineNumber,
'data-line-state': focusLines
? focusLines.includes(lineNumber)
? 'focus'
: 'blur'
: undefined,
}
if (lastSeenHighlightSpan) {
newChildren.push(
cloneElement(lastSeenHighlightSpan, {
key: newChildren.length,
children: content,
...lineProps,
})
)
} else {
newChildren.push(<Fragment key={newChildren.length}>{content}</Fragment>)
newChildren.push(
<span key={newChildren.length} {...lineProps}>
{content}
</span>
)
}
}
@ -161,7 +183,16 @@ export function Code({ children, ...props }: React.ComponentProps<'code'>) {
}
} else {
finishCurrentIdentifier()
pushInCurrentSpan(token)
const lineParts = token.split('\n')
for (let i = 0; i < lineParts.length; i++) {
let part = lineParts[i]
if (i > 0) {
lineNumber += 1
part = '\n' + part
}
pushInCurrentSpan(part)
}
}
}
@ -197,7 +228,7 @@ export function Code({ children, ...props }: React.ComponentProps<'code'>) {
}
return processChildrenArray(children)
}, [children, codeLinks])
}, [children, codeLinks, focusLines])
return <code {...props}>{newChildren}</code>
}

Wyświetl plik

@ -142,7 +142,7 @@ export const Video = (props: any) => {
/* ------------------- Code Blocks ------------------ */
export const Pre = (props: any) => {
if (props.children?.props?.className.startsWith('language-')) {
if (props.children?.props?.className?.startsWith('language-')) {
return props.children
}
@ -173,8 +173,8 @@ export const Embed = (props: any) => {
export const Callout = ({ icon, children }: any) => {
return (
<div className="article__callout">
<span>{icon}</span>
<p>{children}</p>
<div className="article__callout__icon">{icon}</div>
<div className="article__callout__message">{children}</div>
</div>
)
}

Wyświetl plik

@ -1,6 +1,6 @@
import * as customComponents from '../article-components'
import * as apiComponents from './api-docs'
import { Code, CodeBlock, CodeLinkProvider } from './code'
import { Code, CodeBlock, CodeLinks, FocusLines } from './code'
import {
A,
ApiHeading,
@ -32,7 +32,8 @@ export const components = {
a: A,
blockquote: Blockquote,
code: Code,
CodeLinkProvider,
CodeLinks: CodeLinks,
FocusLines: FocusLines,
h1: Heading1,
h2: Heading2,
h3: Heading3,

Wyświetl plik

@ -3,7 +3,7 @@ title: Assets
status: published
author: steveruizok
date: 6/9/2023
order: 3
order: 5
keywords:
- image
- video
@ -13,14 +13,30 @@ keywords:
- files
---
Assets are dynamic records that store data about a shared asset. For example, our image and video shapes refer to assets rather than embedding their source files directly. This is essential because, by default, these asset sources are stored in base64 data.
Assets are dynamic records that store data about a shared asset. For example, our image and video
shapes refer to assets rather than embedding their source files directly. Asset storage and
retrieval is controlled by [`TLAssetStore`](?). Different [`TLStore`](?) setups require different asset setups:
You can use assets for any shared piece of information, however they're best used for images, videos, and files.
- By default, the store is in-memory only, so [`inlineBase64AssetStore`](?) converts images to data
URLs.
- When using the [`persistenceKey` prop](/docs/persistence#The-persistenceKey-prop), the store is
synced to the browser's local
[IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API), so we store images
there too.
- When using a [multiplayer sync server](/docs/sync), you would implement TLAssetStore to upload
images to e.g. an S3 bucket.
## Examples
While we're working on docs for this part of the project, please refer to the examples below:
- [Using images hosted](https://github.com/tldraw/tldraw/blob/main/apps/examples/src/examples/hosted-images/HostedImagesExample.tsx)
- [Customizing the default asset options](https://github.com/tldraw/tldraw/blob/main/apps/examples/src/examples/asset-props/AssetPropsExample.tsx)
- [Handling pasted / dropped external content](https://github.com/tldraw/tldraw/blob/main/apps/examples/src/examples/external-content-sources/ExternalContentSourcesExample.tsx)
- [Using images
hosted](/examples/data/assets/hosted-images)
- [Customizing the default asset
options](/examples/data/assets/asset-props)
- [Handling pasted / dropped external
content](/examples/data/assets/external-content-sources)
- [A simple asset store that uploads content to a remote
server](https://github.com/tldraw/tldraw/blob/main/apps/simple-server-example/src/client/App.tsx)
- [A more complex asset store that optimizes images when retrieving
them](https://github.com/tldraw/tldraw/blob/main/packages/sync/src/useMutliplayerDemo.ts#L87)

Wyświetl plik

@ -1,75 +0,0 @@
---
title: Collaboration
status: published
author: ds300
date: 3/22/2023
order: 8
---
We've designed the tldraw SDK to work with any collaboration backend. Depending on which backend you choose, you will need an interface that pipes changes coming from the editor to the backend and then merge changes from the backend back to the editor.
The best way to get started is by adapting one of our examples.
### Yjs sync example
We created a [tldraw-yjs example](https://github.com/tldraw/tldraw-yjs-example) to illustrate a way of using the [yjs](https://yjs.dev) library with the tldraw SDK. If you need a "drop in solution" for prototyping multiplayer experiences with tldraw, start here.
### Sockets example
We have a [sockets example](https://github.com/tldraw/tldraw-sockets-example) that uses [PartyKit](https://www.partykit.io/) as a backend. Unlike the yjs example, this example does not use any special data structures to handle conflicts. It should be a good starting point if you needed to write your own conflict-resolution logic.
### Our own sync engine
We developed our own sync engine for use on tldraw.com based on a push/pull/rebase-style algorithm. It powers our "shared projects", such as [this one](https://tldraw.com/r). The engine's source code can be found [here](https://github.com/tldraw/tldraw/tree/main/packages/sync-core). It was designed to be hosted on Cloudflare workers with [DurableObjects](https://developers.cloudflare.com/durable-objects/).
We don't suggest using this code directly. However, like our other examples, it may serve as a good reference for your own sync engine.
## Store data
For information about how to synchronize the store with other processes, i.e. how to get data out and put data in, including from remote sources, see the [Persistence](/docs/persistence) page.
## User presence
Tldraw has support for displaying the 'presence' of other users. Presence information consists of:
- The user's pointer position
- The user's set of selected shapes
- The user's viewport bounds (the part of the canvas they are currently viewing)
- The user's name, id, and a color to represent them
This information will usually come from two sources:
- The tldraw editor state (e.g. pointer position, selected shapes)
- The data layer of whichever app tldraw has been embedded in (e.g. user name, user id)
Tldraw is agnostic about how this data is shared among users. However, in order for tldraw to use the presence data it needs to be put into the editor's store as `instance_presence` records.
We provide a helper for constructing a reactive signal for an `instance_presence` record locally, which can then be sent to other clients somehow. It is called [createPresenceStateDerivation](?).
```ts
import { createPresenceStateDerivation, react, atom } from 'tldraw'
// First you need to create a Signal containing the basic user details: id, name, and color
const user = atom<{ id: string; color: string; name: string }>('user', {
id: myUser.id,
color: myUser.color,
name: myUser.name,
})
// if you don't have your own user data backend, you can use our localStorage-only user preferences store
// import { getUserPreferences, computed } from 'tldraw'
// const user = computed('user', getUserPreferences)
// Then, with access to your store instance, you can create a presence signal
const userPresence = createPresenceStateDerivation(user)(store)
// Then you can listen for changes to the presence signal and send them to other clients
const unsub = react('update presence', () => {
const presence = userPresence.get()
broadcastPresence(presence)
})
```
The other clients would then call `store.put([presence])` to add the presence information to their store.
Any such `instance_presence` records tldraw finds in the store that have a different user `id` than the editor's configured user id will cause the presence information to be rendered on the canvas.

Wyświetl plik

@ -3,7 +3,7 @@ title: Persistence
status: published
author: steveruizok
date: 3/22/2023
order: 6
order: 4
keywords:
- data
- sync
@ -13,7 +13,9 @@ keywords:
- localstorage
---
Persistence in tldraw means storing information about the editor's state to a database and then restoring it later. There are a few options that developers have for getting data into tldraw and out again.
In tldraw, persitence means storing information about the editor's state to a database and then restoring it later.
The simplest implementation is the browser's local storage. But this also provides the hooks for a sync engine, which can send realtime incremenal updates the canvas to your backend server, allowing multiple people to collaborate on the canvas.
## The `"persistenceKey"` prop
@ -406,18 +408,6 @@ const migrations = createShapePropsMigrationSequence({
Tldraw ships with a local-only sync engine based on `IndexedDb` and `BroadcastChannel` called [`TLLocalSyncClient`](https://github.com/tldraw/tldraw/blob/main/packages/editor/src/lib/utils/sync/TLLocalSyncClient.ts).
### Tldraw.com sync engine
[tldraw.com/r](https://tldraw.com/r) currently uses a simple custom sync engine based on a push/pull/rebase-style algorithm.
It can be found [here](https://github.com/tldraw/tldraw/tree/main/packages/sync-core).
It was optimized for Cloudflare workers with [DurableObjects](https://developers.cloudflare.com/durable-objects/)
We don't suggest using our code directly yet, but it may serve as a good reference for your own sync engine.
### Yjs sync example
We created a [tldraw-yjs example](https://github.com/tldraw/tldraw-yjs-example) to illustrate a way of using yjs with the tldraw SDK.
### Shape props migrations example
Our [custom-config example](/examples/shapes/tools/custom-config) shows how to add custom shape props migrations to the tldraw store.

Wyświetl plik

@ -0,0 +1,139 @@
---
title: Sync
status: published
author: alex
date: 7/11/2024
order: 6
keywords:
- data
- sync
- persistence
- database
- multiplayer
- collaboration
- server
- websockets
---
Allow multiple users to collaborate on the canvas with **tldraw sync**, an optional module included with tldraw.
<div className="article__image" style={{ border: 'none' }}>
<img
alt="Multiplayer example using tldraw sync"
src="/images/multiplayer.gif"
style={{
// learn this one weird trick for transparent animated gifs. doctors hate her!!!
mask: 'url(/images/multiplayer_mask.png) luminance center 100% / 100% no-repeat',
WebkitMask: 'url(/images/multiplayer_mask.png) luminance center 100% / 100% no-repeat',
}}
/>
</div>
Changes to a document are synchronized in realtime to other users, including presence information like live cursors. This requires you to run a backend server, although we provide a demo server you can use for testing.
## Try the demo server
The easiest way to start experimenting with multiplayer is with our demo server. Start by installing
`@tldraw/sync`:
```bash
npm install @tldraw/sync
```
Then, in your app, call the `useMutiplayerDemo` hook with a room ID. It'll return a
[`store`](/docs/persistence#The-store-prop) that you can pass into the tldraw component:
```tsx
function MyApp() {
const store = useSyncDemo({ roomId: 'myApp-abc123' })
return <Tldraw store={store} />
}
```
Make sure the room ID is unique. Anyone who uses the same ID can access your room. Our sync demo
server is a great way to prototype multiplayer in tldraw, but it shouldn't be used in production.
Rooms will be deleted after 24 hours.
## Self-host the backend
To use tldraw sync in production, you need to host the backend on your own server. There are three parts to sync in
tldraw:
1. The sync server, which serves the room over [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API).
2. Somewhere to large assets that are too big to send over websockets, like images and videos.
3. The sync client, which connects to the server with the [`useSync`](?) hook.
We recommend starting from our [Cloudflare Workers
template](https://github.com/tldraw/cloudflare-workers-template). This is a minimal setup of the
system that powers multiplayer collaboration for hundreds of thousands of rooms & users on
tldraw.com. It includes everything you need to get up and running with multiplayer tldraw quickly:
the sync server, asset hosting, and URL unfurling.
### TLSocketRoom
The core part of our sync server is [`TLSocketRoom`](?). It works with any JavaScript-based web
framework with support for WebSockets, and any storage backend: it's up to you to wire in those
parts yourself.
Make sure there's only ever one `TLSocketRoom` for each room in your app. If there's
more than one, users won't see each other and will overwrite others' changes. We use [Durable
Objects](https://developers.cloudflare.com/durable-objects/) to achieve this on tldraw.com.
If you don't want to our Cloudflare template, we also provide a [simple Node/Bun server
example](https://github.com/tldraw/tldraw/tree/main/apps/simple-server-example). This is not
production ready, but you can use it as a reference implementation to build it into your own
existing backend.
Read the reference docs for [`TLSocketRoom`](?) for more.
### Asset storage
As well as synchronizing the rapidly-changing document data, tldraw also needs a way to store and
retrieve large binary assets like images or videos.
You'll need to make sure your backend can handle asset uploads & downloads, then implement
[`TLAssetStore`](?) to connect it to tldraw.
- Read about [how assets work in tldraw](/docs/assets).
- Read the [`TLAssetStore`](?) reference docs.
### The sync client
The [`useSync`](?) hook connects your backend to the tldraw editor. Call the hook to create a store,
then pass it in to the `<Tldraw />` component:
```tsx
function MyApp() {
const store = useSync({
uri: 'https://myapp.com/tlsocketroom/my-room-id',
assets: myAssetStore,
})
return <Tldraw store={store} />
}
const myAssetStore: TLAssetStore {
upload(file, asset) {
return uploadFileAndReturnUrl(file)
},
resolve(asset) {
return asset.props.src
},
}
```
The sync client automatically enables some default collaborative UX which you can customize using
editor [component overrides](/reference/tldraw/TLComponents):
- Collaborator cursors: [`TLComponents#CollaboratorCursor`](?)
- Cursor chat: [`TLComponents#CursorChatBubble`](?)
- Offline indicator: [`TLComponents#TopPanel`](?)
- Collaborator list: [`TLComponents#SharePanel`](?)
## Integrating other sync systems
While tldraw multiplayer is the easiest way to add sync capabilities to your tldraw canvas, you can
implement your own or use another off-the-shelf system.
One example is this [Yjs integration](https://github.com/tldraw/tldraw-yjs-example). Refer to the
[Persistence docs](/docs/persistence) for how to hook it together with your sync layer of choice.

Wyświetl plik

@ -3,7 +3,7 @@ title: Tools
status: published
author: steveruizok
date: 3/22/2023
order: 5
order: 3
keywords:
- custom
- tools

Wyświetl plik

@ -3,7 +3,7 @@ title: User interface
status: published
author: steveruizok
date: 3/22/2023
order: 7
order: 3
keywords:
- ui
- interface

Wyświetl plik

@ -71,26 +71,6 @@ If you're using the [Tldraw](?) component in a full-screen app, you probably als
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, { ssr: false })
```
### Using a bundler
If you're using a bundler like webpack or rollup, you can import the assets directly from the `assets` package. Here you can use `getAssetUrlsByMetaUrl` helper function:
```tsx
import { getAssetUrlsByMetaUrl } from '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.
@ -99,6 +79,26 @@ Visit our [framework examples repository](https://github.com/tldraw/examples) to
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 bundler
If you're using a bundler like webpack or rollup, you can import the assets directly from the `@tldraw/assets` package. Your bundler should be able to copy across the assets with your bundle, and insert the correct URLs in their place.
There are three options:
- `import {getAssetUrlsByImport} from '@tldraw/assets/imports`: import asset files using import statements. You'll need to configure your bundler to treat imports of .svg, .png, .json, and .woff2 files as external assets.
- `import {getAssetUrlsByImport} from '@tldraw/assets/imports.vite`: import asset files, appending `?url` to the asset path. This works correctly with [vite](https://vitejs.dev/guide/assets#explicit-url-imports) without any extra configuration needed.
- `import {getAssetUrlsByMeta} from '@tldraw/assets/urls`: get asset urls using `new URL(path, import.meta.url)`. This is a standards-based approach that works natively in most modern browsers & bundlers.
Call the function, and pass the resulting `assetUrls` into the `Tldraw` component:
```tsx
import { getAssetUrlsByMetaUrl } from '@tldraw/assets/urls'
const assetUrls = getAssetUrlsByMetaUrl()
<Tldraw assetUrls={assetUrls} />
```
### Using a public CDN
By default we serve these assets from a public CDN. Everything should work out of the box and is a good way to get started.

Wyświetl plik

@ -12,7 +12,7 @@ You can use the tldraw SDK to craft infinite canvas experiences for the web. It'
By the end of this guide you will have made something that looks like this:
<Embed className="article__embed--quickstart" src="https://examples.tldraw.com/develop" />
<Embed className="article__embed--quickstart" src="https://examples.tldraw.com/sync-demo" />
### 1. Installation
@ -23,7 +23,7 @@ By the end of this guide you will have made something that looks like this:
npm install tldraw
```
### 2. Import Styles
### 2. Import styles
To import fonts and CSS for tldraw:
@ -39,7 +39,7 @@ body {
}
```
### 3. Render Tldraw Component
### 3. Render tldraw component
To render the Tldraw component
@ -62,14 +62,47 @@ export default function App() {
}
```
### 4. Sync betweens users
At this point, you have a complete working single-user whiteboard. To add support for multiple users
collaborating in realtime, you can use the **tldraw sync** extension library.
```bash
npm install @tldraw/sync
```
Set up your component with our demo server:
- Import the `useSyncDemo` hook from the `@tldraw/sync` package
- Add the hook to your component with a unique `roomId`
- Pass the `store` returned from the hook into your `<Tldraw />` component
<FocusLines lines={[2, 6, 10]}>
```javascript
import { Tldraw } from 'tldraw'
import { useSyncDemo } from '@tldraw/sync'
import './index.css'
export default function App() {
const store = useSyncDemo({ roomId: 'myapp-abc123' })
return (
<div style={{ position: 'fixed', inset: 0 }}>
<Tldraw store={store} />
</div>
)
}
```
</FocusLines>
## Next Steps
Now that you have your canvas working, you may be wondering: what next?
Now that you have your canvas working, you can:
You can try:
- Giving the editor a makeover by [customizing the UI](/docs/user-interface)
- Adding your own [shapes](/docs/shapes) and [tools](/docs/tools)
- Providing collaboration using [multiplayer](https://github.com/tldraw/tldraw-yjs-example)
- [Customize the UI](/docs/user-interface) of the editor
- Add your own [shapes](/docs/shapes) and [tools](/docs/tools)
- Replace our sync demo server with a [more robust sync backend](/docs/sync)
We provide the above examples and more in our [examples section](/examples). Go build something creative and please do share it with us in our [#show-and-tell](https://discord.com/invite/SBBEVCA4PG) channel on Discord!

Wyświetl plik

@ -112,6 +112,86 @@
],
"hero": null
},
{
"id": "sync-core",
"title": "@tldraw/sync-core",
"description": "",
"groups": [
{
"id": "Class",
"path": null
},
{
"id": "Function",
"path": null
},
{
"id": "Variable",
"path": null
},
{
"id": "Enum",
"path": null
},
{
"id": "Interface",
"path": null
},
{
"id": "TypeAlias",
"path": null
},
{
"id": "Namespace",
"path": null
},
{
"id": "Component",
"path": null
}
],
"hero": null
},
{
"id": "sync",
"title": "@tldraw/sync",
"description": "",
"groups": [
{
"id": "Class",
"path": null
},
{
"id": "Function",
"path": null
},
{
"id": "Variable",
"path": null
},
{
"id": "Enum",
"path": null
},
{
"id": "Interface",
"path": null
},
{
"id": "TypeAlias",
"path": null
},
{
"id": "Namespace",
"path": null
},
{
"id": "Component",
"path": null
}
],
"hero": null
},
{
"id": "tldraw",
"title": "tldraw",

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 603 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 9.4 KiB

Wyświetl plik

@ -545,13 +545,13 @@ async function excerptToMarkdown(
}
return [
`<CodeLinkProvider links={${JSON.stringify(links)}}>`,
`<CodeLinks links={${JSON.stringify(links)}}>`,
'',
'```ts',
code,
'```',
'',
'</CodeLinkProvider>',
'</CodeLinks>',
].join('\n')
}
@ -608,11 +608,11 @@ function addExtends(result: Result, item: ApiItem, heritage: HeritageType[]) {
}
result.markdown += [
`<CodeLinkProvider links={${JSON.stringify(links)}}>`,
`<CodeLinks links={${JSON.stringify(links)}}>`,
'',
`Extends \`${heritage.map((type) => type.excerpt.text).join(', ')}\`.`,
'',
'</CodeLinkProvider>',
'</CodeLinks>',
'',
].join('\n')
}

Wyświetl plik

@ -4,4 +4,6 @@ export const TLDRAW_PACKAGES_TO_INCLUDE_IN_DOCS = [
'tldraw',
'tlschema',
'validate',
'sync',
'sync-core',
]

Wyświetl plik

@ -548,7 +548,8 @@ body {
margin: 20px 0px;
}
.article > blockquote {
.article > blockquote,
.article__callout {
max-width: 100%;
margin: 32px 0px;
padding: 16px;
@ -556,6 +557,21 @@ body {
background-color: var(--color-blockquote);
}
.article__callout {
display: flex;
align-items: start;
gap: 16px;
}
.article__callout__icon {
/* use the system font so we get proper emoji: */
font-family: sans-serif;
font-size: 20px;
flex: 0 0 auto;
}
.article__callout__message {
flex: 1 1 auto;
}
.article pre {
max-width: 100%;
margin: 32px 0px;
@ -710,6 +726,7 @@ body {
.article__embed--quickstart {
aspect-ratio: 16 / 9;
min-height: 405px;
max-width: 100%;
margin: 32px 0px;
}

Wyświetl plik

@ -96,3 +96,8 @@ a.code-link::before {
vertical-align: -0.142em; /* 2px when font-size is 14px */
margin-right: 0.142em;
}
.hljs [data-line-state='blur'] {
color: var(--hl);
opacity: 0.5;
}

Wyświetl plik

@ -13,7 +13,7 @@ export async function autoLinkDocs(db: Database<sqlite3.Database, sqlite3.Statem
await Promise.all(articles.map((a) => autoLinkDocsForArticle(db, a)))
}
const regex = /\[([^\[\]]*?)\]\(\?\)/g
const regex = /\[`?([^\[\]]*?)`?\]\(\?\)/g
export async function autoLinkDocsForArticle(
db: Database<sqlite3.Database, sqlite3.Statement>,
@ -42,7 +42,7 @@ export async function autoLinkDocsForArticle(
if (heading) {
const headingRow = await db.get('SELECT slug FROM headings WHERE slug = ?', heading)
if (!headingRow) throw Error(`Could not find heading for ${_title} (${heading})`)
if (!headingRow) throw Error(`Could not find heading for ${_title} (${heading}) in ${id}`)
str = `[\`${title}.${heading}\`](/${article.sectionId}/${article.categoryId}/${article.id}#${headingRow.slug})`
} else {
str = `[\`${title}\`](/${article.sectionId}/${article.categoryId}/${article.id})`

Wyświetl plik

@ -34,7 +34,7 @@ const getExamplesForCategory = (category: Category) =>
const categories: Record<Category, string> = {
basic: 'Getting started',
collaboration: 'Collaboration',
collaboration: 'Sync',
ui: 'UI & theming',
'shapes/tools': 'Shapes & tools',
'data/assets': 'Data & assets',

Wyświetl plik

@ -1,5 +1,5 @@
---
title: Sync with custom shape
title: …with a custom shape
component: ./SyncDemoShapeExample.tsx
category: collaboration
priority: 3
@ -9,4 +9,4 @@ multiplayer: true
---
The `useSyncDemo` hook can be used to quickly prototype multiplayer experiences in tldraw using a demo backend that we host. Data is wiped after one day.
This example shows a custom shape in combination with tldraw sync.

Wyświetl plik

@ -1,5 +1,5 @@
---
title: Custom user data
title: …with custom user data
component: ./SyncCustomUser.tsx
category: collaboration
priority: 3
@ -9,4 +9,4 @@ multiplayer: true
---
This example shows how to integrate your own user data into tldraw and tldraw's multiplayer system.
This example shows how to integrate your own user data into tldraw sync.

Wyświetl plik

@ -1,5 +1,5 @@
---
title: Sync
title: Multiplayer sync
component: ./SyncDemoExample.tsx
category: collaboration
priority: 1

Wyświetl plik

@ -1,7 +0,0 @@
---
title: Collaboration with YJS
component: ./YjsExample.tsx
category: collaboration
priority: 2
keywords: [multi, player]
---

Wyświetl plik

@ -1,11 +0,0 @@
export default function YjsExample() {
return (
<div className="tldraw__editor" style={{ padding: 32 }}>
Weve moved! See{' '}
<a href="https://github.com/tldraw/tldraw-yjs-example">
https://github.com/tldraw/tldraw-yjs-example
</a>
.
</div>
)
}

Wyświetl plik

@ -1,5 +1,7 @@
# Node/Bun server example
# tldraw sync, simple Node/Bun server example
This is a simple example of how to integrate tldraw's sync engine with a Node server or a Bun server.
This is a simple example of a backend for [tldraw sync](https://tldraw.dev/docs/sync) with a Node or Bun server.
Run `yarn dev-node` or `yarn dev-bun` in this folder to start the server + client.
For a production-ready example specific to Cloudflare, see /templates/cloudflare-workers.

Wyświetl plik

@ -435,7 +435,6 @@ export const DefaultQuickActions: NamedExoticComponent<TLUiQuickActionsProps>;
export function DefaultQuickActionsContent(): JSX_2.Element | undefined;
// @public (undocumented)
export const defaultShapeTools: readonly [typeof TextShapeTool, typeof DrawShapeTool, typeof GeoShapeTool, typeof NoteShapeTool, typeof LineShapeTool, typeof FrameShapeTool, typeof ArrowShapeTool, typeof HighlightShapeTool];
// @public (undocumented)
@ -1547,7 +1546,7 @@ export interface TLArrowPoint {
point: VecLike;
}
// @public (undocumented)
// @public
export interface TLComponents extends TLEditorComponents, TLUiComponents {
}

Wyświetl plik

@ -37,7 +37,26 @@ import { usePreloadAssets } from './ui/hooks/usePreloadAssets'
import { useTranslation } from './ui/hooks/useTranslation/useTranslation'
import { useDefaultEditorAssetsWithOverrides } from './utils/static-assets/assetUrls'
/** @public */
/**
* Override the default react components used by the editor and UI. Set components to null to
* disable them entirely.
*
* @example
* ```tsx
* import {Tldraw, TLComponents} from 'tldraw'
*
* const components: TLComponents = {
* Scribble: MyCustomScribble,
* }
*
* export function MyApp() {
* return <Tldraw components={components} />
* }
* ```
*
*
* @public
*/
export interface TLComponents extends TLEditorComponents, TLUiComponents {}
/** @public */

Wyświetl plik

@ -1,15 +1,17 @@
# tldraw on cloudflare durable objects
# tldraw sync server
This repo contains a complete example of tldraw sync ready to deploy on cloudflare.
This is a production-ready backend for [tldraw sync](https://tldraw.dev/docs/sync).
- The client itself can be deployed wherever you like.
- The server is on [Cloudflare Workers](https://developers.cloudflare.com/workers/).
- Your client-side tldraw-based app can be served from anywhere you want.
- This backend uses [Cloudflare Workers](https://developers.cloudflare.com/workers/), and will need
to be deployed to your own Cloudflare account.
- Each whiteboard is synced via
[WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) to a [Cloudflare
Durable Object](https://developers.cloudflare.com/durable-objects/).
- Whiteboards and any uploaded images/videos are stored in a [Cloudflare
R2](https://developers.cloudflare.com/r2/) bucket.
- URL metadata unfurling for bookmarks shapes is also supported.
- Although unreliated to tldraw sync, this server also includes a component to fetch link previews
for URLs added to the canvas.
This is a minimal setup of the same system that powers multiplayer collaboration for hundreds of
thousands of rooms & users on www.tldraw.com. Because durable objects effectively create a mini
@ -72,3 +74,10 @@ Finally, deploy your client HTML & JavaScript. Create a production build with
When you visit your published client, it should connect to your cloudflare workers domain and sync
your document across devices.
## License
Whilst the code in this template is available under the MIT license, `tldraw` and `@tldraw/sync` are
under the [tldraw license](https://github.com/tldraw/tldraw/blob/main/LICENSE.md) which does not
allow their use for commercial purposes. To purchase an alternative license for commercial use,
contact [sales@tldraw.com](mailto:sales@tldraw.com).

Wyświetl plik

@ -6,3 +6,10 @@
</div>
This repo contains a very basic example of how to use [tldraw](https://github.com/tldraw/tldraw) in a [Next.js](https://nextjs.org/) app using the src directory and App router.
## License
Whilst the code in this template is available under the MIT license, `tldraw` and `@tldraw/sync` are
under the [tldraw license](https://github.com/tldraw/tldraw/blob/main/LICENSE.md) which does not
allow their use for commercial purposes. To purchase an alternative license for commercial use,
contact [sales@tldraw.com](mailto:sales@tldraw.com).

Wyświetl plik

@ -6,3 +6,10 @@
</div>
This repo contains a template you can copy for using [tldraw](https://github.com/tldraw/tldraw) in a vite application.
## License
Whilst the code in this template is available under the MIT license, `tldraw` and `@tldraw/sync` are
under the [tldraw license](https://github.com/tldraw/tldraw/blob/main/LICENSE.md) which does not
allow their use for commercial purposes. To purchase an alternative license for commercial use,
contact [sales@tldraw.com](mailto:sales@tldraw.com).