Compare commits

..

1 Commits

Author SHA1 Message Date
Tobias Lins
2f30c8ed72 Update expo-sqlite-adapter.ts 2025-04-26 17:38:38 +02:00
245 changed files with 5025 additions and 10346 deletions

View File

@@ -1,38 +1,5 @@
# chat-rn-expo-clerk
## 1.0.110
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-expo@0.13.18
- jazz-react-native-media-images@0.13.18
## 1.0.109
### Patch Changes
- jazz-expo@0.13.17
- jazz-tools@0.13.17
- jazz-react-native-media-images@0.13.17
## 1.0.108
### Patch Changes
- jazz-expo@0.13.16
- jazz-tools@0.13.16
- jazz-react-native-media-images@0.13.16
## 1.0.107
### Patch Changes
- jazz-expo@0.13.15
- jazz-tools@0.13.15
- jazz-react-native-media-images@0.13.15
## 1.0.106
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "chat-rn-expo-clerk",
"main": "index.js",
"version": "1.0.110",
"version": "1.0.106",
"scripts": {
"build": "expo export -p ios",
"start": "expo start",

View File

@@ -1,34 +1,5 @@
# chat-rn-expo
## 1.0.97
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-expo@0.13.18
## 1.0.96
### Patch Changes
- jazz-expo@0.13.17
- jazz-tools@0.13.17
## 1.0.95
### Patch Changes
- jazz-expo@0.13.16
- jazz-tools@0.13.16
## 1.0.94
### Patch Changes
- jazz-expo@0.13.15
- jazz-tools@0.13.15
## 1.0.93
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "chat-rn-expo",
"version": "1.0.97",
"version": "1.0.93",
"main": "index.js",
"scripts": {
"build": "expo export -p ios",

View File

@@ -9,8 +9,6 @@ appId: com.jazz.chatrn
# - tapOn: "Reload"
# login
- assertVisible: "Logout"
- tapOn: "Logout"
- assertVisible: "Anonymous user"
- runFlow:
label: "Erase existing username"

View File

@@ -1,50 +1,5 @@
# chat-rn
## 1.0.105
### Patch Changes
- Updated dependencies [9089252]
- Updated dependencies [b470f63]
- Updated dependencies [761759c]
- Updated dependencies [66373ba]
- Updated dependencies [f24cad1]
- cojson@0.13.18
- jazz-tools@0.13.18
- cojson-transport-ws@0.13.18
- jazz-react-native@0.13.18
## 1.0.104
### Patch Changes
- Updated dependencies [9fb98e2]
- Updated dependencies [0b89fad]
- cojson@0.13.17
- cojson-transport-ws@0.13.17
- jazz-react-native@0.13.17
- jazz-tools@0.13.17
## 1.0.103
### Patch Changes
- Updated dependencies [c6fb8dc]
- cojson@0.13.16
- cojson-transport-ws@0.13.16
- jazz-react-native@0.13.16
- jazz-tools@0.13.16
## 1.0.102
### Patch Changes
- Updated dependencies [c712ef2]
- cojson@0.13.15
- cojson-transport-ws@0.13.15
- jazz-react-native@0.13.15
- jazz-tools@0.13.15
## 1.0.101
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "chat-rn",
"version": "1.0.105",
"version": "1.0.101",
"main": "index.js",
"scripts": {
"android": "react-native run-android",

View File

@@ -1,38 +1,5 @@
# chat-vue
## 0.0.89
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-browser@0.13.18
- jazz-vue@0.13.18
## 0.0.88
### Patch Changes
- jazz-browser@0.13.17
- jazz-tools@0.13.17
- jazz-vue@0.13.17
## 0.0.87
### Patch Changes
- jazz-browser@0.13.16
- jazz-tools@0.13.16
- jazz-vue@0.13.16
## 0.0.86
### Patch Changes
- jazz-browser@0.13.15
- jazz-tools@0.13.15
- jazz-vue@0.13.15
## 0.0.85
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "chat-vue",
"version": "0.0.89",
"version": "0.0.85",
"private": true,
"type": "module",
"scripts": {

View File

@@ -1,38 +1,5 @@
# jazz-example-chat
## 0.0.187
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-inspector@0.13.18
- jazz-react@0.13.18
## 0.0.186
### Patch Changes
- jazz-inspector@0.13.17
- jazz-react@0.13.17
- jazz-tools@0.13.17
## 0.0.185
### Patch Changes
- jazz-inspector@0.13.16
- jazz-react@0.13.16
- jazz-tools@0.13.16
## 0.0.184
### Patch Changes
- jazz-inspector@0.13.15
- jazz-react@0.13.15
- jazz-tools@0.13.15
## 0.0.183
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-chat",
"private": true,
"version": "0.0.187",
"version": "0.0.183",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,38 +1,5 @@
# minimal-auth-clerk
## 0.0.86
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-react@0.13.18
- jazz-react-auth-clerk@0.13.18
## 0.0.85
### Patch Changes
- jazz-react@0.13.17
- jazz-react-auth-clerk@0.13.17
- jazz-tools@0.13.17
## 0.0.84
### Patch Changes
- jazz-react@0.13.16
- jazz-react-auth-clerk@0.13.16
- jazz-tools@0.13.16
## 0.0.83
### Patch Changes
- jazz-react@0.13.15
- jazz-react-auth-clerk@0.13.15
- jazz-tools@0.13.15
## 0.0.82
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "clerk",
"private": true,
"version": "0.0.86",
"version": "0.0.82",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,34 +1,5 @@
# file-share-svelte
## 0.0.69
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-svelte@0.13.18
## 0.0.68
### Patch Changes
- jazz-svelte@0.13.17
- jazz-tools@0.13.17
## 0.0.67
### Patch Changes
- jazz-svelte@0.13.16
- jazz-tools@0.13.16
## 0.0.66
### Patch Changes
- jazz-svelte@0.13.15
- jazz-tools@0.13.15
## 0.0.65
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "file-share-svelte",
"version": "0.0.69",
"version": "0.0.65",
"private": true,
"type": "module",
"scripts": {

View File

@@ -30,7 +30,7 @@
const fileId = file._refs.file.id;
// Load the file as a blob, can take a while
const blob = await FileStream.loadAsBlob(fileId);
const blob = await FileStream.loadAsBlob(fileId, me, {});
if (!blob) {
toast.error('Failed to download file');
return;

View File

@@ -1,26 +1,48 @@
<script lang="ts">
import { useAccount, useCoState } from 'jazz-svelte';
import { SharedFile, ListOfSharedFiles } from '$lib/schema';
import { createInviteLink } from 'jazz-svelte';
import { FileStream } from 'jazz-tools';
import FileItem from '$lib/components/FileItem.svelte';
import { SvelteMap } from 'svelte/reactivity';
import { generateTempFileId } from '$lib/utils';
import { CloudUpload } from 'lucide-svelte';
const { me, logOut } = useAccount();
const mySharedFilesId = me?.root?._refs.sharedFiles.id;
const sharedFiles = $derived(useCoState(ListOfSharedFiles, mySharedFilesId));
const sharedFiles = $derived(useCoState(ListOfSharedFiles, mySharedFilesId, [{}]));
let fileInput: HTMLInputElement;
type PendingSharedFile = {
name: string;
id: string;
createdAt: Date;
};
// Track files that are currently uploading
const uploadingFiles = new SvelteMap<string, PendingSharedFile>();
async function handleFileUpload(event: Event) {
const input = event.target as HTMLInputElement;
const files = input.files;
if (!files || !files.length || !me?.root?.sharedFiles || !me?.root?.publicGroup) return;
if (!files || !files.length || !me.root?.sharedFiles || !me.root.publicGroup) return;
const file = files[0];
const fileName = file.name;
const createdAt = new Date();
const fileId = generateTempFileId(fileName, createdAt);
const tempFile: PendingSharedFile = {
name: fileName,
id: fileId,
createdAt
};
// Add to uploading files
uploadingFiles.set(fileId, tempFile);
try {
const ownership = { owner: me.root.publicGroup };
@@ -43,10 +65,17 @@
// Add the file to the user's files list
me.root.sharedFiles.push(sharedFile);
} finally {
uploadingFiles.delete(fileId);
fileInput.value = ''; // reset input
}
}
async function shareFile(file: SharedFile) {
const inviteLink = createInviteLink(file, 'reader');
await navigator.clipboard.writeText(inviteLink);
alert('Share link copied to clipboard!');
}
async function deleteFile(file: SharedFile) {
if (!me?.root?.sharedFiles || !sharedFiles.current) return;
@@ -98,11 +127,13 @@
<!-- Files List -->
<div class="space-y-4">
{#if sharedFiles.current}
{#if !(sharedFiles.current.length === 0)}
{#each sharedFiles.current as file}
{#if !(sharedFiles.current.length === 0 && uploadingFiles.size === 0)}
{#each [...sharedFiles.current, ...uploadingFiles.values()] as file (generateTempFileId(file?.name, file?.createdAt))}
{#if file}
<FileItem
{file}
loading={uploadingFiles.has(generateTempFileId(file?.name, file?.createdAt))}
onShare={shareFile}
onDelete={deleteFile}
/>
{/if}

View File

@@ -1,38 +1,5 @@
# jazz-tailwind-demo-auth-starter
## 0.0.26
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-inspector@0.13.18
- jazz-react@0.13.18
## 0.0.25
### Patch Changes
- jazz-inspector@0.13.17
- jazz-react@0.13.17
- jazz-tools@0.13.17
## 0.0.24
### Patch Changes
- jazz-inspector@0.13.16
- jazz-react@0.13.16
- jazz-tools@0.13.16
## 0.0.23
### Patch Changes
- jazz-inspector@0.13.15
- jazz-react@0.13.15
- jazz-tools@0.13.15
## 0.0.22
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "filestream",
"private": true,
"version": "0.0.26",
"version": "0.0.22",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,34 +1,5 @@
# form
## 0.1.27
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-react@0.13.18
## 0.1.26
### Patch Changes
- jazz-react@0.13.17
- jazz-tools@0.13.17
## 0.1.25
### Patch Changes
- jazz-react@0.13.16
- jazz-tools@0.13.16
## 0.1.24
### Patch Changes
- jazz-react@0.13.15
- jazz-tools@0.13.15
## 0.1.23
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "form",
"private": true,
"version": "0.1.27",
"version": "0.1.23",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,34 +1,5 @@
# image-upload
## 0.0.83
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-react@0.13.18
## 0.0.82
### Patch Changes
- jazz-react@0.13.17
- jazz-tools@0.13.17
## 0.0.81
### Patch Changes
- jazz-react@0.13.16
- jazz-tools@0.13.16
## 0.0.80
### Patch Changes
- jazz-react@0.13.15
- jazz-tools@0.13.15
## 0.0.79
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "image-upload",
"private": true,
"version": "0.0.83",
"version": "0.0.79",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,45 +1,5 @@
# jazz-example-inspector
## 0.0.137
### Patch Changes
- Updated dependencies [9089252]
- Updated dependencies [b470f63]
- Updated dependencies [66373ba]
- Updated dependencies [f24cad1]
- cojson@0.13.18
- cojson-transport-ws@0.13.18
- jazz-inspector@0.13.18
## 0.0.136
### Patch Changes
- Updated dependencies [9fb98e2]
- Updated dependencies [0b89fad]
- cojson@0.13.17
- cojson-transport-ws@0.13.17
- jazz-inspector@0.13.17
## 0.0.135
### Patch Changes
- Updated dependencies [c6fb8dc]
- cojson@0.13.16
- cojson-transport-ws@0.13.16
- jazz-inspector@0.13.16
## 0.0.134
### Patch Changes
- Updated dependencies [c712ef2]
- cojson@0.13.15
- cojson-transport-ws@0.13.15
- jazz-inspector@0.13.15
## 0.0.133
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-inspector-app",
"private": true,
"version": "0.0.137",
"version": "0.0.133",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -8,9 +8,7 @@
"dev:worker": "tsx --watch --env-file=.env ./src/worker.ts",
"build": "vite build && tsc",
"serve": "vite preview",
"generate-env": "tsx generate-env.ts",
"format-and-lint": "biome check .",
"format-and-lint:fix": "biome check . --write"
"generate-env": "tsx generate-env.ts"
},
"dependencies": {
"@radix-ui/react-label": "^2.1.2",

View File

@@ -1 +0,0 @@
export const apiKey = "jazz-paper-scissors@garden.co";

View File

@@ -3,7 +3,6 @@ import { StrictMode } from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import { apiKey } from "@/apiKey.ts";
import { JazzProvider } from "jazz-react";
import { App } from "./app";
@@ -14,7 +13,7 @@ if (rootElement && !rootElement.innerHTML) {
<StrictMode>
<JazzProvider
sync={{
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
peer: "wss://cloud.jazz.tools/?key=jazz-paper-scissors@garden.co",
}}
>
<JazzInspector />

View File

@@ -10,139 +10,139 @@
// Import Routes
import { Route as rootRoute } from './routes/__root'
import { Route as AuthenticatedImport } from './routes/_authenticated'
import { Route as IndexImport } from './routes/index'
import { Route as AuthenticatedWaitingRoomWaitingRoomIdImport } from './routes/_authenticated/waiting-room.$waitingRoomId'
import { Route as AuthenticatedGameGameIdImport } from './routes/_authenticated/game.$gameId'
import { Route as rootRoute } from "./routes/__root";
import { Route as AuthenticatedImport } from "./routes/_authenticated";
import { Route as IndexImport } from "./routes/index";
import { Route as AuthenticatedWaitingRoomWaitingRoomIdImport } from "./routes/_authenticated/waiting-room.$waitingRoomId";
import { Route as AuthenticatedGameGameIdImport } from "./routes/_authenticated/game.$gameId";
// Create/Update Routes
const AuthenticatedRoute = AuthenticatedImport.update({
id: '/_authenticated',
id: "/_authenticated",
getParentRoute: () => rootRoute,
} as any)
} as any);
const IndexRoute = IndexImport.update({
id: '/',
path: '/',
id: "/",
path: "/",
getParentRoute: () => rootRoute,
} as any)
} as any);
const AuthenticatedWaitingRoomWaitingRoomIdRoute =
AuthenticatedWaitingRoomWaitingRoomIdImport.update({
id: '/waiting-room/$waitingRoomId',
path: '/waiting-room/$waitingRoomId',
id: "/waiting-room/$waitingRoomId",
path: "/waiting-room/$waitingRoomId",
getParentRoute: () => AuthenticatedRoute,
} as any)
} as any);
const AuthenticatedGameGameIdRoute = AuthenticatedGameGameIdImport.update({
id: '/game/$gameId',
path: '/game/$gameId',
id: "/game/$gameId",
path: "/game/$gameId",
getParentRoute: () => AuthenticatedRoute,
} as any)
} as any);
// Populate the FileRoutesByPath interface
declare module '@tanstack/react-router' {
declare module "@tanstack/react-router" {
interface FileRoutesByPath {
'/': {
id: '/'
path: '/'
fullPath: '/'
preLoaderRoute: typeof IndexImport
parentRoute: typeof rootRoute
}
'/_authenticated': {
id: '/_authenticated'
path: ''
fullPath: ''
preLoaderRoute: typeof AuthenticatedImport
parentRoute: typeof rootRoute
}
'/_authenticated/game/$gameId': {
id: '/_authenticated/game/$gameId'
path: '/game/$gameId'
fullPath: '/game/$gameId'
preLoaderRoute: typeof AuthenticatedGameGameIdImport
parentRoute: typeof AuthenticatedImport
}
'/_authenticated/waiting-room/$waitingRoomId': {
id: '/_authenticated/waiting-room/$waitingRoomId'
path: '/waiting-room/$waitingRoomId'
fullPath: '/waiting-room/$waitingRoomId'
preLoaderRoute: typeof AuthenticatedWaitingRoomWaitingRoomIdImport
parentRoute: typeof AuthenticatedImport
}
"/": {
id: "/";
path: "/";
fullPath: "/";
preLoaderRoute: typeof IndexImport;
parentRoute: typeof rootRoute;
};
"/_authenticated": {
id: "/_authenticated";
path: "";
fullPath: "";
preLoaderRoute: typeof AuthenticatedImport;
parentRoute: typeof rootRoute;
};
"/_authenticated/game/$gameId": {
id: "/_authenticated/game/$gameId";
path: "/game/$gameId";
fullPath: "/game/$gameId";
preLoaderRoute: typeof AuthenticatedGameGameIdImport;
parentRoute: typeof AuthenticatedImport;
};
"/_authenticated/waiting-room/$waitingRoomId": {
id: "/_authenticated/waiting-room/$waitingRoomId";
path: "/waiting-room/$waitingRoomId";
fullPath: "/waiting-room/$waitingRoomId";
preLoaderRoute: typeof AuthenticatedWaitingRoomWaitingRoomIdImport;
parentRoute: typeof AuthenticatedImport;
};
}
}
// Create and export the route tree
interface AuthenticatedRouteChildren {
AuthenticatedGameGameIdRoute: typeof AuthenticatedGameGameIdRoute
AuthenticatedWaitingRoomWaitingRoomIdRoute: typeof AuthenticatedWaitingRoomWaitingRoomIdRoute
AuthenticatedGameGameIdRoute: typeof AuthenticatedGameGameIdRoute;
AuthenticatedWaitingRoomWaitingRoomIdRoute: typeof AuthenticatedWaitingRoomWaitingRoomIdRoute;
}
const AuthenticatedRouteChildren: AuthenticatedRouteChildren = {
AuthenticatedGameGameIdRoute: AuthenticatedGameGameIdRoute,
AuthenticatedWaitingRoomWaitingRoomIdRoute:
AuthenticatedWaitingRoomWaitingRoomIdRoute,
}
};
const AuthenticatedRouteWithChildren = AuthenticatedRoute._addFileChildren(
AuthenticatedRouteChildren,
)
);
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'': typeof AuthenticatedRouteWithChildren
'/game/$gameId': typeof AuthenticatedGameGameIdRoute
'/waiting-room/$waitingRoomId': typeof AuthenticatedWaitingRoomWaitingRoomIdRoute
"/": typeof IndexRoute;
"": typeof AuthenticatedRouteWithChildren;
"/game/$gameId": typeof AuthenticatedGameGameIdRoute;
"/waiting-room/$waitingRoomId": typeof AuthenticatedWaitingRoomWaitingRoomIdRoute;
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'': typeof AuthenticatedRouteWithChildren
'/game/$gameId': typeof AuthenticatedGameGameIdRoute
'/waiting-room/$waitingRoomId': typeof AuthenticatedWaitingRoomWaitingRoomIdRoute
"/": typeof IndexRoute;
"": typeof AuthenticatedRouteWithChildren;
"/game/$gameId": typeof AuthenticatedGameGameIdRoute;
"/waiting-room/$waitingRoomId": typeof AuthenticatedWaitingRoomWaitingRoomIdRoute;
}
export interface FileRoutesById {
__root__: typeof rootRoute
'/': typeof IndexRoute
'/_authenticated': typeof AuthenticatedRouteWithChildren
'/_authenticated/game/$gameId': typeof AuthenticatedGameGameIdRoute
'/_authenticated/waiting-room/$waitingRoomId': typeof AuthenticatedWaitingRoomWaitingRoomIdRoute
__root__: typeof rootRoute;
"/": typeof IndexRoute;
"/_authenticated": typeof AuthenticatedRouteWithChildren;
"/_authenticated/game/$gameId": typeof AuthenticatedGameGameIdRoute;
"/_authenticated/waiting-room/$waitingRoomId": typeof AuthenticatedWaitingRoomWaitingRoomIdRoute;
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' | '' | '/game/$gameId' | '/waiting-room/$waitingRoomId'
fileRoutesByTo: FileRoutesByTo
to: '/' | '' | '/game/$gameId' | '/waiting-room/$waitingRoomId'
fileRoutesByFullPath: FileRoutesByFullPath;
fullPaths: "/" | "" | "/game/$gameId" | "/waiting-room/$waitingRoomId";
fileRoutesByTo: FileRoutesByTo;
to: "/" | "" | "/game/$gameId" | "/waiting-room/$waitingRoomId";
id:
| '__root__'
| '/'
| '/_authenticated'
| '/_authenticated/game/$gameId'
| '/_authenticated/waiting-room/$waitingRoomId'
fileRoutesById: FileRoutesById
| "__root__"
| "/"
| "/_authenticated"
| "/_authenticated/game/$gameId"
| "/_authenticated/waiting-room/$waitingRoomId";
fileRoutesById: FileRoutesById;
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
AuthenticatedRoute: typeof AuthenticatedRouteWithChildren
IndexRoute: typeof IndexRoute;
AuthenticatedRoute: typeof AuthenticatedRouteWithChildren;
}
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
AuthenticatedRoute: AuthenticatedRouteWithChildren,
}
};
export const routeTree = rootRoute
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()
._addFileTypes<FileRouteTypes>();
/* ROUTE_MANIFEST_START
{

View File

@@ -1,34 +1,5 @@
# multi-cursors
## 0.0.79
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-react@0.13.18
## 0.0.78
### Patch Changes
- jazz-react@0.13.17
- jazz-tools@0.13.17
## 0.0.77
### Patch Changes
- jazz-react@0.13.16
- jazz-tools@0.13.16
## 0.0.76
### Patch Changes
- jazz-react@0.13.15
- jazz-tools@0.13.15
## 0.0.75
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "multi-cursors",
"private": true,
"version": "0.0.79",
"version": "0.0.75",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,38 +1,5 @@
# multiauth
## 0.0.27
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-react@0.13.18
- jazz-react-auth-clerk@0.13.18
## 0.0.26
### Patch Changes
- jazz-react@0.13.17
- jazz-react-auth-clerk@0.13.17
- jazz-tools@0.13.17
## 0.0.25
### Patch Changes
- jazz-react@0.13.16
- jazz-react-auth-clerk@0.13.16
- jazz-tools@0.13.16
## 0.0.24
### Patch Changes
- jazz-react@0.13.15
- jazz-react-auth-clerk@0.13.15
- jazz-tools@0.13.15
## 0.0.23
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "multiauth",
"private": true,
"version": "0.0.27",
"version": "0.0.23",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,38 +1,5 @@
# jazz-example-musicplayer
## 0.0.108
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-inspector@0.13.18
- jazz-react@0.13.18
## 0.0.107
### Patch Changes
- jazz-inspector@0.13.17
- jazz-react@0.13.17
- jazz-tools@0.13.17
## 0.0.106
### Patch Changes
- jazz-inspector@0.13.16
- jazz-react@0.13.16
- jazz-tools@0.13.16
## 0.0.105
### Patch Changes
- jazz-inspector@0.13.15
- jazz-react@0.13.15
- jazz-tools@0.13.15
## 0.0.104
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-music-player",
"private": true,
"version": "0.0.108",
"version": "0.0.104",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,34 +1,5 @@
# organization
## 0.0.79
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-react@0.13.18
## 0.0.78
### Patch Changes
- jazz-react@0.13.17
- jazz-tools@0.13.17
## 0.0.77
### Patch Changes
- jazz-react@0.13.16
- jazz-tools@0.13.16
## 0.0.76
### Patch Changes
- jazz-react@0.13.15
- jazz-tools@0.13.15
## 0.0.75
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "organization",
"private": true,
"version": "0.0.79",
"version": "0.0.75",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,34 +1,5 @@
# passkey-svelte
## 0.0.73
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-svelte@0.13.18
## 0.0.72
### Patch Changes
- jazz-svelte@0.13.17
- jazz-tools@0.13.17
## 0.0.71
### Patch Changes
- jazz-svelte@0.13.16
- jazz-tools@0.13.16
## 0.0.70
### Patch Changes
- jazz-svelte@0.13.15
- jazz-tools@0.13.15
## 0.0.69
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "passkey-svelte",
"version": "0.0.73",
"version": "0.0.69",
"type": "module",
"private": true,
"scripts": {

View File

@@ -1,34 +1,5 @@
# minimal-auth-passkey
## 0.0.84
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-react@0.13.18
## 0.0.83
### Patch Changes
- jazz-react@0.13.17
- jazz-tools@0.13.17
## 0.0.82
### Patch Changes
- jazz-react@0.13.16
- jazz-tools@0.13.16
## 0.0.81
### Patch Changes
- jazz-react@0.13.15
- jazz-tools@0.13.15
## 0.0.80
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "passkey",
"private": true,
"version": "0.0.84",
"version": "0.0.80",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,34 +1,5 @@
# passphrase
## 0.0.81
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-react@0.13.18
## 0.0.80
### Patch Changes
- jazz-react@0.13.17
- jazz-tools@0.13.17
## 0.0.79
### Patch Changes
- jazz-react@0.13.16
- jazz-tools@0.13.16
## 0.0.78
### Patch Changes
- jazz-react@0.13.15
- jazz-tools@0.13.15
## 0.0.77
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "passphrase",
"private": true,
"version": "0.0.81",
"version": "0.0.77",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,34 +1,5 @@
# jazz-password-manager
## 0.0.105
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-react@0.13.18
## 0.0.104
### Patch Changes
- jazz-react@0.13.17
- jazz-tools@0.13.17
## 0.0.103
### Patch Changes
- jazz-react@0.13.16
- jazz-tools@0.13.16
## 0.0.102
### Patch Changes
- jazz-react@0.13.15
- jazz-tools@0.13.15
## 0.0.101
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-password-manager",
"private": true,
"version": "0.0.105",
"version": "0.0.101",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,34 +1,5 @@
# jazz-example-pets
## 0.0.203
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-react@0.13.18
## 0.0.202
### Patch Changes
- jazz-react@0.13.17
- jazz-tools@0.13.17
## 0.0.201
### Patch Changes
- jazz-react@0.13.16
- jazz-tools@0.13.16
## 0.0.200
### Patch Changes
- jazz-react@0.13.15
- jazz-tools@0.13.15
## 0.0.199
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-pets",
"private": true,
"version": "0.0.203",
"version": "0.0.199",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,34 +1,5 @@
# reactions
## 0.0.83
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-react@0.13.18
## 0.0.82
### Patch Changes
- jazz-react@0.13.17
- jazz-tools@0.13.17
## 0.0.81
### Patch Changes
- jazz-react@0.13.16
- jazz-tools@0.13.16
## 0.0.80
### Patch Changes
- jazz-react@0.13.15
- jazz-tools@0.13.15
## 0.0.79
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "reactions",
"private": true,
"version": "0.0.83",
"version": "0.0.79",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,40 +1,5 @@
# richtext
## 0.0.73
### Patch Changes
- Updated dependencies [761759c]
- Updated dependencies [133b8ab]
- jazz-tools@0.13.18
- jazz-richtext-prosemirror@0.1.7
- jazz-react@0.13.18
## 0.0.72
### Patch Changes
- Updated dependencies [133b8ab]
- jazz-richtext-prosemirror@0.1.6
- jazz-react@0.13.17
- jazz-tools@0.13.17
## 0.0.71
### Patch Changes
- jazz-react@0.13.16
- jazz-tools@0.13.16
- jazz-richtext-prosemirror@0.1.5
## 0.0.70
### Patch Changes
- jazz-react@0.13.15
- jazz-tools@0.13.15
- jazz-richtext-prosemirror@0.1.4
## 0.0.69
### Patch Changes

View File

@@ -2,8 +2,6 @@
A demonstration of collaborative rich text editing with Jazz, React, and ProseMirror.
Live version: [https://richtext-demo.jazz.tools](https://richtext-demo.jazz.tools)
## Overview
This example shows how to implement collaborative rich text editing using:
@@ -18,52 +16,23 @@ The example features:
- Side-by-side plaintext and rich text editors
- Real-time collaboration across devices
- Persistent document storage
## Getting started
You can either
1. Clone the jazz repository, and run the app within the monorepo.
2. Or create a new Jazz project using this example as a template.
## Running locally
Install dependencies:
### Using the example as a template
Create a new Jazz project, and use this example as a template.
```bash
npx create-jazz-app@latest richtext-app --example richtext
npm i
# or
yarn
```
Go to the new project directory.
```bash
cd richtext-app
```
Then, run the development server:
Run the dev server.
```bash
npm run dev
```
### Using the monorepo
This requires `pnpm` to be installed, see [https://pnpm.io/installation](https://pnpm.io/installation).
Clone the jazz repository.
```bash
git clone https://github.com/garden-co/jazz.git
```
Install and build dependencies.
```bash
pnpm i && npx turbo build
```
Go to the example directory.
```bash
cd jazz/examples/richtext/
```
Start the dev server.
```bash
pnpm dev
# or
yarn dev
```
Open [http://localhost:5173](http://localhost:5173) with your browser to see the result.

View File

@@ -1,7 +1,7 @@
{
"name": "richtext",
"private": true,
"version": "0.0.73",
"version": "0.0.69",
"type": "module",
"scripts": {
"dev": "vite",
@@ -19,7 +19,6 @@
"prosemirror-example-setup": "^1.2.3",
"prosemirror-model": "^1.25.0",
"prosemirror-schema-basic": "^1.2.4",
"prosemirror-schema-list": "^1.5.1",
"prosemirror-state": "^1.4.3",
"prosemirror-view": "^1.39.1",
"react": "18.3.1",

View File

@@ -1,9 +1,7 @@
import { useAccount } from "jazz-react";
import { createJazzPlugin } from "jazz-richtext-prosemirror";
import { exampleSetup } from "prosemirror-example-setup";
import { Schema } from "prosemirror-model";
import { schema as basicSchema } from "prosemirror-schema-basic";
import { addListNodes } from "prosemirror-schema-list";
import { schema } from "prosemirror-schema-basic";
import { EditorState } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { useEffect, useRef } from "react";
@@ -16,11 +14,6 @@ export function Editor() {
useEffect(() => {
if (!me || !editorRef.current || !me.profile.bio) return;
const schema = new Schema({
nodes: addListNodes(basicSchema.spec.nodes, "paragraph block*", "block"),
marks: basicSchema.spec.marks,
});
const setupPlugins = exampleSetup({ schema });
const jazzPlugin = createJazzPlugin(me.profile.bio);

View File

@@ -1,10 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
.ProseMirror ul {
@apply list-disc;
}
.ProseMirror ol {
@apply list-decimal;
}

View File

@@ -1,38 +1,5 @@
# todo-vue
## 0.0.87
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-browser@0.13.18
- jazz-vue@0.13.18
## 0.0.86
### Patch Changes
- jazz-browser@0.13.17
- jazz-tools@0.13.17
- jazz-vue@0.13.17
## 0.0.85
### Patch Changes
- jazz-browser@0.13.16
- jazz-tools@0.13.16
- jazz-vue@0.13.16
## 0.0.84
### Patch Changes
- jazz-browser@0.13.15
- jazz-tools@0.13.15
- jazz-vue@0.13.15
## 0.0.83
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "todo-vue",
"version": "0.0.87",
"version": "0.0.83",
"private": true,
"type": "module",
"scripts": {

View File

@@ -1,34 +1,5 @@
# jazz-example-todo
## 0.0.202
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-react@0.13.18
## 0.0.201
### Patch Changes
- jazz-react@0.13.17
- jazz-tools@0.13.17
## 0.0.200
### Patch Changes
- jazz-react@0.13.16
- jazz-tools@0.13.16
## 0.0.199
### Patch Changes
- jazz-react@0.13.15
- jazz-tools@0.13.15
## 0.0.198
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-todo",
"private": true,
"version": "0.0.202",
"version": "0.0.198",
"type": "module",
"scripts": {
"dev": "vite",
@@ -11,7 +11,6 @@
"preview": "vite preview"
},
"dependencies": {
"@faker-js/faker": "^9.7.0",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-slot": "^1.1.1",
"@radix-ui/react-toast": "^1.1.4",

View File

@@ -41,9 +41,12 @@ export class TodoAccount extends Account {
*/
migrate() {
if (!this._refs.root) {
this.root = TodoAccountRoot.create({
projects: ListOfProjects.create([]),
});
this.root = TodoAccountRoot.create(
{
projects: ListOfProjects.create([], { owner: this }),
},
{ owner: this },
);
}
}
}

View File

@@ -23,7 +23,6 @@ import {
ThemeProvider,
TitleAndLogo,
} from "./basicComponents/index.ts";
import { TaskGenerator } from "./components/TaskGenerator.tsx";
import { wordlist } from "./wordlist.ts";
/**
@@ -96,10 +95,6 @@ export default function App() {
path: "/invite/*",
element: <p>Accepting invite...</p>,
},
{
path: "/generate",
element: <TaskGenerator />,
},
]);
// `useAcceptInvite()` is a hook that accepts an invite link from the URL hash,

View File

@@ -35,11 +35,7 @@ export function ProjectTodoTable() {
// content - whether we create edits locally, load persisted data, or receive
// sync updates from other devices or participants!
// It also recursively resolves and subsribes to all referenced CoValues.
const project = useCoState(TodoProject, projectId, {
resolve: {
tasks: true,
},
});
const project = useCoState(TodoProject, projectId);
// `createTask` is similar to `createProject` we saw earlier, creating a new CoMap
// for a new task (in the same group as the project), and then

View File

@@ -1,61 +0,0 @@
import { TodoAccount } from "@/1_schema";
import { FormEvent, useState } from "react";
import { useNavigate } from "react-router-dom";
import { generateRandomProject } from "../generate";
export function TaskGenerator() {
const [isGenerating, setIsGenerating] = useState(false);
const navigate = useNavigate();
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const numTasks = Math.max(
1,
parseInt(formData.get("numTasks") as string) || 1,
);
setIsGenerating(true);
const project = generateRandomProject(numTasks);
const { root } = await TodoAccount.getMe().ensureLoaded({
resolve: {
root: {
projects: true,
},
},
});
root.projects.push(project);
navigate(`/project/${project.id}`);
};
return (
<div className="p-4 border rounded-lg shadow-sm bg-white">
<h2 className="text-lg font-semibold mb-4">Generate Random Tasks</h2>
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
<div className="flex items-center gap-2">
<label htmlFor="numTasks" className="text-sm font-medium">
Number of tasks:
</label>
<input
id="numTasks"
name="numTasks"
type="number"
min="1"
defaultValue={5}
className="w-20 px-2 py-1 border rounded"
/>
</div>
<button
type="submit"
disabled={isGenerating}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 disabled:bg-blue-300"
>
{isGenerating ? "Generating..." : "Generate Tasks"}
</button>
</form>
</div>
);
}

View File

@@ -1,25 +0,0 @@
import { faker } from "@faker-js/faker";
import { ListOfTasks, Task, TodoProject } from "./1_schema";
export function generateRandomProject(numTasks: number): TodoProject {
// Generate a random project title
const projectTitle = faker.company.catchPhrase();
// Create a list of tasks
const tasks = ListOfTasks.create([]);
// Generate random tasks
for (let i = 0; i < numTasks; i++) {
const task = Task.create({
done: faker.datatype.boolean(),
text: faker.lorem.sentence({ min: 3, max: 8 }),
});
tasks.push(task);
}
// Create and return the project
return TodoProject.create({
title: projectTitle,
tasks: tasks,
});
}

View File

@@ -1,38 +1,5 @@
# version-history
## 0.0.81
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-inspector@0.13.18
- jazz-react@0.13.18
## 0.0.80
### Patch Changes
- jazz-inspector@0.13.17
- jazz-react@0.13.17
- jazz-tools@0.13.17
## 0.0.79
### Patch Changes
- jazz-inspector@0.13.16
- jazz-react@0.13.16
- jazz-tools@0.13.16
## 0.0.78
### Patch Changes
- jazz-inspector@0.13.15
- jazz-react@0.13.15
- jazz-tools@0.13.15
## 0.0.77
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "version-history",
"private": true,
"version": "0.0.81",
"version": "0.0.77",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -2,7 +2,6 @@ import {
AlertTriangleIcon,
ArrowDownIcon,
ArrowRightIcon,
BoldIcon,
BookTextIcon,
BoxIcon,
BracesIcon,
@@ -21,7 +20,6 @@ import {
HashIcon,
ImageIcon,
InfoIcon,
ItalicIcon,
LinkIcon,
LockKeyholeIcon,
type LucideIcon,
@@ -89,10 +87,6 @@ const icons = {
colist: Brackets,
user: UserIcon,
group: UsersIcon,
// text editor icons
bold: BoldIcon,
italic: ItalicIcon,
};
// copied from tailwind line height https://tailwindcss.com/docs/font-size

View File

@@ -22,7 +22,7 @@ export function JazzLogo({
fillRule="evenodd"
clipRule="evenodd"
d="M136.179 44.8277C136.179 44.8277 136.179 44.8277 136.179 44.8276V21.168C117.931 28.5527 97.9854 32.6192 77.0897 32.6192C65.1466 32.6192 53.5138 31.2908 42.331 28.7737V51.4076C42.331 51.4076 42.331 51.4076 42.331 51.4076V81.1508C41.2955 80.4385 40.1568 79.8458 38.9405 79.3915C36.1732 78.358 33.128 78.0876 30.1902 78.6145C27.2524 79.1414 24.5539 80.4419 22.4358 82.3516C20.3178 84.2613 18.8754 86.6944 18.291 89.3433C17.7066 91.9921 18.0066 94.7377 19.1528 97.2329C20.2991 99.728 22.2403 101.861 24.7308 103.361C27.2214 104.862 30.1495 105.662 33.1448 105.662H33.1455C33.6061 105.662 33.8365 105.662 34.0314 105.659C44.5583 105.449 53.042 96.9656 53.2513 86.4386C53.2534 86.3306 53.2544 86.2116 53.2548 86.0486H53.2552V85.7149L53.2552 85.5521V82.0762L53.2552 53.1993C61.0533 54.2324 69.0092 54.7656 77.0897 54.7656C77.6696 54.7656 78.2489 54.7629 78.8276 54.7574V110.696C77.792 109.983 76.6533 109.391 75.437 108.936C72.6697 107.903 69.6246 107.632 66.6867 108.159C63.7489 108.686 61.0504 109.987 58.9323 111.896C56.8143 113.806 55.3719 116.239 54.7875 118.888C54.2032 121.537 54.5031 124.283 55.6494 126.778C56.7956 129.273 58.7368 131.405 61.2273 132.906C63.7179 134.406 66.646 135.207 69.6414 135.207C70.1024 135.207 70.3329 135.207 70.5279 135.203C81.0548 134.994 89.5385 126.51 89.7478 115.983C89.7517 115.788 89.7517 115.558 89.7517 115.097V111.621L89.7517 54.3266C101.962 53.4768 113.837 51.4075 125.255 48.2397V80.9017C124.219 80.1894 123.081 79.5966 121.864 79.1424C119.097 78.1089 116.052 77.8384 113.114 78.3653C110.176 78.8922 107.478 80.1927 105.36 82.1025C103.242 84.0122 101.799 86.4453 101.215 89.0941C100.631 91.743 100.931 94.4886 102.077 96.9837C103.223 99.4789 105.164 101.612 107.655 103.112C110.145 104.612 113.073 105.413 116.069 105.413C116.53 105.413 116.76 105.413 116.955 105.409C127.482 105.2 135.966 96.7164 136.175 86.1895C136.179 85.9945 136.179 85.764 136.179 85.3029V81.8271L136.179 44.8277Z"
fill="#146AFF"
className="fill-primary"
/>
</svg>
);

View File

@@ -1,3 +1,5 @@
import { readFile } from "node:fs/promises";
import { join } from "node:path";
import { ImageResponse } from "next/og";
import { JazzLogo } from "../atoms/logos/JazzLogo";
@@ -8,112 +10,28 @@ export const imageSize = {
export const imageContentType = "image/png";
async function loadManropeGoogleFont() {
const url = `https://fonts.googleapis.com/css2?family=Manrope:wght@600`;
const css = await (await fetch(url)).text();
const resource = css.match(
/src: url\((.+)\) format\('(opentype|truetype)'\)/,
export default async function OpenGraphImage({ title }: { title: string }) {
const manropeSemiBold = await readFile(
join(process.cwd(), "public/fonts/Manrope-SemiBold.ttf"),
);
if (resource) {
const response = await fetch(resource[1]);
if (response.status == 200) {
return await response.arrayBuffer();
}
}
throw new Error("failed to load font data");
}
export async function OpenGraphImage({
title,
framework,
contents,
topic,
subtopic,
}: {
title: string;
framework?: string;
contents?: string[];
topic?: string;
subtopic?: string;
}) {
if (!title) {
throw new Error(
`No title from tocItems in opengraph-image.tsx ${framework} ${topic} ${subtopic}`,
);
}
return new ImageResponse(
<div
style={{
fontSize: "7em",
background: "white",
width: "100%",
height: "100%",
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
justifyContent: "center",
alignItems: "center",
justifyContent: "flex-start",
padding: "77px",
letterSpacing: "-0.05em",
}}
>
{title}
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: "1rem",
fontSize: "4rem",
}}
>
{title}
</div>
<div
style={{
position: "absolute",
right: 15,
top: 10,
display: "flex",
alignItems: "center",
justifyContent: "flex-start",
fontSize: "3rem",
color: "#888888",
letterSpacing: "-0.03em",
}}
>
<div style={{ display: "flex", color: "#BBB", paddingRight: "0.5rem" }}>
{framework}
</div>
{topic && (
<span style={{ color: "#CCC", paddingRight: "0.5rem" }}>
/ {topic}
</span>
)}
{subtopic && <span style={{ color: "#DDD" }}>/ {subtopic}</span>}
</div>
<div
style={{
display: "flex",
flexDirection: "column",
marginTop: "1rem",
gap: "0.2rem",
fontSize: "2rem",
color: "#888888",
letterSpacing: "-0.03em",
}}
>
{contents?.map((content) => (
<div key={content}>{content}</div>
))}
</div>
<div
style={{
display: "flex",
position: "absolute",
bottom: 35,
right: 45,
}}
style={{ display: "flex", position: "absolute", bottom: 35, right: 45 }}
>
<JazzLogo width={193} height={73} />
</div>
@@ -123,9 +41,7 @@ export async function OpenGraphImage({
fonts: [
{
name: "Manrope",
data: await loadManropeGoogleFont(),
style: "normal",
weight: 600,
data: manropeSemiBold,
},
],
},

View File

@@ -1,6 +1,6 @@
import { packages } from "@/content/packages";
import { Icon } from "@garden-co/design-system/src/components/atoms/Icon";
import { clsx } from "clsx";
import { Icon } from "@garden-co/design-system/src/components/atoms/Icon";
import type { Metadata } from "next";
import Link from "next/link";

View File

@@ -1,25 +0,0 @@
import { OpenGraphImage, imageSize, imageContentType } from '@garden-co/design-system/src/components/organisms/OpenGraphImage';
import { getMdxWithToc } from '@/lib/docMdxContent';
export const title = "Quickstart";
export const size = imageSize;
export const contentType = imageContentType;
export const alt = "Quickstart";
export default async function Image({ params }: { params: Promise<{ framework: string, topic: string, subtopic: string }> }) {
const { framework, topic, subtopic } = await params;
const { tocItems } = await getMdxWithToc(framework, [topic, subtopic]);
const title = tocItems[0]?.value;
if (!title) {
throw new Error(`No title from tocItems in opengraph-image.tsx ${framework} ${topic} ${subtopic}`);
}
return OpenGraphImage({
title: title,
framework,
contents: tocItems[0]?.children?.map((child) => child.value) ?? [],
topic,
subtopic,
});
}

View File

@@ -1,26 +0,0 @@
import { OpenGraphImage, imageSize, imageContentType } from '@garden-co/design-system/src/components/organisms/OpenGraphImage';
import { getMdxWithToc } from '@/lib/docMdxContent';
export const title = "Quickstart";
export const size = imageSize;
export const contentType = imageContentType;
export const alt = "Quickstart";
export default async function Image({ params }: { params: Promise<{ framework: string, topic: string }> }) {
const { framework, topic } = await params;
const { tocItems } = await getMdxWithToc(framework, [topic]);
// console.log('tocItems', tocItems);
const title = tocItems[0]?.value;
if (!title) {
throw new Error(`No title from tocItems in opengraph-image.tsx ${framework} ${topic}`);
}
return OpenGraphImage({
title: title,
framework,
contents: tocItems[0]?.children?.map((child) => child.value) ?? [],
topic,
});
}

View File

@@ -1,32 +0,0 @@
import { getMdxWithToc } from "@/lib/docMdxContent";
import {
OpenGraphImage,
imageSize,
imageContentType,
} from "@garden-co/design-system/src/components/organisms/OpenGraphImage";
export const title = "Quickstart";
export const size = imageSize;
export const contentType = imageContentType;
export const alt = "Jazz Docs | Quickstart";
export default async function Image({
params,
}: {
params: Promise<{ framework: string }>;
}) {
const { framework } = await params;
const { tocItems } = await getMdxWithToc(framework, []);
const title = tocItems[0]?.value;
if (!title) {
throw new Error(`No title from tocItems in opengraph-image.tsx ${framework}`);
}
return OpenGraphImage({
title: title,
framework,
contents: tocItems[0]?.children?.map((child) => child.value) ?? [],
});
}

View File

@@ -6,11 +6,11 @@ import { ReactNativeLogo } from "@/components/icons/ReactNativeLogo";
import { SvelteLogo } from "@/components/icons/SvelteLogo";
import { VueLogo } from "@/components/icons/VueLogo";
import { Example, features, tech } from "@/content/example";
import { clsx } from "clsx";
import { H2 } from "@garden-co/design-system/src/components/atoms/Headings";
import { Icon } from "@garden-co/design-system/src/components/atoms/Icon";
import { GappedGrid } from "@garden-co/design-system/src/components/molecules/GappedGrid";
import { HeroHeader } from "@garden-co/design-system/src/components/molecules/HeroHeader";
import { clsx } from "clsx";
import type { Metadata } from "next";
const title = "Examples";
@@ -198,12 +198,6 @@ const MusicIllustration = () => (
</div>
);
const JazzPaperScissorsIllustration = () => (
<div className="flex flex-col items-center justify-center h-full p-8 text-4xl">
</div>
);
const ImageUploadIllustration = () => (
<div className="flex flex-col items-center justify-center h-full p-8">
<div className="p-3 w-[12rem] h-[8rem] border border-dashed border-blue dark:border-blue-500 rounded-lg flex gap-2 flex-col items-center justify-center">
@@ -261,21 +255,21 @@ const ReactionsIllustration = () => (
const MultiCursorIllustration = () => (
<div className="flex bg-stone-100 h-full flex-col items-center justify-center dark:bg-transparent p-4">
<div className=" bg-white min-w-64 md:aspect-[3/2] flex flex-col rounded-md shadow-xl shadow-stone-400/20 dark:shadow-none">
<div className=" bg-white md:aspect-[3/2] flex flex-col rounded-md shadow-xl shadow-stone-400/20 dark:shadow-none">
<div className="w-full py-2 flex items-center gap-1.5 px-2 border-b dark:border-b-stone-200">
<span className="rounded-full size-2 bg-stone-200"></span>
<span className="rounded-full size-2 bg-stone-200"></span>
<span className="rounded-full size-2 bg-stone-200"></span>
</div>
<div className="h-full mx-auto flex flex-col justify-center p-12">
<div className="h-full mx-auto flex flex-col justify-center p-12 sm:p-16">
<div className="inline-block relative px-1 ring-1 ring-blue-400">
<div className="absolute size-2 bg-white border border-blue-400 -left-1 -top-1"></div>
<div className="absolute size-2 bg-white border border-blue-400 -right-1 -top-1"></div>
<div className="absolute size-2 bg-white border border-blue-400 -left-1 -bottom-1"></div>
<div className="absolute size-2 bg-white border border-blue-400 -right-1 -bottom-1"></div>
<span className="text-lg font-semibold md:text-2xl md:font-bold text-stone-800">
<span className="text-lg font-semibold md:text-2xl md:font-bold text-stone-800 ">
Hello, world!
</span>
<div className="absolute -top-10 right-4 text-rose-600 flex items-end gap-1">
@@ -291,21 +285,6 @@ const MultiCursorIllustration = () => (
</div>
);
const CoTextIllustration = () => (
<div className="flex bg-stone-100 h-full flex-col items-center justify-center dark:bg-transparent p-4">
<div className=" bg-white md:aspect-[3/2] min-w-64 flex flex-col rounded-md shadow-xl shadow-stone-400/20 dark:shadow-none">
<div className="flex gap-2 p-3 border-b">
<Icon name="bold" size="xs" />
<Icon name="italic" size="xs" />
<Icon name="code" size="xs" />
</div>
<div className="py-2 px-3 text-xl text-stone-800">
<em>Hello</em>, <strong>world!</strong>
</div>
</div>
</div>
);
const PetIllustration = () => (
<div className="h-full p-4 bg-[url('/dog.jpg')] bg-cover bg-center p-4 flex items-end">
<div className="inline-flex justify-center gap-1 mx-auto">
@@ -455,19 +434,9 @@ const reactExamples: Example[] = [
"Track user presence on a canvas with multiple cursors and out of bounds indicators.",
tech: [tech.react],
features: [features.coFeed],
demoUrl: "https://multi-cursors-demo.jazz.tools",
demoUrl: "https://jazz-multi-cursors.vercel.app",
illustration: <MultiCursorIllustration />,
},
{
name: "Collaborative rich text",
slug: "richtext",
description:
"Handle multiple users editing the same text, integrated with a ProseMirror editor for rich text.",
tech: [tech.react],
features: [features.coRichText, features.coPlainText],
demoUrl: "https://richtext-demo.jazz.tools",
illustration: <CoTextIllustration />,
},
{
name: "Rate my pet",
slug: "pets",
@@ -508,16 +477,6 @@ const reactExamples: Example[] = [
demoUrl: "https://music-demo.jazz.tools",
illustration: <MusicIllustration />,
},
{
name: "Jazz paper scissors",
slug: "jazz-paper-scissors",
description:
"A game that shows how to communicate with other accounts through the experimental Inbox API.",
tech: [tech.react],
features: [features.serverWorker, features.inbox],
illustration: <JazzPaperScissorsIllustration />,
demoUrl: "https://jazz-paper-scissors.vercel.app",
},
{
name: "Clerk",
slug: "clerk",
@@ -552,7 +511,6 @@ const reactExamples: Example[] = [
tech: [tech.react],
features: [features.inviteLink],
illustration: <OrganizationIllustration />,
demoUrl: "https://jazz-organization.vercel.app",
},
{
name: "Version history",
@@ -561,7 +519,6 @@ const reactExamples: Example[] = [
"Track and restore previous versions of your data, and see who made the changes.",
tech: [tech.react],
illustration: <VersionHistoryIllustration />,
demoUrl: "https://jazz-version-history.vercel.app",
},
];
@@ -609,6 +566,7 @@ const vueExamples: Example[] = [
description: "A todo list where you can collaborate with invited guests.",
tech: [tech.vue],
features: [features.inviteLink],
demoUrl: "https://todo-demo.jazz.tools",
illustration: (
<div className="h-full w-full bg-cover bg-[url('/todo.jpg')] bg-left-bottom"></div>
),

View File

@@ -1,6 +1,6 @@
import LatencyChart from "@/components/LatencyChart";
import { HeroHeader } from "@garden-co/design-system/src/components/molecules/HeroHeader";
import { clsx } from "clsx";
import { HeroHeader } from "@garden-co/design-system/src/components/molecules/HeroHeader";
import type { Metadata } from "next";
import { Fragment } from "react";

View File

@@ -53,7 +53,6 @@ pre.shiki .line {
.twoslash-popup-code pre.shiki .line {
display: inline;
padding-left: 0;
white-space: break-spaces;
}
html.dark .shiki {

View File

@@ -1,11 +1,11 @@
import "./globals.css";
import type { Metadata } from "next";
import { fontClasses } from "@garden-co/design-system/src/fonts";
import { ThemeProvider } from "@/components/ThemeProvider";
import { JazzFooter } from "@/components/footer";
import { marketingCopy } from "@/content/marketingCopy";
import { fontClasses } from "@garden-co/design-system/src/fonts";
import { Analytics } from "@vercel/analytics/react";
import { SpeedInsights } from "@vercel/speed-insights/next";
import type { Metadata } from "next";
const metaTags = {
title: `Jazz - ${marketingCopy.headline}`,

View File

@@ -1,6 +1,5 @@
import { marketingCopy } from "@/content/marketingCopy";
import {
OpenGraphImage,
import OpenGraphImage, {
imageSize,
imageContentType,
} from "@garden-co/design-system/src/components/organisms/OpenGraphImage";

View File

@@ -1,7 +1,7 @@
"use client";
import { Button } from "@garden-co/design-system/src/components/atoms/Button";
import { track } from "@vercel/analytics";
import { Button } from "@garden-co/design-system/src/components/atoms/Button";
export function FakeGetStartedButton({ tier }: { tier: "starter" | "indie" }) {
return (

View File

@@ -1,5 +1,5 @@
import { Button } from "@garden-co/design-system/src/components/atoms/Button";
import { clsx } from "clsx";
import { Button } from "@garden-co/design-system/src/components/atoms/Button";
import {
CircleCheckIcon,
LucideBuilding2,

View File

@@ -1,7 +1,7 @@
"use client";
import { Icon } from "@garden-co/design-system/src/components/atoms/Icon";
import { clsx } from "clsx";
import { Icon } from "@garden-co/design-system/src/components/atoms/Icon";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { ReactNode } from "react";

View File

@@ -1,6 +1,6 @@
import { JazzNav } from "@/components/nav";
import { NavSection } from "@garden-co/design-system/src/components/organisms/Nav";
import { clsx } from "clsx";
import { NavSection } from "@garden-co/design-system/src/components/organisms/Nav";
export function SideNavLayout({
children,

View File

@@ -2,9 +2,9 @@
import { TableOfContents } from "@/components/docs/TableOfContents";
import { JazzMobileNav } from "@/components/nav";
import { TocEntry } from "@stefanprobst/rehype-extract-toc";
import type { IconName } from "@garden-co/design-system/src/components/atoms/Icon";
import { NavSection } from "@garden-co/design-system/src/components/organisms/Nav";
import { TocEntry } from "@stefanprobst/rehype-extract-toc";
export default function DocsLayout({
children,
@@ -19,7 +19,8 @@ export default function DocsLayout({
navIcon?: IconName;
tocItems?: TocEntry[];
}) {
const tableOfContentsItems = tocItems ?? [];
const tableOfContentsItems =
tocItems?.length && tocItems[0].children ? tocItems[0].children : [];
const navSections: NavSection[] = [
{

View File

@@ -3,14 +3,13 @@
import type { Toc, TocEntry } from "@stefanprobst/rehype-extract-toc";
import { clsx } from "clsx";
import Link from "next/link";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useCallback, useEffect, useState } from "react";
const TocList = ({
items,
level,
currentId,
}: { items: Toc; level: number; currentId: string }) => {
const isActive = (item: TocEntry) => {
if (!item.id) return false;
if (item.id === currentId) return true;
@@ -21,7 +20,7 @@ const TocList = ({
};
return (
<ul className="space-y-2" style={{ paddingLeft: (level > 0) ? "1rem" : "0" }}>
<ul className="space-y-2" style={{ paddingLeft: level ? "1rem" : "0" }}>
{items.map((item) => (
<li key={item.id} className="space-y-2">
{item.id && (
@@ -58,10 +57,8 @@ export function TableOfContents({
}) {
const [currentId, setCurrentId] = useState<string>("");
const itemsUnderH1 = useMemo(() => items[0]?.children || [], [items]);
const getHeadings = useCallback(() => {
return itemsUnderH1
return items
.flatMap((node) => {
const headings = [node];
if (node.children) {
@@ -81,17 +78,17 @@ export function TableOfContents({
return { id: item.id, top };
})
.filter((x): x is { id: string; top: number } => x !== null);
}, [itemsUnderH1]);
}, [items]);
useEffect(() => {
if (itemsUnderH1.length === 0) return;
if (items.length === 0) return;
const onScroll = () => {
const headings = getHeadings();
if (headings.length === 0) return;
const top = window.scrollY;
let current = headings[0]?.id;
let current = headings[0].id;
for (const heading of headings) {
if (top >= heading.top - 500) {
@@ -101,7 +98,7 @@ export function TableOfContents({
}
}
current && setCurrentId(current);
setCurrentId(current);
};
window.addEventListener("scroll", onScroll, { passive: true });
@@ -110,14 +107,14 @@ export function TableOfContents({
return () => {
window.removeEventListener("scroll", onScroll);
};
}, [getHeadings, itemsUnderH1]);
}, [getHeadings, items]);
if (!itemsUnderH1.length) return null;
if (!items.length) return null;
return (
<div className={className}>
<p className="font-medium text-highlight mb-3">On this page</p>
<TocList items={itemsUnderH1} level={0} currentId={currentId} />
<TocList items={items} level={0} currentId={currentId} />
</div>
);
}

View File

@@ -2,6 +2,7 @@
import { Example } from "@/content/example";
import { InterpolateInCode } from "@/mdx-components";
import { DialogDescription } from "@headlessui/react";
import { Button } from "@garden-co/design-system/src/components/atoms/Button";
import { CodeGroup } from "@garden-co/design-system/src/components/molecules/CodeGroup";
import {
@@ -10,7 +11,6 @@ import {
DialogBody,
DialogTitle,
} from "@garden-co/design-system/src/components/organisms/Dialog";
import { DialogDescription } from "@headlessui/react";
import { useState } from "react";
import CreateJazzApp from "./CreateJazzApp.mdx";

View File

@@ -1,8 +1,6 @@
```tsx
<JazzProvider
sync={{
peer: "wss://cloud.jazz.tools/?key=you@example.com"
}}
sync={{ peer: "wss://cloud.jazz.tools/?key=you@example.com" }}
>
{children}
</JazzProvider>

View File

@@ -1,8 +1,8 @@
import { clsx } from "clsx";
import { Card } from "@garden-co/design-system/src/components/atoms/Card";
import { H2 } from "@garden-co/design-system/src/components/atoms/Headings";
import { Kicker } from "@garden-co/design-system/src/components/atoms/Kicker";
import { GappedGrid } from "@garden-co/design-system/src/components/molecules/GappedGrid";
import { clsx } from "clsx";
import CodeStepAction from "./CodeStepAction.mdx";
import CodeStepCloud from "./CodeStepCloud.mdx";
import CodeStepRender from "./CodeStepRender.mdx";
@@ -21,7 +21,7 @@ function Code({
<div
className={clsx(
className,
"w-full h-full relative -right-2 -bottom-1 max-w-full lg:max-w-[500px] ml-auto",
"w-full h-full relative -right-2 -bottom-1 max-w-full lg:max-w-[480px] overflow-x-auto ml-auto overflow-hidden",
"shadow-xl shadow-blue/20",
"rounded-tl-lg border",
"flex-1 bg-white ring ring-4 ring-stone-400/20",
@@ -33,9 +33,7 @@ function Code({
{fileName}
</span>
</div>
<pre className="text-xs lg:text-sm p-1 pb-2 overflow-x-auto">
{children}
</pre>
<pre className="text-xs lg:text-sm p-1 pb-2">{children}</pre>
</div>
);
}

File diff suppressed because one or more lines are too long

View File

@@ -134,20 +134,9 @@ export async function onAnonymousAccountDiscarded(
To see how this works, try uploading a song in the [music player demo](https://music-demo.jazz.tools/) and then log in with an existing account.
## Provider Configuration for Authentication
You can configure how authentication states work in your app with the [JazzProvider](/docs/project-setup/providers/). The provider offers several options that impact authentication behavior:
- `guestMode`: Enable/disable Guest Mode
- `onAnonymousAccountDiscarded`: Handle data migration when switching accounts
- `sync.when`: Control when data synchronization happens
- `defaultProfileName`: Set default name for new user profiles
For detailed information on all provider options, see [Provider Configuration options](/docs/project-setup/providers/#additional-options).
## Controlling sync for different authentication states
You can control network sync with [Providers](/docs/project-setup/providers/) based on authentication state:
You can control network sync with [Providers](/docs/project-setup/providers) based on authentication state:
- `when: "always"`: Sync is enabled for both Anonymous Authentication and Authenticated Account
- `when: "signedUp"`: Sync is enabled when the user is authenticated
@@ -208,7 +197,7 @@ function App() {
### Configuring Guest Mode Access
You can configure Guest Mode access with the `guestMode` prop for [Providers](/docs/project-setup/providers/).
You can configure Guest Mode access with the `guestMode` prop for [Providers](/docs/project-setup/providers).
<ContentByFramework framework="react">
<CodeGroup>

View File

@@ -53,7 +53,6 @@ export const docNavigationItems = [
name: "Providers",
href: "/docs/project-setup/providers",
done: {
react: 100,
"react-native": 100,
"react-native-expo": 100,
},
@@ -169,11 +168,6 @@ export const docNavigationItems = [
href: "/docs/using-covalues/cofeeds",
done: 100,
},
{
name: "CoTexts",
href: "/docs/using-covalues/cotexts",
done: 100,
},
{
name: "FileStreams",
href: "/docs/using-covalues/filestreams",

View File

@@ -1,4 +1,4 @@
import { CodeGroup, ContentByFramework } from '@/components/forMdx'
import { CodeGroup } from '@/components/forMdx'
import { JazzIcon } from "@garden-co/design-system/src/components/atoms/logos/JazzIcon";
# Jazz Inspector
@@ -9,14 +9,11 @@ For now, you can get your account credentials from the `jazz-logged-in-secret` l
[https://inspector.jazz.tools](https://inspector.jazz.tools)
<ContentByFramework framework={["react", "svelte", "vue", "vanilla"]}>
## Exporting current account to Inspector from your app [!framework=react,svelte,vue,vanilla]
## Exporting current account to Inspector from your app
In development mode, you can launch the Inspector from your Jazz app to inspect your account by pressing `Cmd+J`.
</ContentByFramework>
<ContentByFramework framework="react">
## Embedding the Inspector widget into your app [!framework=react]
## Embedding the Inspector widget into your app
Alternatively, you can embed the Inspector directly into your app, so you don't need to open a separate window.
@@ -43,7 +40,7 @@ import { JazzInspector } from "jazz-inspector";
This will show the Inspector launch button on the right of your page.
### Positioning the Inspector button [!framework=react]
### Positioning the Inspector button
You can also customize the button position with the following options:
@@ -70,4 +67,3 @@ For example:
</div>
Check out the [music player app](https://github.com/garden-co/jazz/blob/main/examples/music-player/src/2_main.tsx) for a full example.
</ContentByFramework>

View File

@@ -4,6 +4,8 @@ import { CodeGroup } from "@/components/forMdx";
# Providers
## Introduction
`<JazzProvider />` is the core component that connects your Expo application to Jazz. It handles:
- **Data Synchronization**: Manages connections to peers and the Jazz cloud

View File

@@ -4,6 +4,8 @@ import { CodeGroup } from "@/components/forMdx";
# Providers
## Introduction
`<JazzProvider />` is the core component that connects your React Native application to Jazz. It handles:
- **Data Synchronization**: Manages connections to peers and the Jazz cloud

View File

@@ -1,193 +0,0 @@
export const metadata = { title: "Providers" };
import { CodeGroup } from "@/components/forMdx";
# Providers
`<JazzProvider />` is the core component that connects your React application to Jazz. It handles:
- **Data Synchronization**: Manages connections to peers and the Jazz cloud
- **Local Storage**: Persists data locally between app sessions
- **Schema Types**: Provides APIs for the [AccountSchema](/docs/schemas/accounts-and-migrations)
- **Authentication**: Connects your authentication system to Jazz
Our [Chat example app](https://jazz.tools/examples#chat) provides a complete implementation of JazzProvider with authentication and real-time data sync.
## Setting up the Provider
The `<JazzProvider />` accepts several configuration options:
<CodeGroup>
```tsx twoslash
// @filename: schema.ts
import { Account, co } from "jazz-tools";
export class MyAppAccount extends Account {
name = co.string;
}
// @filename: app.tsx
import * as React from "react";
// ---cut---
// App.tsx
import { JazzProvider } from "jazz-react";
import { MyAppAccount } from "./schema";
export function MyApp({ children }: { children: React.ReactNode }) {
return (
<JazzProvider
sync={{
peer: "wss://cloud.jazz.tools/?key=your-api-key",
when: "always" // When to sync: "always", "never", or "signedUp"
}}
AccountSchema={MyAppAccount}
>
{children}
</JazzProvider>
);
}
// Register the Account schema so `useAccount` returns our custom `MyAppAccount`
declare module "jazz-react" {
interface Register {
Account: MyAppAccount;
}
}
```
</CodeGroup>
## Provider Options
### Sync Options
The `sync` property configures how your application connects to the Jazz network:
<CodeGroup>
```tsx twoslash
import { type SyncConfig } from "jazz-tools";
const syncConfig: SyncConfig = {
// Connection to Jazz Cloud or your own sync server
peer: "wss://cloud.jazz.tools/?key=your-api-key",
// When to sync: "always" (default), "never", or "signedUp"
when: "always",
}
```
</CodeGroup>
See [Authentication States](/docs/authentication/authentication-states#controlling-sync-for-different-authentication-states) for more details on how the `when` property affects synchronization based on authentication state.
### Account Schema
The `AccountSchema` property defines your application's account structure:
<CodeGroup>
```tsx twoslash
// @filename: schema.ts
import { Account, CoMap, co } from "jazz-tools";
// schema.ts
class Preferences extends CoMap {
theme = co.string;
notifications = co.boolean;
}
// Define your custom account schema
export class MyAppAccount extends Account {
name = co.string;
preferences = co.ref(Preferences);
}
// @filename: app.tsx
import * as React from "react";
import { JazzProvider } from "jazz-react";
import { SyncConfig } from "jazz-tools";
const syncConfig: SyncConfig = {
peer: "wss://cloud.jazz.tools/?key=your-api-key",
when: "always",
}
// ---cut---
// app.tsx
import { MyAppAccount } from "./schema";
export function MyApp ({ children }: { children: React.ReactNode }) {
// Use in provider
return (
<JazzProvider
sync={syncConfig}
AccountSchema={MyAppAccount}
>
{children}
</JazzProvider>
);
}
// Register type for useAccount
declare module "jazz-react" {
interface Register {
Account: MyAppAccount;
}
}
```
</CodeGroup>
### Additional Options
The provider accepts these additional options:
<CodeGroup>
```tsx twoslash
import * as React from "react";
import { JazzProvider } from "jazz-react";
import { SyncConfig } from "jazz-tools";
const syncConfig: SyncConfig = {
peer: "wss://cloud.jazz.tools/?key=your-api-key",
when: "always",
}
// ---cut---
// app.tsx
export function MyApp ({ children }: { children: React.ReactNode }) {
return (
<JazzProvider
sync={syncConfig}
// Enable guest mode for account-less access
guestMode={false}
// Set default name for new user profiles
defaultProfileName="New User"
// Handle user logout
onLogOut={() => {
console.log("User logged out");
}}
// Handle anonymous account data when user logs in to existing account
onAnonymousAccountDiscarded={(account) => {
console.log("Anonymous account discarded", account.id);
// Migrate data here
return Promise.resolve();
}}
>
{children}
</JazzProvider>
);
}
```
</CodeGroup>
See [Authentication States](/docs/authentication/authentication-states) for more information on authentication states, guest mode, and handling anonymous accounts.
## Authentication
`<JazzProvider />` works with various authentication methods to enable users to access their data across multiple devices. For a complete guide to authentication, see our [Authentication Overview](/docs/authentication/overview).
## Need Help?
If you have questions about configuring the Jazz Provider for your specific use case, [join our Discord community](https://discord.gg/utDMjHYg42) for help.

View File

@@ -1,380 +1,99 @@
export const metadata = { title: "Installation" };
export const metadata = { title: "React" };
import { CodeGroup } from "@/components/forMdx";
# Installation and Setup
# <span id="react">React</span>
Add Jazz to your React application in minutes. This setup covers standard React apps, Next.js, and gives an overview of experimental SSR approaches.
Integrating Jazz with React is straightforward. You'll define data schemas that describe your application's structure, then wrap your app with a provider that handles sync and storage. The whole process takes just three steps:
1. [Install dependencies](#install-dependencies)
2. [Write your schema](#write-your-schema)
3. [Wrap your app in `<JazzProvider />`](#standard-react-setup)
Looking for complete examples? Check out our [example applications](/examples) for chat apps, collaborative editors, and more.
## Install dependencies
First, install the required packages:
Wrap your application with `<JazzProvider />`, this is where you specify the sync & storage server to connect to (see [Sync and storage](/docs/react/sync-and-storage)).
<CodeGroup>
```bash
pnpm install jazz-react jazz-tools
```
</CodeGroup>
## Write your schema
Define your data schema using [CoValues](/docs/schemas/covalues) from `jazz-tools`.
<CodeGroup>
```tsx twoslash
// schema.ts
import { Account, co } from "jazz-tools";
export class MyAppAccount extends Account {
name = co.string;
}
```
</CodeGroup>
See [CoValues](/docs/schemas/covalues) for more information on how to define your schema.
## Standard React Setup
Wrap your application with `<JazzProvider />` to connect to the Jazz network and define your data schema:
<CodeGroup>
```tsx twoslash
// @filename: schema.ts
import { Account } from "jazz-tools";
export class MyAppAccount extends Account {}
// @filename: app.tsx
import * as React from "react";
import { createRoot } from 'react-dom/client';
function App() {
return <div>Hello, world!</div>;
}
// ---cut---
// app.tsx
{/* prettier-ignore */}
```tsx
import { JazzProvider } from "jazz-react";
import { MyAppAccount } from "./schema";
createRoot(document.getElementById("root")!).render(
<JazzProvider
sync={{ peer: "wss://cloud.jazz.tools/?key=you@example.com" }}
AccountSchema={MyAppAccount}
>
<App />
</JazzProvider>
ReactDOM.createRoot(document.getElementById("root")!).render(
<JazzProvider // [!code ++:6]
sync={{ peer: "wss://cloud.jazz.tools/?key=you@example.com" }}
AccountSchema={MyAppAccount}
>
<App />
</JazzProvider>
);
// Register your Account schema to enhance TypeScript support
// [!code ++:6]
// Register the Account schema so `useAccount` returns our custom `MyAppAccount`
declare module "jazz-react" {
interface Register {
Account: MyAppAccount;
}
interface Register {
Account: MyAppAccount;
}
}
```
</CodeGroup>
This setup handles:
- Connection to the Jazz sync server
- Schema registration for type-safe data handling
- Local storage configuration
With this in place, you're ready to start using Jazz hooks in your components. [Learn how to access and update your data](/docs/using-covalues/subscription-and-loading#subscription-hooks).
## Next.js Integration
## Next.js
### Client-side Only (Easiest)
### Client-side only
The simplest approach for Next.js is client-side only integration:
The easiest way to use Jazz with Next.JS is to only use it on the client side. You can ensure this by:
- marking the Jazz provider file as `"use client"`
<CodeGroup>
```tsx twoslash
// @filename: schema.ts
import { Account } from "jazz-tools";
export class MyAppAccount extends Account {}
// @filename: app.tsx
import * as React from "react";
// ---cut---
// app.tsx
"use client" // Mark as client component
{/* prettier-ignore */}
```tsx
"use client" // [!code ++]
import { JazzProvider } from "jazz-react";
import { MyAppAccount } from "./schema";
export function JazzWrapper({ children }: { children: React.ReactNode }) {
return (
<JazzProvider
sync={{ peer: "wss://cloud.jazz.tools/?key=you@example.com" }}
AccountSchema={MyAppAccount}
>
{children}
</JazzProvider>
);
export function MyJazzProvider(props: { children: React.ReactNode }) {
return (
<JazzProvider
sync={{ peer: "wss://cloud.jazz.tools/?key=you@example.com" }}
AccountSchema={MyAppAccount}
>
{props.children}
</JazzProvider>
);
}
```
</CodeGroup>
Remember to mark any component that uses Jazz hooks with `"use client"`:
- marking any file with components where you use Jazz hooks (such as `useAccount` or `useCoState`) as `"use client"`
<CodeGroup>
```tsx twoslash
// @filename: schema.ts
import { Account, co } from "jazz-tools";
### SSR use (experimental)
export class MyAppAccount extends Account {
name = co.string;
}
// @filename: Profile.tsx
import * as React from "react";
import { MyAppAccount } from "./schema";
Pure SSR use of Jazz is basically just using jazz-nodejs (see [Node.JS / Server Workers](/docs/react/project-setup/server-side)) inside Server Components.
declare module "jazz-react" {
interface Register {
Account: MyAppAccount;
}
}
// ---cut---
// Profile.tsx
"use client"; // [!code ++]
Instead of using hooks as you would on the client, you await promises returned by `CoValue.load(...)` inside your Server Components.
import { useAccount } from "jazz-react";
TODO: code example
export function Profile() {
const { me } = useAccount();
return <div>Hello, {me?.name}</div>;
}
```
</CodeGroup>
This should work well for cases like rendering publicly-readable information, since the worker account will be able to load them.
### SSR Support (Experimental)
In the future, it will be possible to use trusted auth methods (such as Clerk, Auth0, etc.) that let you act as the same Jazz user both on the client and on the server, letting you use SSR even for data private to that user.
For server-side rendering, Jazz offers experimental approaches:
### SSR + client-side (experimental)
- Pure SSR
- Hybrid SSR + Client Hydration
You can combine the two approaches by creating
#### Pure SSR
1. A pure "rendering" component that renders an already-loaded CoValue (in JSON-ified form)
Use Jazz in server components by directly loading data with `CoValue.load()`.
TODO: code example
{/*
<CodeGroup>
```tsx twoslash
// @errors: 18047
// @filename: schema.ts
import { co, CoList, CoMap } from "jazz-tools";
2. A "hydrating" component (with `"use client"`) that
export class MyItem extends CoMap {
title = co.string;
}
- expects a pre-loaded CoValue as a prop (in JSON-ified form)
- uses one of the client-side Jazz hooks (such as `useAccount` or `useCoState`) to subscribe to that same CoValue
- passing the client-side subscribed state to the "rendering" component, with the pre-loaded CoValue as a fallback until the client receives the first subscribed state
export class MyCollection extends CoList.Of(co.ref(MyItem)) {}
// @filename: PublicData.tsx
import * as React from "react";
import { ID } from "jazz-tools";
const collectionID = "co_z123" as ID<MyCollection>;
// ---cut---
// Server Component (no "use client" directive)
import { MyCollection, MyItem } from "./schema";
TODO: code example
export default async function PublicData() {
// Load data directly in the server component
const items = await MyCollection.load(collectionID);
if (!items) {
return <div>Loading...</div>;
}
return (
<ul>
{items.map(item => (
item ? <li key={item.id}>{item.title}</li> : null
))}
</ul>
);
}
```
</CodeGroup>
*/}
3. A "pre-loading" Server Component that
This works well for public data accessible to the server account.
- pre-loads the CoValue by awaiting it's `load(...)` method (as described above)
- renders the "hydrating" component, passing the pre-loaded CoValue as a prop
#### Hybrid SSR + Client Hydration
For more complex cases, you can pre-render on the server and hydrate on the client:
1. Create a shared rendering component.
{/*
<CodeGroup>
```tsx twoslash
// @filename: schema.ts
import { co, CoList, CoMap } from "jazz-tools";
export class MyItem extends CoMap {
title = co.string;
}
// @filename: ItemList.tsx
import * as React from "react";
import { MyItem } from "./schema";
// ---cut---
// ItemList.tsx - works in both server and client contexts
export function ItemList({ items }: { items: MyItem[] }) {
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.title}</li>
))}
</ul>
);
}
```
</CodeGroup>
*/}
2. Create a client hydration component.
{/*
<CodeGroup>
```tsx twoslash
// @filename: schema.ts
import { co, CoList, CoMap } from "jazz-tools";
export class MyItem extends CoMap {
title = co.string;
}
export class MyCollection extends CoList.Of(co.ref(MyItem)) {}
// @filename: ItemList.tsx
import * as React from "react";
import { MyItem } from "./schema";
export function ItemList({ items }: { items: MyItem[] }) {
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.title}</li>
))}
</ul>
);
}
// @filename: ItemListHydrator.tsx
// ItemListHydrator.tsx
import * as React from "react";
import { useCoState } from "jazz-react";
import { ID } from "jazz-tools";
const myCollectionID = "co_z123" as ID<MyCollection>;
// ---cut---
"use client"
import { MyCollection, MyItem } from "./schema";
import { ItemList } from "./ItemList";
export function ItemListHydrator({ initialItems }: { initialItems: MyItem[] }) {
// Hydrate with real-time data once client loads
const myCollection = useCoState(MyCollection, myCollectionID);
// Filter out nulls for type safety
const items = Array.from(myCollection?.values() || []).filter(
(item): item is MyItem => !!item
);
// Use server data until client data is available
const displayItems = items || initialItems;
return <ItemList items={displayItems} />;
}
```
</CodeGroup>
*/}
3. Create a server component that pre-loads data.
{/*
<CodeGroup>
```tsx twoslash
// @filename: schema.ts
import { co, CoList, CoMap } from "jazz-tools";
export class MyItem extends CoMap {
title = co.string;
}
export class MyCollection extends CoList.Of(co.ref(MyItem)) {}
// @filename: ItemList.tsx
import * as React from "react";
import { MyItem } from "./schema";
export function ItemList({ items }: { items: MyItem[] }) {
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.title}</li>
))}
</ul>
);
}
// @filename: ItemListHydrator.tsx
// ItemListHydrator.tsx
import * as React from "react";
import { useCoState } from "jazz-react";
import { ID } from "jazz-tools";
const myCollectionID = "co_z123" as ID<MyCollection>;
// ---cut---
"use client"
import { MyCollection, MyItem } from "./schema";
import { ItemList } from "./ItemList";
export function ItemListHydrator({ initialItems }: { initialItems: MyItem[] }) {
// Hydrate with real-time data once client loads
const myCollection = useCoState(MyCollection, myCollectionID);
// Filter out nulls for type safety
const items = Array.from(myCollection?.values() || []).filter(
(item): item is MyItem => !!item
);
// Use server data until client data is available
const displayItems = items || initialItems;
return <ItemList items={displayItems} />;
}
// @filename: ServerItemPage.tsx
import * as React from 'react';
import { ID } from "jazz-tools";
import { MyCollection, MyItem } from "./schema";
import { ItemListHydrator } from "./ItemListHydrator";
const myCollectionID = "co_z123" as ID<MyCollection>;
// ---cut---
// ServerItemPage.tsx
export default async function ServerItemPage() {
// Pre-load data on the server
const initialItems = await MyCollection.load(myCollectionID);
// Filter out nulls for type safety
const items = Array.from(initialItems?.values() || []).filter(
(item): item is MyItem => !!item
);
// Pass to client hydrator
return <ItemListHydrator initialItems={items} />;
}
```
</CodeGroup>
*/}
This approach gives you the best of both worlds: fast initial loading with server rendering, plus real-time updates on the client.
## Further Reading
- [Schemas](/docs/schemas/covalues) - Learn about defining your data model
- [Provider Configuration](/docs/project-setup/providers) - Learn about other configuration options for Providers
- [Authentication](/docs/authentication/overview) - Set up user authentication
- [Sync and Storage](/docs/sync-and-storage) - Learn about data persistence
TODO: code example

View File

@@ -12,7 +12,7 @@ As their name suggests, CoValues are inherently collaborative, meaning **multipl
- CoValues keep their full edit histories, from which they derive their "current state".
- The fact that this happens in an eventually-consistent way makes them [CRDTs](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type).
- Having the full history also means that you often don't need explicit timestamps and author info - you get this for free as part of a CoValue's [edit metadata](/docs/using-covalues/history).
- Having the full history also means that you often don't need explicit timestamps and author info - you get this for free as part of a CoValue's [edit metadata](/docs/using-covalues/metadata).
CoValues model JSON with CoMaps and CoLists, but also offer CoFeeds for simple per-user value feeds, and let you represent binary data with FileStreams.
@@ -31,14 +31,11 @@ Even before you know the details of how your app will work, you'll probably know
Jazz makes it quick to declare schemas, since they are simple TypeScript classes:
<CodeGroup>
```ts twoslash
import { co, CoMap, CoList } from "jazz-tools";
class ListOfTasks extends CoList.Of(co.string) {}
// ---cut---
// schema.ts
{/* prettier-ignore */}
```ts
export class TodoProject extends CoMap {
title = co.string;
tasks = co.ref(ListOfTasks);
title = co.string;
tasks = co.ref(ListOfTasks);
}
```
</CodeGroup>
@@ -48,28 +45,16 @@ Here you can see how we extend a CoValue type and use `co` for declaring (collab
Classes might look old-fashioned, but Jazz makes use of them being both types and values in TypeScript, letting you refer to either with a single definition and import.
<CodeGroup>
```ts twoslash
// @filename: schema.ts
import { co, CoMap, CoList } from "jazz-tools";
export class ListOfTasks extends CoList.Of(co.string) {}
export class TodoProject extends CoMap {
title = co.string;
tasks = co.ref(ListOfTasks);
}
// @filename: app.ts
import { Group } from "jazz-tools";
// ---cut---
// app.ts
{/* prettier-ignore */}
```ts
import { TodoProject, ListOfTasks } from "./schema";
const project: TodoProject = TodoProject.create(
{
title: "New Project",
tasks: ListOfTasks.create([], Group.create()),
},
Group.create()
{
title: "New Project",
tasks: ListOfTasks.create([], Group.create()),
},
Group.create()
);
```
</CodeGroup>
@@ -78,18 +63,17 @@ const project: TodoProject = TodoProject.create(
### `CoMap` (declaration)
CoMaps are the most commonly used type of CoValue. They are the equivalent of JSON objects (Collaborative editing follows a last-write-wins strategy per-key).
CoMaps are the most commonly used type of CoValue. They are the equivalent of JSON objects. (Collaborative editing follows a last-write-wins strategy per-key.)
You can either declare struct-like CoMaps:
<CodeGroup>
```ts twoslash
// schema.ts
import { co, CoMap } from "jazz-tools";
// ---cut---
export class Task extends CoMap {
title = co.string;
completed = co.boolean;
{/* prettier-ignore */}
```ts
class Person extends CoMap {
name = co.string;
age = co.number;
pet = co.optional.ref(Pet);
}
```
</CodeGroup>
@@ -97,13 +81,8 @@ export class Task extends CoMap {
Or record-like CoMaps (key-value pairs, where keys are always `string`):
<CodeGroup>
```ts twoslash
import { co, CoMap } from "jazz-tools";
class Fruit extends CoMap {
name = co.string;
color = co.string;
}
// ---cut---
{/* prettier-ignore */}
```ts
class ColorToHex extends CoMap.Record(co.string) {}
class ColorToFruit extends CoMap.Record(co.ref(Fruit)) {}
@@ -111,10 +90,10 @@ class ColorToFruit extends CoMap.Record(co.ref(Fruit)) {}
</CodeGroup>
See the corresponding sections for [creating](/docs/using-covalues/comaps#creating-comaps),
See the corresponding sections for [creating](/docs/using-covalues/creation#comap-creation),
[subscribing/loading](/docs/using-covalues/subscription-and-loading),
[reading from](/docs/using-covalues/comaps#reading-from-comaps) and
[updating](/docs/using-covalues/comaps#updating-comaps) CoMaps.
[reading from](/docs/using-covalues/reading#comap-reading) and
[writing to](/docs/using-covalues/writing#comap-writing) CoMaps.
### `CoList` (declaration)
@@ -123,48 +102,38 @@ CoLists are ordered lists and are the equivalent of JSON arrays. (They support c
You define them by specifying the type of the items they contain:
<CodeGroup>
```ts twoslash
import { co, CoList, CoMap } from "jazz-tools";
class Task extends CoMap {
title = co.string;
completed = co.boolean;
}
// ---cut---
{/* prettier-ignore */}
```ts
class ListOfColors extends CoList.Of(co.string) {}
class ListOfTasks extends CoList.Of(co.ref(Task)) {}
```
</CodeGroup>
See the corresponding sections for [creating](/docs/using-covalues/colists#creating-colists),
See the corresponding sections for [creating](/docs/using-covalues/creation#colist-creation),
[subscribing/loading](/docs/using-covalues/subscription-and-loading),
[reading from](/docs/using-covalues/colists#reading-from-colists) and
[updating](/docs/using-covalues/colists#updating-colists) CoLists.
[reading from](/docs/using-covalues/reading#colist-reading) and
[writing to](/docs/using-covalues/writing#colist-writing) CoLists.
### `CoFeed` (declaration)
CoFeeds are a special CoValue type that represent a feed of values for a set of users/sessions (Each session of a user gets its own append-only feed).
CoFeeds are a special CoValue type that represent a feed of values for a set of users / sessions. (Each session of a user gets its own append-only feed.)
They allow easy access of the latest or all items belonging to a user or their sessions. This makes them particularly useful for user presence, reactions, notifications, etc.
You define them by specifying the type of feed item:
<CodeGroup>
```ts twoslash
import { co, CoFeed, CoMap } from "jazz-tools";
class Task extends CoMap {
title = co.string;
completed = co.boolean;
}
// ---cut---
{/* prettier-ignore */}
```ts
class FeedOfTasks extends CoFeed.Of(co.ref(Task)) {}
```
</CodeGroup>
See the corresponding sections for [creating](/docs/using-covalues/cofeeds#creating-cofeeds),
See the corresponding sections for [creating](/docs/using-covalues/creation#cofeed-creation),
[subscribing/loading](/docs/using-covalues/subscription-and-loading),
[reading from](/docs/using-covalues/cofeeds#reading-from-cofeeds) and
[writing to](/docs/using-covalues/cofeeds#writing-to-cofeeds) CoFeeds.
[reading from](/docs/using-covalues/reading#cofeed-reading) and
[writing to](/docs/using-covalues/writing#cofeed-writing) CoFeeds.
### `FileStream` (declaration)
@@ -175,22 +144,21 @@ They allow you to upload and reference files, images, etc.
You typically don't need to declare or extend them yourself, you simply refer to the built-in `FileStream` from another CoValue:
<CodeGroup>
```ts twoslash
import { co, CoMap } from "jazz-tools";
// ---cut---
{/* prettier-ignore */}
```ts
import { FileStream } from "jazz-tools";
class Document extends CoMap {
title = co.string;
file = co.ref(FileStream);
class UserProfile extends CoMap {
name = co.string;
avatar = co.ref(FileStream);
}
```
</CodeGroup>
See the corresponding sections for [creating](/docs/using-covalues/filestreams#creating-filestreams),
See the corresponding sections for [creating](/docs/using-covalues/creation#filestream-creation),
[subscribing/loading](/docs/using-covalues/subscription-and-loading),
[reading from](/docs/using-covalues/filestreams#reading-from-filestreams) and
[writing to](/docs/using-covalues/filestreams#writing-to-filestreams) FileStreams.
[reading from](/docs/using-covalues/reading#filestream-reading) and
[writing to](/docs/using-covalues/writing#filestream-writing) FileStreams.
### `SchemaUnion` (declaration)
@@ -199,9 +167,8 @@ SchemaUnion is a helper type that allows you to load and refer to multiple subcl
You declare them with a base class type and discriminating lambda, in which you have access to the `RawCoMap`, on which you can call `get` with the field name to get the discriminating value.
<CodeGroup>
```ts twoslash
import { co } from "jazz-tools";
// ---cut---
{/* prettier-ignore */}
```ts
import { SchemaUnion, CoMap } from "jazz-tools";
class BaseWidget extends CoMap {
@@ -229,9 +196,9 @@ const WidgetUnion = SchemaUnion.Of<BaseWidget>((raw) => {
```
</CodeGroup>
See the corresponding sections for [creating](/docs/using-covalues/schemaunions#creating-schemaunions),
See the corresponding sections for [creating](/docs/using-covalues/creation#schemaunion-creation),
[subscribing/loading](/docs/using-covalues/subscription-and-loading) and
[narrowing](/docs/using-covalues/schemaunions#narrowing) SchemaUnions.
[narrowing](/docs/using-covalues/reading#schemaunion-narrowing) SchemaUnions.
## CoValue field/item types
@@ -242,11 +209,12 @@ Now that we've seen the different types of CoValues, let's see more precisely ho
You can declare primitive field types using the `co` declarer:
<CodeGroup>
```ts twoslash
import { co, CoMap, CoList } from "jazz-tools";
{/* prettier-ignore */}
```ts
import { co } from "jazz-tools";
export class Person extends CoMap {
title = co.string;
title = co.string;
}
export class ListOfColors extends CoList.Of(co.string) {}
@@ -256,25 +224,23 @@ export class ListOfColors extends CoList.Of(co.string) {}
Here's a quick overview of the primitive types you can use:
<CodeGroup>
```ts twoslash
import { co } from "jazz-tools";
// ---cut---
co.string; // For simple strings
co.number; // For numbers
co.boolean; // For booleans
co.null; // For null
co.Date; // For dates
co.literal("waiting", "ready"); // For enums
{/* prettier-ignore */}
```ts
co.string;
co.number;
co.boolean;
co.null;
co.Date;
co.literal("waiting", "ready");
```
</CodeGroup>
Finally, for more complex JSON data, that you *don't want to be collaborative internally* (but only ever update as a whole), you can use `co.json<T>()`:
<CodeGroup>
```ts twoslash
import { co } from "jazz-tools";
// ---cut---
co.json<{ name: string }>(); // For JSON objects
{/* prettier-ignore */}
```ts
co.json<{ name: string }>();
```
</CodeGroup>
@@ -291,18 +257,13 @@ The important caveat here is that **a referenced CoValue might or might not be l
In Schemas, you declare Refs using the `co.ref<T>()` declarer:
<CodeGroup>
```ts twoslash
import { co, CoMap, CoList } from "jazz-tools";
class Person extends CoMap {
name = co.string;
}
// ---cut---
// schema.ts
class ListOfPeople extends CoList.Of(co.ref(Person)) {}
{/* prettier-ignore */}
```ts
class Company extends CoMap {
members = co.ref(ListOfPeople);
members = co.ref(ListOfPeople);
}
class ListOfPeople extends CoList.Of(co.ref(Person)) {}
```
</CodeGroup>
@@ -311,14 +272,10 @@ class Company extends CoMap {
⚠️ If you want to make a referenced CoValue field optional, you *have to* use `co.optional.ref<T>()`: ⚠️
<CodeGroup>
```ts twoslash
import { co, CoMap } from "jazz-tools";
class Pet extends CoMap {
name = co.string;
}
// ---cut---
{/* prettier-ignore */}
```ts
class Person extends CoMap {
pet = co.optional.ref(Pet);
pet = co.optional.ref(Pet);
}
```
</CodeGroup>
@@ -328,25 +285,20 @@ class Person extends CoMap {
Since CoValue schemas are based on classes, you can easily add computed fields and methods:
<CodeGroup>
```ts twoslash
import { co, CoMap } from "jazz-tools";
function differenceInYears(date1: Date, date2: Date) {
const diffTime = Math.abs(date1.getTime() - date2.getTime());
return Math.ceil(diffTime / (1000 * 60 * 60 * 24 * 365.25));
}
// ---cut---
{/* prettier-ignore */}
```ts
class Person extends CoMap {
firstName = co.string;
lastName = co.string;
dateOfBirth = co.Date;
firstName = co.string;
lastName = co.string;
dateOfBirth = co.Date;
get name() {
return `${this.firstName} ${this.lastName}`;
}
get name() {
return `${this.firstName} ${this.lastName}`;
}
ageAsOf(date: Date) {
return differenceInYears(date, this.dateOfBirth);
}
ageAsOf(date: Date) {
return differenceInYears(date, this.dateOfBirth);
}
}
```
</CodeGroup>

View File

@@ -1,437 +0,0 @@
import { CodeGroup, ContentByFramework } from "@/components/forMdx";
export const metadata = { title: "CoTexts" };
# CoTexts
Jazz provides two CoValue types for collaborative text editing, collectively referred to as "CoText" values:
- **CoPlainText** for simple text editing without formatting
- **CoRichText** for rich text with HTML-based formatting (extends CoPlainText)
Both types enable real-time collaborative editing of text content while maintaining consistency across multiple users.
**Note:** If you're looking for a quick way to add rich text editing to your app, check out [jazz-richtext-prosemirror](#using-rich-text-with-prosemirror).
<CodeGroup>
```ts twoslash
import { CoPlainText } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
// ---cut---
const note = CoPlainText.create("Meeting notes", { owner: me });
// Update the text
note.applyDiff("Meeting notes for Tuesday");
console.log(note.toString()); // "Meeting notes for Tuesday"
```
</CodeGroup>
For a full example of CoTexts in action, see [our Richtext example app](https://github.com/garden-co/jazz/tree/main/examples/richtext), which shows plain text and rich text editing.
## CoPlainText vs co.string
While `co.string` is perfect for simple text fields, `CoPlainText` is the right choice when you need:
- Multiple users editing the same text simultaneously
- Fine-grained control over text edits (inserting, deleting at specific positions)
- Character-by-character collaboration
- Efficient merging of concurrent changes
Both support real-time updates, but `CoPlainText` provides specialized tools for collaborative editing scenarios.
## Creating CoText Values
CoText values are typically used as fields in your schemas:
<CodeGroup>
```ts twoslash
import { CoMap, CoPlainText, CoRichText, co } from "jazz-tools";
// ---cut---
class Profile extends CoMap {
name = co.string;
bio = co.ref(CoPlainText); // Plain text field
description = co.ref(CoRichText); // Rich text with formatting
}
```
</CodeGroup>
Create a CoText value with a simple string:
<CodeGroup>
```ts twoslash
import { CoPlainText, CoRichText, Account } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
// ---cut---
// Create plaintext with default ownership (current user)
const note = CoPlainText.create("Meeting notes", { owner: me });
// Create rich text with HTML content
const document = CoRichText.create("<p>Project <strong>overview</strong></p>",
{ owner: me }
);
```
</CodeGroup>
### Ownership
Like other CoValues, you can specify ownership when creating CoTexts.
<CodeGroup>
```ts twoslash
import { CoPlainText, Group, Account } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const colleagueAccount = await createJazzTestAccount();
// ---cut---
// Create with shared ownership
const teamGroup = Group.create();
teamGroup.addMember(colleagueAccount, "writer");
const teamNote = CoPlainText.create("Team updates", { owner: teamGroup });
```
</CodeGroup>
See [Groups as permission scopes](/docs/groups/intro) for more information on how to use groups to control access to CoText values.
## Reading Text
CoText values work like JavaScript strings:
<CodeGroup>
```ts twoslash
import { CoPlainText, Account } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const note = CoPlainText.create("Meeting notes", { owner: me });
// ---cut---
// Get the text content
console.log(note.toString()); // "Meeting notes"
// Check the text length
console.log(note.length); // 14
```
</CodeGroup>
## Making Edits
Insert and delete text with intuitive methods:
<CodeGroup>
```ts twoslash
import { CoPlainText, Account } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const note = CoPlainText.create("Meeting notes", { owner: me });
// ---cut---
// Insert text at a specific position
note.insertBefore(8, "weekly "); // "Meeting weekly notes"
// Insert after a position
note.insertAfter(21, " for Monday"); // "Meeting weekly notes for Monday"
// Delete a range of text
note.deleteRange({ from: 8, to: 15 }); // "Meeting notes for Monday"
// Apply a diff to update the entire text
note.applyDiff("Team meeting notes for Tuesday");
```
</CodeGroup>
### Applying Diffs
Use `applyDiff` to efficiently update text with minimal changes:
<CodeGroup>
```ts twoslash
import { CoPlainText, Account } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
// ---cut---
// Original text: "Team status update"
const minutes = CoPlainText.create("Team status update", { owner: me });
// Replace the entire text with a new version
minutes.applyDiff("Weekly team status update for Project X");
// Make partial changes
let text = minutes.toString();
text = text.replace("Weekly", "Monday");
minutes.applyDiff(text); // Efficiently updates only what changed
```
</CodeGroup>
Perfect for handling user input in form controls:
<ContentByFramework framework="react">
<CodeGroup>
```tsx twoslash
import { CoPlainText, Account } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
import React, { useState } from "react";
const me = await createJazzTestAccount();
// ---cut---
function TextEditor() {
const [note, setNote] = useState(CoPlainText.create("", { owner: me }));
return (
<textarea
value={note.toString()}
onChange={(e) => {
// Efficiently update only what the user changed
note.applyDiff(e.target.value);
}}
/>
);
}
```
</CodeGroup>
</ContentByFramework>
<ContentByFramework framework="vanilla">
<CodeGroup>
```ts twoslash
import { CoPlainText, Account } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
// ---cut---
const note = CoPlainText.create("", { owner: me });
// Create and set up the textarea
const textarea = document.createElement('textarea');
textarea.value = note.toString();
// Add event listener for changes
textarea.addEventListener('input', (e: Event) => {
const target = e.target as HTMLTextAreaElement;
// Efficiently update only what the user changed
note.applyDiff(target.value);
});
// Add the textarea to the document
document.body.appendChild(textarea);
```
</CodeGroup>
</ContentByFramework>
<ContentByFramework framework="vue">
<CodeGroup>
```vue twoslash
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { CoPlainText } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const note = ref(null);
const textContent = ref("");
onMounted(async () => {
const me = await createJazzTestAccount();
note.value = CoPlainText.create("", { owner: me });
textContent.value = note.value.toString();
});
function updateText(e) {
if (note.value) {
note.value.applyDiff(e.target.value);
textContent.value = note.value.toString();
}
}
</script>
<template>
<textarea
:value="textContent"
@input="updateText"
/>
</template>
```
</CodeGroup>
</ContentByFramework>
<ContentByFramework framework="svelte">
<CodeGroup>
```svelte twoslash
<script lang="ts">
import { CoPlainText, Account } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const note = CoPlainText.create("", { owner: me });
</script>
<textarea
value={note.toString()}
oninput={e => note.applyDiff(e.target.value)}
/>
```
</CodeGroup>
</ContentByFramework>
## Using Rich Text with ProseMirror
Jazz provides a dedicated plugin for integrating CoRichText with the popular ProseMirror editor. This plugin, [`jazz-richtext-prosemirror`](https://www.npmjs.com/package/jazz-richtext-prosemirror), enables bidirectional synchronization between your CoRichText instances and ProseMirror editors.
### ProseMirror Plugin Features
- **Bidirectional Sync**: Changes in the editor automatically update the CoRichText and vice versa
- **Real-time Collaboration**: Multiple users can edit the same document simultaneously
- **HTML Conversion**: Automatically converts between HTML (used by CoRichText) and ProseMirror's document model
### Installation
<CodeGroup>
```bash
pnpm add jazz-richtext-prosemirror \
prosemirror-view \
prosemirror-state \
prosemirror-schema-basic
```
</CodeGroup>
### Integration
<ContentByFramework framework="react-native">
We don't currently have a React Native-specific example, but you need help you can [request one](https://github.com/garden-co/jazz/issues/new), or ask on [Discord](https://discord.gg/utDMjHYg42).
</ContentByFramework>
<ContentByFramework framework="react-native-expo">
We don't currently have a React Native Expo-specific example, but you need help please [request one](https://github.com/garden-co/jazz/issues/new), or ask on [Discord](https://discord.gg/utDMjHYg42).
</ContentByFramework>
<ContentByFramework framework={["react", "react-native", "react-native-expo"]}>
For use with React:
<CodeGroup>
```tsx twoslash
class JazzProfile extends Profile {
bio = co.ref(CoRichText);
}
class JazzAccount extends Account {
profile = co.ref(JazzProfile);
}
declare module "jazz-react" {
interface Register {
Account: JazzAccount;
}
}
import { useAccount, useCoState } from "jazz-react";
import { CoRichText, Account, Profile, co } from "jazz-tools";
import React, { useEffect, useRef } from "react";
// ---cut---
// RichTextEditor.tsx
import { createJazzPlugin } from "jazz-richtext-prosemirror";
import { exampleSetup } from "prosemirror-example-setup";
import { schema } from "prosemirror-schema-basic";
import { EditorState } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
function RichTextEditor() {
const { me } = useAccount({ resolve: { profile: true } });
const editorRef = useRef<HTMLDivElement>(null);
const viewRef = useRef<EditorView | null>(null);
useEffect(() => {
if (!me?.profile.bio || !editorRef.current) return;
// Create the Jazz plugin for ProseMirror
// Providing a CoRichText instance to the plugin to automatically sync changes
const jazzPlugin = createJazzPlugin(me.profile.bio); // [!code ++]
// Set up ProseMirror with the Jazz plugin
if (!viewRef.current) {
viewRef.current = new EditorView(editorRef.current, {
state: EditorState.create({
schema,
plugins: [
...exampleSetup({ schema }),
jazzPlugin, // [!code ++]
],
}),
});
}
return () => {
if (viewRef.current) {
viewRef.current.destroy();
viewRef.current = null;
}
};
}, [me?.profile.bio?.id]);
if (!me) return null;
return (
<div className="border rounded">
<div ref={editorRef} className="p-2" />
</div>
);
}
```
</CodeGroup>
</ContentByFramework>
<ContentByFramework framework="svelte">
We don't currently have a Svelte-specific example, but you need help you can [request one](https://github.com/garden-co/jazz/issues/new), or ask on [Discord](https://discord.gg/utDMjHYg42).
</ContentByFramework>
<ContentByFramework framework="vue">
We don't currently have a Vue-specific example, but you need help you can [request one](https://github.com/garden-co/jazz/issues/new), or ask on [Discord](https://discord.gg/utDMjHYg42).
</ContentByFramework>
<ContentByFramework framework={["vanilla", "svelte", "vue", "react-native", "react-native-expo"]}>
For use without a framework:
<CodeGroup>
```js twoslash
import { CoRichText } from "jazz-tools";
import { createJazzPlugin } from "jazz-richtext-prosemirror";
import { exampleSetup } from "prosemirror-example-setup";
import { schema } from "prosemirror-schema-basic";
import { EditorState } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
function setupRichTextEditor(coRichText, container) {
// Create the Jazz plugin for ProseMirror
// Providing a CoRichText instance to the plugin to automatically sync changes
const jazzPlugin = createJazzPlugin(coRichText); // [!code ++]
// Set up ProseMirror with Jazz plugin
const view = new EditorView(container, {
state: EditorState.create({
schema,
plugins: [
...exampleSetup({ schema }),
jazzPlugin, // [!code ++]
],
}),
});
// Return cleanup function
return () => {
view.destroy();
};
}
// Usage
const document = CoRichText.create("<p>Initial content</p>", { owner: me });
const editorContainer = document.getElementById("editor");
const cleanup = setupRichTextEditor(document, editorContainer);
// Later when done with the editor
cleanup();
```
</CodeGroup>
</ContentByFramework>

View File

@@ -18,8 +18,7 @@ FileStreams provide automatic chunking when using the `createFromBlob` method, t
In your schema, reference FileStreams like any other CoValue:
<CodeGroup>
```ts twoslash
// schema.ts
```ts
import { CoMap, FileStream, co } from "jazz-tools";
class Document extends CoMap {
@@ -38,32 +37,25 @@ There are two main ways to create FileStreams: creating empty ones for manual da
For files from input elements or drag-and-drop interfaces, use `createFromBlob`:
<CodeGroup>
```ts twoslash
import { FileStream, Group } from "jazz-tools";
const myGroup = Group.create();
const progressBar: HTMLElement = document.querySelector('.progress-bar')!;
// ---cut---
```ts
// From a file input
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', async () => {
const file = fileInput.files?.[0];
if (!file) return;
// Create FileStream from user-selected file
const fileStream = await FileStream.createFromBlob(file, { owner: myGroup });
// Or with progress tracking for better UX
const fileWithProgress = await FileStream.createFromBlob(file, {
onProgress: (progress) => {
// progress is a value between 0 and 1
const percent = Math.round(progress * 100);
console.log(`Upload progress: ${percent}%`);
progressBar.style.width = `${percent}%`;
},
owner: myGroup
});
const file = fileInput.files[0];
if (file) {
// Create FileStream from user-selected file
const fileStream = await FileStream.createFromBlob(file);
// Or with progress tracking for better UX
const fileWithProgress = await FileStream.createFromBlob(file, {
onProgress: (progress) => {
// progress is a value between 0 and 1
const percent = Math.round(progress * 100);
console.log(`Upload progress: ${percent}%`);
progressBar.style.width = `${percent}%`;
}
});
}
});
```
</CodeGroup>
@@ -73,12 +65,11 @@ fileInput.addEventListener('change', async () => {
Create an empty FileStream when you want to manually [add binary data in chunks](/docs/using-covalues/filestreams#writing-to-filestreams):
<CodeGroup>
```ts twoslash
import { Group, FileStream } from "jazz-tools";
const myGroup = Group.create();
// ---cut---
```ts
import { FileStream } from "jazz-tools";
// Create a new empty FileStream
const fileStream = FileStream.create({ owner: myGroup } );
const fileStream = FileStream.create();
```
</CodeGroup>
@@ -114,10 +105,7 @@ const teamFileStream = FileStream.create({ owner: teamGroup });
To access the raw binary data and metadata:
<CodeGroup>
```ts twoslash
import { FileStream } from "jazz-tools";
const fileStream = FileStream.create();
// ---cut---
```ts
// Get all chunks and metadata
const fileData = fileStream.getChunks();
@@ -139,10 +127,7 @@ if (fileData) {
By default, `getChunks()` only returns data for completely synced `FileStream`s. To start using chunks from a `FileStream` that's currently still being synced use the `allowUnfinished` option:
<CodeGroup>
```ts twoslash
import { FileStream } from "jazz-tools";
const fileStream = FileStream.create();
// ---cut---
```ts
// Get data even if the stream isn't complete
const partialData = fileStream.getChunks({ allowUnfinished: true });
```
@@ -153,16 +138,10 @@ const partialData = fileStream.getChunks({ allowUnfinished: true });
For easier integration with web APIs, convert to a `Blob`:
<CodeGroup>
```ts twoslash
import { FileStream } from "jazz-tools";
const fileStream = FileStream.create();
// ---cut---
```ts
// Convert to a Blob
const blob = fileStream.toBlob();
// Get the filename from the metadata
const filename = fileStream.getChunks()?.fileName;
if (blob) {
// Use with URL.createObjectURL
const url = URL.createObjectURL(blob);
@@ -170,7 +149,7 @@ if (blob) {
// Create a download link
const link = document.createElement('a');
link.href = url;
link.download = filename || 'document.pdf';
link.download = fileData?.fileName || 'document.pdf';
link.click();
// Clean up when done
@@ -184,10 +163,7 @@ if (blob) {
You can directly load a `FileStream` as a `Blob` when you only have its ID:
<CodeGroup>
```ts twoslash
import { FileStream, type ID } from "jazz-tools";
const fileStreamId = "co_z123" as ID<FileStream>;
// ---cut---
```ts
// Load directly as a Blob when you have an ID
const blob = await FileStream.loadAsBlob(fileStreamId);
@@ -204,10 +180,7 @@ const partialBlob = await FileStream.loadAsBlob(fileStreamId, {
Check if a `FileStream` is fully synced:
<CodeGroup>
```ts twoslash
import { FileStream } from "jazz-tools";
const fileStream = FileStream.create();
// ---cut---
```ts
if (fileStream.isBinaryStreamEnded()) {
console.log('File is completely synced');
} else {
@@ -233,12 +206,9 @@ When creating a `FileStream` manually (not using `createFromBlob`), you need to
Begin by providing metadata about the file:
<CodeGroup>
```ts twoslash
import { FileStream, Group } from "jazz-tools";
const myGroup = Group.create();
// ---cut---
```ts
// Create an empty FileStream
const fileStream = FileStream.create({ owner: myGroup });
const fileStream = FileStream.create();
// Initialize with metadata
fileStream.start({
@@ -254,15 +224,9 @@ fileStream.start({
Add binary data in chunks - this helps with large files and progress tracking:
<CodeGroup>
```ts twoslash
import { FileStream } from "jazz-tools";
const fileStream = FileStream.create();
const file = [0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64]; // "Hello World" in ASCII
const bytes = new Uint8Array(file);
const arrayBuffer = bytes.buffer;
// ---cut---
const data = new Uint8Array(arrayBuffer);
```ts
// Create a sample Uint8Array (in real apps, this would be file data)
const data = new Uint8Array([...]);
// For large files, break into chunks (e.g., 100KB each)
const chunkSize = 1024 * 100;
@@ -285,10 +249,7 @@ for (let i = 0; i < data.length; i += chunkSize) {
Once all chunks are pushed, mark the `FileStream` as complete:
<CodeGroup>
```ts twoslash
import { FileStream } from "jazz-tools";
const fileStream = FileStream.create();
// ---cut---
```ts
// Finalize the upload
fileStream.end();
@@ -305,12 +266,9 @@ Like other CoValues, you can subscribe to `FileStream`s to get notified of chang
Load a `FileStream` when you have its ID:
<CodeGroup>
```ts twoslash
import { FileStream, type ID } from "jazz-tools";
const fileStreamId = "co_z123" as ID<FileStream>;
// ---cut---
```ts
// Load a FileStream by ID
const fileStream = await FileStream.load(fileStreamId);
const fileStream = await FileStream.load(fileStreamId, []);
if (fileStream) {
console.log('FileStream loaded successfully');
@@ -329,20 +287,16 @@ if (fileStream) {
Subscribe to a `FileStream` to be notified when chunks are added or when the upload is complete:
<CodeGroup>
```ts twoslash
import { FileStream, type ID } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const fileStreamId = "co_z123" as ID<FileStream>;
// ---cut---
```ts
// Subscribe to a FileStream by ID
const unsubscribe = FileStream.subscribe(fileStreamId, (fileStream: FileStream) => {
const unsubscribe = FileStream.subscribe(fileStreamId, [], (fileStream) => {
// Called whenever the FileStream changes
console.log('FileStream updated');
// Get current status
const chunks = fileStream.getChunks({ allowUnfinished: true });
if (chunks) {
const uploadedBytes = chunks.chunks.reduce((sum: number, chunk: Uint8Array) => sum + chunk.length, 0);
const uploadedBytes = chunks.chunks.reduce((sum, chunk) => sum + chunk.length, 0);
const totalBytes = chunks.totalSizeBytes || 1;
const progress = Math.min(100, Math.round(uploadedBytes * 100 / totalBytes));
@@ -366,10 +320,7 @@ const unsubscribe = FileStream.subscribe(fileStreamId, (fileStream: FileStream)
If you need to wait for a `FileStream` to be fully synchronized across devices:
<CodeGroup>
```ts twoslash
import { FileStream } from "jazz-tools";
const fileStream = FileStream.create();
// ---cut---
```ts
// Wait for the FileStream to be fully synced
await fileStream.waitForSync({
timeout: 5000 // Optional timeout in ms

View File

@@ -26,8 +26,4 @@ export const features = {
clerk: "Clerk auth",
inviteLink: "Invite link",
coFeed: "CoFeed",
coRichText: "CoRichText",
coPlainText: "CoPlainText",
serverWorker: "Server worker",
inbox: "Inbox",
};

View File

@@ -1,7 +1,7 @@
import DocsLayout from "@/components/docs/DocsLayout";
import { DocNav } from "@/components/docs/DocsNav";
import { Prose } from "@garden-co/design-system/src/components/molecules/Prose";
import { Toc } from "@stefanprobst/rehype-extract-toc";
import { Prose } from "@garden-co/design-system/src/components/molecules/Prose";
export async function getMdxSource(framework: string, slugPath?: string) {
// Try to import the framework-specific file first
@@ -42,7 +42,7 @@ export async function getDocMetadata(framework: string, slug?: string[]) {
function DocProse({ children }: { children: React.ReactNode }) {
return (
<Prose className="overflow-hidden pb-8 pt-[calc(61px+2rem)] md:pt-8 md:max-w-3xl mx-auto">
<Prose className="overflow-x-hidden lg:overflow-x-visible lg:flex-1 pb-8 pt-[calc(61px+2rem)] md:pt-8 md:max-w-3xl mx-auto">
{children}
</Prose>
);
@@ -55,8 +55,23 @@ export async function DocPage({
framework: string;
slug?: string[];
}) {
const slugPath = slug?.join("/");
try {
const { Content, tocItems } = await getMdxWithToc(framework, slug);
const mdxSource = await getMdxSource(framework, slugPath);
const {
default: Content,
tableOfContents = [],
headingsFrameworkVisibility = {},
} = mdxSource;
// Remove items that should not be shown for the current framework
const tocItems = (tableOfContents as Toc).filter(({ id }) =>
id && id in headingsFrameworkVisibility
? headingsFrameworkVisibility[id]?.includes(framework)
: true,
);
return (
<DocsLayout nav={<DocNav />} tocItems={tocItems}>
@@ -78,50 +93,3 @@ export async function DocPage({
);
}
}
export async function getMdxWithToc(framework: string, slug?: string[]) {
const slugPath = slug?.join("/");
const mdxSource = await getMdxSource(framework, slugPath);
const {
default: Content,
tableOfContents,
headingsFrameworkVisibility,
} = mdxSource;
// Remove items that should not be shown for the current framework
const tocItems = filterTocItemsForFramework(
tableOfContents as Toc,
framework,
headingsFrameworkVisibility
);
return {
Content,
tocItems,
};
}
function filterTocItemsForFramework(
tocItems: Toc,
framework: string,
headingsFrameworkVisibility: Record<string, string[]>
): Toc {
return tocItems
.map(item => {
const isVisible =
!item.id || !(item.id in headingsFrameworkVisibility) ||
headingsFrameworkVisibility[item.id]?.includes(framework);
if (!isVisible) return null;
const filteredChildren = item.children
? filterTocItemsForFramework(item.children, framework, headingsFrameworkVisibility)
: [];
return {
...item,
children: filteredChildren,
};
})
.filter(Boolean) as Toc;
}

View File

@@ -44,7 +44,6 @@
"jazz-react": "link:../../packages/jazz-react",
"jazz-react-auth-clerk": "link:../../packages/jazz-react-auth-clerk",
"jazz-react-native": "link:../../packages/jazz-react-native",
"jazz-richtext-prosemirror": "link:../../packages/jazz-richtext-prosemirror",
"jazz-tools": "link:../../packages/jazz-tools",
"lucide-react": "^0.436.0",
"mdast-util-from-markdown": "^2.0.0",

Some files were not shown because too many files have changed in this diff Show More