### Clone and Run Project
Source: https://automerge.org/docs/tutorial/setup
Follow these commands to clone the tutorial project, switch to the correct branch, install dependencies, and start the development server.
```bash
$ git clone https://github.com/automerge/automerge-repo-quickstart
# Cloning into 'automerge-repo-quickstart'...
$ cd automerge-repo-quickstart
$ git checkout without-automerge
$ npm install
# ...installing dependencies...
$ npm run dev
```
--------------------------------
### Install react-use Package
Source: https://automerge.org/docs/tutorial/multiple-task-lists
Installs the `react-use` package, which provides utility hooks for React applications, including `useHash` for managing the browser's location hash.
```bash
npm install react-use
```
--------------------------------
### Install Vite Plugins for WASM
Source: https://automerge.org/docs/reference/library-initialization
Install the necessary plugins for Vite to handle WebAssembly modules and top-level await.
```bash
yarn add vite-plugin-wasm vite-plugin-top-level-await
```
--------------------------------
### Initialize Automerge Repo and Adapters
Source: https://automerge.org/docs/cookbook/rich-text-prosemirror-vanilla
Sets up the Automerge repository with IndexedDB storage and a WebSocket client for network synchronization. Ensure these libraries are installed.
```javascript
import { DocHandle, Repo, isValidAutomergeUrl } from "@automerge/automerge-repo"
import { IndexedDBStorageAdapter } from "@automerge/automerge-repo-storage-indexeddb"
import { BrowserWebSocketClientAdapter } from "@automerge/automerge-repo-network-websocket"
import { init } from "@automerge/automerge-prosemirror"
const repo = new Repo({
storage: new IndexedDBStorageAdapter("automerge"),
network: [new BrowserWebSocketClientAdapter("wss://sync.automerge.org")],
})
```
--------------------------------
### Install Automerge React
Source: https://automerge.org/docs/tutorial/local-sync
Install the Automerge React library to get started with Automerge in your React project.
```bash
$ npm install @automerge/react
# ...installing dependencies...
```
--------------------------------
### WebSocket Client Adapter Setup
Source: https://automerge.org/docs/reference/repositories/networking
Initialize a WebSocket client adapter to connect to a remote WebSocket server. Specify the server's WebSocket URL.
```typescript
import { WebSocketClientAdapter } from "@automerge/automerge-repo-network-websocket";
const network = new WebSocketClientAdapter("ws://localhost:3030");
```
--------------------------------
### Install ProseMirror Dependencies
Source: https://automerge.org/docs/cookbook/rich-text-prosemirror-react
Add the necessary ProseMirror and related packages to your project.
```bash
yarn add @automerge/prosemirror prosemirror-example-setup prosemirror-model prosemirror-state prosemirror-view
```
--------------------------------
### Create a Repo with Storage and Network Adapters
Source: https://automerge.org/docs/reference/repositories
Initialize a Repo instance, configuring it with storage for local data persistence and network adapters for peer-to-peer communication. This example uses WebSocket and Node.js file system adapters.
```typescript
import { Repo } from "@automerge/automerge-repo";
import { WebSocketServer } from "ws";
import { NodeWSServerAdapter } from "@automerge/automerge-repo-network-websocket";
import { NodeFSStorageAdapter } from "@automerge/automerge-repo-storage-nodefs";
const wss = new WebSocketServer({ noServer: true });
const repo = new Repo({
network: [new NodeWSServerAdapter(wss)],
storage: new NodeFSStorageAdapter(dir),
});
```
--------------------------------
### BroadcastChannel Network Adapter Setup
Source: https://automerge.org/docs/reference/repositories/networking
Instantiate a BroadcastChannel network adapter for communication between browser processes. Note that this can be inefficient for point-to-point synchronization.
```typescript
import { BroadcastChannelNetworkAdapter } from "@automerge/automerge-repo-network-broadcastchannel";
const network = new BroadcastChannelNetworkAdapter();
```
--------------------------------
### WebSocket Server Adapter Setup
Source: https://automerge.org/docs/reference/repositories/networking
Set up a WebSocket server adapter using the 'ws' library. This is the server-side component for WebSocket communication.
```typescript
import { WebSocketServer } from "ws";
import { WebSocketServerAdapter } from "@automerge/automerge-repo-network-websocket";
const wss = new WebSocketServer({ port: 8080 });
const adapter = new WebSocketServerAdapter(wss);
```
--------------------------------
### Initialize Automerge Repo with Local Sync
Source: https://automerge.org/docs/tutorial/local-sync
Configure and initialize an Automerge Repo with IndexedDB for local storage and BroadcastChannel for network synchronization. This setup ensures data persistence and syncs changes between browser tabs.
```typescript
import React, { Suspense } from "react";
import ReactDOM from "react-dom/client";
import App from "./components/App.tsx";
import "./index.css";
import { initTaskList } from "./components/TaskList.tsx";
import {
Repo,
BroadcastChannelNetworkAdapter,
IndexedDBStorageAdapter,
} from "@automerge/react";
const repo = new Repo({
network: [new BroadcastChannelNetworkAdapter()],
storage: new IndexedDBStorageAdapter(),
});
// Add the repo to the global window object so it can be accessed in the browser console
// This is useful for debugging and testing purposes.
declare global {
interface Window {
repo: Repo;
}
}
window.repo = repo;
ReactDOM.createRoot(document.getElementById("root")!).render(
Loading a document...}>
,
);
```
--------------------------------
### MessageChannel Network Adapter Setup
Source: https://automerge.org/docs/reference/repositories/networking
Configure MessageChannel network adapters for inter-process communication within the same browser. This involves creating two ports and wrapping them with the adapter.
```typescript
import { MessageChannelNetworkAdapter } from "@automerge/automerge-repo-network-messagechannel";
import { Repo } from "@automerge/automerge-repo";
const { port1: leftToRight, port2: rightToLeft } = new MessageChannel();
const rightToLeft = new MessageChannelNetworkAdapter(rightToLeft);
const leftToRight = new MessageChannelNetworkAdapter(leftToRight);
const left = new Repo({
network: [leftToRight],
});
const right = new Repo({
network: [rightToLeft],
});
```
--------------------------------
### Example Rich Text Document
Source: https://automerge.org/docs/reference/under-the-hood/rich-text-schema
An example array representing a rich text document with text segments, block elements, and inline formatting like bold and links.
```javascript
[
{
type: "text", value: "From the automerge docs:"
},
{
type: "block",
value: { parents: ["blockquote"], type: "paragraph" },
},
{ type: "text", value: "The requirements we have for this schema are:" },
{
type: "block",
value: { parents: ["blockquote", "ordered-list"], type: "paragraph"},
},
{type: "text": value: "The ability to represent inline text decoration such as bold spans, as well as semantic information like hyperlinks or code spans"},
{
type: "block",
value: { parents: ["blockquote", "ordered-list"], type: "paragraph"},
},
{type: "text": value: "A way of representing hierarchical structure which merges well - or, alternatively, which results in patches which are commensurate in size with the editing action the user took (inserting a paragraph is a single user action, we would like it to not result in a large patch which is hard to interpret)"},
{ type: "block", value: {parents: ["blockquote"], type: "paragraph"}},
{type: "text", value: "..."}
{ type: "block", value: { parents: [], type: "paragraph"}},
{
type: "text", value: "From: ", marks: { strong: true }
},
{
type: "text", value: "Rich Text Schema", marks: { link: '{\"href\": \"/\", title: \"\"}', em: true}
}
]
```
--------------------------------
### Hierarchical Block Structure Example
Source: https://automerge.org/docs/reference/under-the-hood/rich-text-schema
Illustrates how parent arrays define the hierarchical structure of blocks, with implicit creation of parent blocks.
```javascript
{ parents: ["blockquote"], type: "paragraph" }
{ parents: ["blockquote", "ordered-list-item"], type: "paragraph" }
{ parents: [], type: "paragraph" }
```
--------------------------------
### Update Initialization Logic for Task Lists
Source: https://automerge.org/docs/tutorial/multiple-task-lists
Replaces existing document creation logic with a simplified approach that initializes an empty root document. This ensures a consistent starting point for task lists.
```typescript
// ..
// highlight-red-start
// Depending if we have an AutomergeUrl, either find or create the document
if (isValidAutomergeUrl(locationHash)) {
const taskList = await repo.find(locationHash);
window.handle = repo.create({ taskLists: [taskList.url] });
} else {
const taskList = repo.create(initTaskList());
window.handle = repo.create({ taskLists: [taskList.url] });
// Set the location hash to the new document we just made.
document.location.hash = taskList.url;
}
// highlight-red-end
window.handle = repo.create({ taskLists: [] });
// ..
```
--------------------------------
### Connect to a Sync Server with WebSocketClientAdapter
Source: https://automerge.org/docs/tutorial/network-sync
Add WebSocketClientAdapter to your Repo's network subsystem to sync documents over a websocket connection. This example shows how to configure it at creation time.
```typescript
//...import { WebSocketClientAdapter } from "@automerge/react";
const repo = new Repo({
network: [
new BroadcastChannelNetworkAdapter(),
new WebSocketClientAdapter("wss://sync.automerge.org"),
],
storage: new IndexedDBStorageAdapter(),
});
```
--------------------------------
### Ref URLs and Resolution
Source: https://automerge.org/docs/reference/repositories/refs
Describes how to get a URL representation of a ref and how to resolve such a URL back into a Ref object.
```APIDOC
## Ref URLs and Resolution
### Description
Generate a URL from a ref, which can be shared to reference the same document subsection. Use `findRef` to resolve such a URL back into a `Ref` object.
### Properties
- **url** (string) - A serialized representation of the ref.
### Methods
- **findRef(handle, url)** - Resolves a URL to a `Ref` object.
### Request Example
```javascript
// Assuming 'doneRef' is an existing Ref object
const refUrl = doneRef.url
// To resolve the URL:
// const resolvedRef = repo.findRef(handle, refUrl)
```
```
--------------------------------
### Create and Modify Automerge Document with Various Data Types
Source: https://automerge.org/docs/reference/documents
Demonstrates initializing an Automerge document with all supported data types and then modifying it using the change function. Includes examples of text manipulation, counter increments, and updating nested map and list elements.
```javascript
import * as A from "@automerge/automerge";
let doc = A.from({
map: {
key: "value",
nested_map: { key: "value" },
nested_list: [1],
},
list: ["a", "b", "c", { nested: "map" }, ["nested list"]],
text: "world",
raw_string: new A.ImmutableString("immutablestring"),
integer: 1,
float: 2.3,
boolean: true,
bytes: new Uint8Array([1, 2, 3]),
date: new Date(),
counter: new A.Counter(1),
none: null,
});
doc = A.change(doc, (d) => {
// Insert 'Hello' at the beginning of the string
A.splice(d, ["text"], 0, 0, "Hello ");
d.counter.increment(20);
d.map.key = "new value";
d.map.nested_map.key = "new nested value";
d.list[0] = "A";
d.list.insertAt(0, "Z");
d.list[4].nested = "MAP";
d.list[5][0] = "NESTED LIST";
});
console.log(doc);
// Prints
// {
// map: {
// key: 'new value',
// nested_map: { key: 'new nested value' },
// nested_list: [ 1 ]
// },
// list: [ 'Z', 'A', 'b', 'c', { nested: 'MAP' }, [ 'NESTED LIST' ] ],
// text: 'Hello world',
// raw_string: ImmutableString { val: 'ImmutableString' },
// integer: 1,
// float: 2.3,
// boolean: true,
// bytes: Uint8Array(3) [ 1, 2, 3 ],
// date: 2023-09-11T13:35:12.229Z,
// counter: Counter { value: 21 },
// none: null
// }
```
--------------------------------
### Val.town Val for Document Retrieval
Source: https://automerge.org/docs/reference/library-initialization
A Val.town example that initializes Automerge WASM and retrieves document contents by ID. It uses WebSocketClientAdapter for network communication.
```javascript
import { BrowserWebSocketClientAdapter } from "npm:@automerge/automerge-repo-network-websocket";
import { isValidAutomergeUrl, Repo } from "npm:@automerge/automerge-repo/slim";
/* set up Automerge's internal wasm guts manually */
import { automergeWasmBase64 } from "npm:@automerge/automerge/automerge.wasm.base64.js";
import * as automerge from "npm:@automerge/automerge/slim";
await automerge.initializeBase64Wasm(automergeWasmBase64);
/* This example will return the contents of a documentID passed in as the path as JSON. */
export default async function (req: Request): Promise {
const docId = new URL(req.url).pathname.substring(1);
if (!isValidAutomergeUrl("automerge:" + docId)) {
return Response.error();
}
const repo = new Repo({
network: [new BrowserWebSocketClientAdapter("wss://sync.automerge.org")],
});
const handle = await repo.find(docId);
const contents = handle.doc();
return Response.json(contents);
}
```
--------------------------------
### Create a Ref and Get its Value
Source: https://automerge.org/docs/reference/repositories/refs
Create a document handle and then a Ref to a specific field within an array element. Use `.value()` to retrieve the current value at that reference.
```javascript
const handle = repo.create({
todos: [
{ title: "First", done: false },
{ title: "Second", done: true },
]
})
const doneRef = handle.ref("todos", 0, "done")
// Logs false
console.log("first todo done:", doneRef.value())
```
--------------------------------
### Initialize Repo and Load/Create Document by URL
Source: https://automerge.org/docs/tutorial/load-by-url
Sets up the Automerge repository and handles loading an existing document from the URL hash or creating a new one if none is found. The new document's URL is then set as the location hash.
```typescript
import React, { Suspense } from "react";
import ReactDOM from "react-dom/client";
import App from "./components/App.tsx";
import "./index.css";
import { initTaskList, TaskList } from "./components/TaskList.tsx";
import {
Repo,
BroadcastChannelNetworkAdapter,
IndexedDBStorageAdapter,
isValidAutomergeUrl,
DocHandle,
} from "@automerge/react";
const repo = new Repo({
network: [new BroadcastChannelNetworkAdapter()],
storage: new IndexedDBStorageAdapter(),
});
// Add the repo to the global window object so it can be accessed in the browser console
// This is useful for debugging and testing purposes.
declare global {
interface Window {
repo: Repo;
// We also add the handle to the global window object for debugging
handle: DocHandle;
}
}
window.repo = repo;
// Check the URL for a document to load
const locationHash = document.location.hash.substring(1);
// Depending if we have an AutomergeUrl, either find or create the document
if (isValidAutomergeUrl(locationHash)) {
window.handle = await repo.find(locationHash);
} else {
window.handle = repo.create(initTaskList());
// Set the location hash to the new document we just made.
document.location.hash = window.handle.url;
}
ReactDOM.createRoot(document.getElementById("root")!).render(
Loading a document...}>
,
);
```
--------------------------------
### Initialize Document Schema and Merge
Source: https://automerge.org/docs/cookbook/modeling-data
Sets up an initial document schema with an empty array and demonstrates merging this schema into another document before independent updates. Ensures consistent data structures across collaborators.
```javascript
// Set up the `cards` array in doc1
let doc1 = Automerge.change(Automerge.init(), (doc) => {
doc.cards = [];
});
// In doc2, don't create `cards` again! Instead, merge
// the schema initialization from doc1
let doc2 = Automerge.merge(Automerge.init(), doc1);
// Now we can update both documents
doc1 = Automerge.change(doc1, (doc) => {
doc.cards.push({ title: "card1" });
});
doc2 = Automerge.change(doc2, (doc) => {
doc.cards.push({ title: "card2" });
});
// The merged document will contain both cards
doc1 = Automerge.merge(doc1, doc2);
doc2 = Automerge.merge(doc2, doc1);
```
--------------------------------
### Automerge Spans Output Example
Source: https://automerge.org/docs/reference/documents/rich-text
This is the expected output when using `Automerge.spans` after updating text spans to include a new paragraph marker.
```json
[
{ type: 'text', value: 'hello' },
{ type: 'block', value: { type: 'paragraph', parents: [] } },
{ type: 'text', value: 'world' }
]
```
--------------------------------
### Initialize Repo with IndexedDB Storage
Source: https://automerge.org/docs/reference/under-the-hood/storage
Configure the Repo to use IndexedDB for storage, disabling live network synchronization.
```typescript
const repo = new Repo({
network: [], // This part means that we're not sending live changes anywhere
storage: new IndexedDBStorageAdapter(),
});
```
--------------------------------
### Initialize Root Document in main.tsx
Source: https://automerge.org/docs/tutorial/multiple-task-lists
Sets up the Automerge repository and initializes the root document. It either finds an existing root document based on the URL hash or creates a new one, linking the initial task list.
```typescript
import { RootDocument } from "./rootDoc.ts"
// ..
// Add the repo to the global window object so it can be accessed in the browser console
// This is useful for debugging and testing purposes.
declare global {
interface Window {
repo: Repo;
// We also add the handle to the global window object for debugging
handle: DocHandle;
}
}
window.repo = repo;
// Check the URL for a document to load
const locationHash = document.location.hash.substring(1);
// Depending if we have an AutomergeUrl, either find or create the document
if (isValidAutomergeUrl(locationHash)) {
const taskList = await repo.find(locationHash);
window.handle = repo.create({ taskLists: [taskList.url] });
} else {
const taskList = repo.create(initTaskList());
window.handle = repo.create({ taskLists: [taskList.url] });
// Set the location hash to the new document we just made.
document.location.hash = taskList.url;
}
ReactDOM.createRoot(document.getElementById("root")!).render(
Loading a document...}>
,
);
```
--------------------------------
### Automerge Document URL Example
Source: https://automerge.org/docs/reference/concepts
An automerge document URL is a string used to identify and locate documents within a repository. It follows the format `automerge:`.
```text
automerge:2akvofn6L1o4RMUEMQi7qzwRjKWZ
```
--------------------------------
### Initialize Node.js File System Storage Adapter
Source: https://automerge.org/docs/reference/repositories/storage
Instantiate the NodeFSStorageAdapter for persisting data to the local file system in Node.js environments. This adapter is safe for concurrent use by multiple processes.
```typescript
import { NodeFSStorageAdapter } from "@automerge/automerge-repo-storage-nodefs";
const storage = new NodeFSStorageAdapter();
```
--------------------------------
### Initialize Root Document in main.tsx
Source: https://automerge.org/docs/tutorial/persist-root-doc
Initializes the Automerge repo and sets up the root document, making it accessible globally for debugging and application use. The root document URL is retrieved or created and then stored in local storage.
```typescript
import React, { Suspense } from "react";
import ReactDOM from "react-dom/client";
import App from "./components/App.tsx";
import "./index.css";
import {
Repo,
BroadcastChannelNetworkAdapter,
WebSocketClientAdapter,
IndexedDBStorageAdapter,
RepoContext,
DocHandle,
} from "@automerge/react";
import { getOrCreateRoot, RootDocument } from "./rootDoc.ts";
const repo = new Repo ({
network: [
new BroadcastChannelNetworkAdapter(),
new WebSocketClientAdapter("wss://sync.automerge.org"),
],
storage: new IndexedDBStorageAdapter(),
});
// Add the repo to the global window object so it can be accessed in the browser console
// This is useful for debugging and testing purposes.
declare global {
interface Window {
repo: Repo;
// We also add the handle to the global window object for debugging
handle: DocHandle;
}
}
window.repo = repo;
const rootDocUrl = getOrCreateRoot(repo);
window.handle = await repo.find(rootDocUrl);
ReactDOM.createRoot(document.getElementById("root")!).render(
Loading a document...}>
,
);
```
--------------------------------
### Initialize IndexedDB Storage Adapter
Source: https://automerge.org/docs/reference/repositories/storage
Instantiate the IndexedDBStorageAdapter for browser-based data persistence. This adapter is safe for concurrent use across multiple tabs.
```typescript
import { IndexedDBStorageAdapter } from "@automerge/automerge-repo-storage-indexeddb"
const storage = new IndexedDBStorageAdapter()
```
--------------------------------
### Create and Modify a Document Handle
Source: https://automerge.org/docs/reference/repositories
Obtain a DocHandle from the repository to create or access a document. You can then make changes to the document using the change() method and listen for incoming changes from other peers via the 'change' event.
```typescript
let doc = repo.create();
// Make a change ourselves and send that to everyone else
doc.change((d) => (d.text = "hello world"));
// Listen for changes from other peers
doc.on("change", ({ doc }) => {
console.log("new text is ", doc.text);
});
```
--------------------------------
### Get or Create Root Document ID
Source: https://automerge.org/docs/tutorial/persist-root-doc
Retrieves the root document ID from local storage or creates a new root document if none exists. The ID is then stored in local storage for future use.
```typescript
import { AutomergeUrl, Repo } from "@automerge/react";
const ROOT_DOC_URL_KEY = "root-doc-url";
export type RootDocument = {
taskLists: AutomergeUrl[];
};
export const getOrCreateRoot = (repo: Repo): AutomergeUrl => {
// Check if we already have a root document
const existingUrl = localStorage.getItem(ROOT_DOC_URL_KEY);
if (existingUrl) {
return existingUrl as AutomergeUrl;
}
// Otherwise create one and (synchronously) store it
const root = repo.create({ taskLists: [] });
localStorage.setItem(ROOT_DOC_URL_KEY, root.url);
return root.url;
};
```
--------------------------------
### Initialize Automerge with Raw WebAssembly URL
Source: https://automerge.org/docs/reference/library-initialization
Use this method in Vite applications to initialize Automerge by providing the URL to the `automerge.wasm` file. Ensure you use the `/slim` variants for Automerge and Automerge-Repo imports.
```javascript
// Note the ?url suffix
import wasmUrl from "@automerge/automerge/automerge.wasm?url";
// Note the `/slim` suffixes
import * as Automerge from "@automerge/automerge/slim";
import { Repo } from "@automerge/automerge-repo/slim";
await Automerge.initializeWasm(wasmUrl)
// Now we can get on with our lives
const repo = new Repo({..})
```
--------------------------------
### Create a new Automerge Document
Source: https://automerge.org/docs/tutorial/local-sync
Use `window.repo.create()` to generate a new Automerge document. This action saves the document to the configured storage adapter and announces it to the network. It returns a `DocHandle` for accessing the document and its URL.
```javascript
const handle = window.repo.create({foo: "bar"})
console.log(handle.url)
```
--------------------------------
### Creating and Accessing a Ref
Source: https://automerge.org/docs/reference/repositories/refs
Demonstrates how to create a reference to a specific field within a document and access its value.
```APIDOC
## Creating and Accessing a Ref
### Description
Create a reference to a specific field within a document and retrieve its current value.
### Method
`handle.ref(path, ...)`
### Parameters
#### Path Parameters
- **path** (string | number | cursor | object) - The path to the desired subsection of the document.
### Request Example
```javascript
const handle = repo.create({
todos: [
{ title: "First", done: false },
{ title: "Second", done: true },
]
})
const doneRef = handle.ref("todos", 0, "done")
// Logs false
console.log("first todo done:", doneRef.value())
```
### Response
#### Success Response
- **value** (any) - The current value of the referenced subsection.
#### Response Example
```javascript
false
```
```
--------------------------------
### Set up RepoContext in React App
Source: https://automerge.org/docs/tutorial/react
Wrap your App component with RepoContext.Provider in main.tsx to make the Automerge repo available via hooks. Ensure Suspense is used for loading states.
```tsx
// ...
import {
Repo,
BroadcastChannelNetworkAdapter,
IndexedDBStorageAdapter,
RepoContext,
isValidAutomergeUrl,
DocHandle,
} from "@automerge/react";
// ...
ReactDOM.createRoot(document.getElementById("root")!).render(
Loading a document...}>
,
);
```
--------------------------------
### Initialize Automerge with Base64 Encoded WebAssembly
Source: https://automerge.org/docs/reference/library-initialization
Use this method when your environment can load JavaScript files but not other module types. It initializes Automerge using a base64 encoded string of the WebAssembly file. Ensure you use the `/slim` variants for Automerge and Automerge-Repo imports.
```javascript
import { automergeWasmBase64 } from "@automerge/automerge/automerge.wasm.base64.js";
// Note the `/slim` suffixes
import * as Automerge from "@automerge/automerge/slim";
import { Repo } from `@automerge/automerge-repo/slim`;
await Automerge.initializeBase64Wasm(automergeWasmBase64)
// Now we can get on with our lives
const repo = new Repo({..})
```
--------------------------------
### Create New Task List in DocumentList Component
Source: https://automerge.org/docs/tutorial/multiple-task-lists
Use the `useRepo` hook to create a new task list document and register it in the root document. This snippet is part of the `DocumentList` component.
```typescript
import React from "react";
import { useDocument, AutomergeUrl, useRepo } from "@automerge/react";
import { initTaskList, TaskList } from "./TaskList";
import { RootDocument } from "../rootDoc";
export const DocumentList: React.FC<{
docUrl: AutomergeUrl;
selectedDocument: AutomergeUrl | null;
onSelectDocument: (docUrl: AutomergeUrl | null) => void;
}> = ({ docUrl, selectedDocument, onSelectDocument }) => {
const repo = useRepo();
const [doc, changeDoc] = useDocument(docUrl, {
suspense: true,
});
const handleNewDocument = () => {
const newTaskList = repo.create(initTaskList());
changeDoc((d) => d.taskLists.push(newTaskList.url));
onSelectDocument(newTaskList.url);
};
return (
{doc.taskLists.map((docUrl) => (
onSelectDocument(docUrl)}
>
))}
);
};
// Component to display document title
const DocumentTitle: React.FC<{ docUrl: AutomergeUrl }> = ({ docUrl }) => {
const [doc] = useDocument(docUrl, { suspense: true });
// Get the first task's title or use a default
const title = doc.title || "Untitled Task List";
return
{title}
;
};
```
--------------------------------
### Initialize ProseMirror Editor with Automerge Integration
Source: https://automerge.org/docs/cookbook/rich-text-prosemirror-vanilla
Initializes the ProseMirror editor using the Automerge document handle and its associated schema and plugin. Requires `EditorView` and `EditorState` from ProseMirror.
```javascript
// This is the integration with automerge.
const { schema, doc, plugin } = init(handle, ["text"])
const editorConfig = {
schema,
plugins: [plugin],
}
// This is the prosemirror editor.
const view = new EditorView(document.querySelector("#editor"), {
state: EditorState.create({
doc, // Note that we initialize using the mirror
plugins: exampleSetup({ schema, plugins: [plugin] }),
}),
})
```
--------------------------------
### Create Vite App with Automerge Dependencies
Source: https://automerge.org/docs/cookbook/rich-text-prosemirror-react
Use the @automerge/vite-app template to create a new Vite project with Automerge dependencies pre-installed.
```bash
yarn create @automerge/vite-app
```
--------------------------------
### Demonstrate Concurrent Updates and Merging
Source: https://automerge.org/docs/reference/documents/conflicts
Illustrates creating two separate Automerge documents with conflicting updates to the same property ('x') and then merging them. The assertion confirms that merging in either order results in an identical document state, highlighting Automerge's deterministic conflict resolution.
```javascript
let doc1 = Automerge.change(Automerge.init(), (doc) => {
doc.x = 1;
});
let doc2 = Automerge.change(Automerge.init(), (doc) => {
doc.x = 2;
});
let doc1_merged_with_doc2 = Automerge.merge(doc1, doc2);
let doc2_merged_with_doc1 = Automerge.merge(doc2, doc1);
assert.deepEqual(doc1_merged_with_doc2, doc2_merged_with_doc1);
```
--------------------------------
### Pattern Matching with Refs
Source: https://automerge.org/docs/reference/repositories/refs
Explains how to use pattern matching to select document subsections without explicit paths.
```APIDOC
## Pattern Matching with Refs
### Description
Select document subsections by simple pattern matching, such as matching an object with a specific property value.
### Method
`handle.ref(path, pattern)`
### Parameters
#### Path Parameters
- **path** (string) - The path to the array or object containing the target subsection.
- **pattern** (object) - An object defining the properties to match.
### Request Example
```javascript
let handle = repo.create({
users: [
{ id: "a", name: "Alice" },
{ id: "b", name: "Bob" },
{ id: "c", name: "Charlie" },
]
})
const bobRef = handle.ref("users", { id: "b" })
bobRef.remove()
// Logs the objects for Alice and Charlie
console.log(handle.doc().users)
```
```
--------------------------------
### Listen for Document Changes
Source: https://automerge.org/docs/tutorial/local-sync
Attach a change listener to a `DocHandle` using `handle.on("change", ...)` to log updates to the document. This allows you to observe real-time modifications as they occur.
```javascript
handle.on("change", evt => console.log(evt.doc))
```
--------------------------------
### Create and Manage Task List URL
Source: https://automerge.org/docs/tutorial/multiple-task-lists
Use this snippet to create a new task list, update the document with its URL, and then select the new document. Ensure the `changeDoc` function is available and correctly implemented.
```typescript
const newTaskList = repo.create(initTaskList());
changeDoc((d) => d.taskLists.push(newTaskList.url));
onSelectDocument(newTaskList.url);
```
--------------------------------
### Verify Root Document in Console
Source: https://automerge.org/docs/tutorial/persist-root-doc
This snippet is used in the browser's developer console to verify that the root document is correctly loaded from local storage and to inspect its contents. It retrieves the root document URL and then fetches the document using the Automerge repo.
```javascript
const rootDocUrl = localStorage.getItem("root-doc-url")
const root = await window.repo.find(rootDocUrl);
console.log("Root document:", root.doc());
```
--------------------------------
### Integrating DocumentList into App
Source: https://automerge.org/docs/tutorial/multiple-task-lists
This code snippet shows how to integrate the `DocumentList` component into the main `App.tsx` file. It renders the sidebar and the primary task list, assuming `docUrl` is provided.
```typescript
// ..
import { DocumentList } from "./DocumentList";
// ..
function App({ docUrl }: { docUrl: AutomergeUrl }) {
const [doc] = useDocument(docUrl, {
suspense: true,
});
return (
<>
Automerge Task List
>
);
}
```
--------------------------------
### HTML Structure for ProseMirror Editor
Source: https://automerge.org/docs/cookbook/rich-text-prosemirror-vanilla
Basic HTML file structure required to host the ProseMirror editor and load the main JavaScript file.
```html
Prosemirror + Automerge
```
--------------------------------
### Initialize Automerge WASM from Base64 in Deno
Source: https://automerge.org/docs/reference/library-initialization
Manually initialize the Automerge WebAssembly module using a base64 encoded string. This is an alternative when direct WASM file loading is not feasible.
```javascript
import { automergeWasmBase64 } from "npm:@automerge/automerge";
import * as Am from "npm:@automerge/automerge";
await Am.initializeBase64Wasm(automergeWasmBase64);
```
--------------------------------
### Initialize Document from Hard-coded Change
Source: https://automerge.org/docs/cookbook/modeling-data
Loads an Automerge document from a hard-coded initial change byte array. This is useful for ensuring independent devices can initialize a document with the same schema, enabling future merges.
```javascript
// hard-code the initial change here
const initChange = new Uint8Array([133, 111, 74, 131, ...])
let [doc] = Automerge.load(initChange)
```
--------------------------------
### Initialize Automerge WASM in Unbundled JS
Source: https://automerge.org/docs/reference/library-initialization
Manually load the Automerge WebAssembly module using fetch and initialize it. This method is useful for environments without a build process.
```javascript
import * as AutomergeRepo from "https://esm.sh/@automerge/react@2.2.0/slim?bundle-deps";
await AutomergeRepo.initializeWasm(
fetch("https://esm.sh/@automerge/automerge/dist/automerge.wasm")
);
// Then set up an automerge repo (loading with our annoying WASM hack)
const repo = new AutomergeRepo.Repo({
storage: new AutomergeRepo.IndexedDBStorageAdapter(),
network: [
new AutomergeRepo.WebSocketClientAdapter("wss://sync.automerge.org"),
],
});
```
--------------------------------
### Handle Document URL and Create/Find Document
Source: https://automerge.org/docs/cookbook/rich-text-prosemirror-vanilla
Retrieves an Automerge document URL from the browser's hash or creates a new document if none is present. Updates the URL hash to reflect the new document's URL.
```javascript
// Get the document ID from the URL fragment if it's there. Otherwise, create
// a new document and update the URL fragment to match.
const docUrl = window.location.hash.slice(1)
if (docUrl && isValidAutomergeUrl(docUrl)) {
handle = await repo.find(docUrl)
} else {
handle = repo.create({ text: "" })
window.location.hash = handle.url
}
```
--------------------------------
### Removing Properties with Refs
Source: https://automerge.org/docs/reference/repositories/refs
Demonstrates how to remove properties, substrings, or array elements using references.
```APIDOC
## Removing Properties with Refs
### Description
Remove properties, substrings referenced by cursors, or array elements using a reference.
### Method
`ref.remove()`
### Request Example
```javascript
let handle = repo.create({
user: { name: "Alice", age: 30, hobbies: ["biking", "spelunking", "weaving"] }
})
// Remove an object field
const ageRef = handle.ref("user", "age")
ageRef.remove()
// Logs undefined
console.log("age is", handle.doc().user.age)
// Remove a substring using a cursor
const nameRef = handle.ref("user", "name", cursor(2,5))
nameRef.remove()
// Logs Al
console.log("name is", handle.doc().user.name)
// Remove an array element
const bikingHobbyRef = handle.ref("user", "hobbies", 0)
bikingHobbyRef.remove()
// logs ['spelunking', 'weaving']
console.log("hobbies are ", handle.doc().user.hobbies)
```
```
--------------------------------
### Integrate ProseMirror Editor with Automerge Handle
Source: https://automerge.org/docs/cookbook/rich-text-prosemirror-react
This snippet shows the core integration logic. It initializes ProseMirror's state and view, incorporating the Automerge plugin, and attaches it to a DOM element referenced by useRef. It also handles editor cleanup.
```typescript
import { AutomergeUrl } from "@automerge/automerge-repo"
import { useHandle } from "@automerge/automerge-repo-react-hooks"
import { useEffect, useRef, useState } from "react"
import {EditorState} from "prosemirror-state"
import {EditorView} from "prosemirror-view"
import {exampleSetup} from "prosemirror-example-setup"
import { init } from "@automerge/prosemirror"
import "prosemirror-example-setup/style/style.css"
import "prosemirror-menu/style/menu.css"
import "prosemirror-view/style/prosemirror.css"
import "./App.css"
function App({ docUrl }: { docUrl: AutomergeUrl }) {
const editorRoot = useRef(null)
const handle = useHandle<{text: string}>(docUrl)
const [loaded, setLoaded] = useState(handle && handle.docSync() != null)
useEffect(() => {
if (handle != null) {
handle.whenReady().then(() => {
if (handle.docSync() != null) {
setLoaded(true)
}
})
}
}, [handle])
const [view, setView] = useState(null)
useEffect(() => {
if (editorRoot.current != null && loaded) {
// This is the integration with automerge
const { pmDoc: doc, schema, plugin } = init(handle!, ["text"])
const plugins = exampleSetup({schema})
plugins.push(plugin)
const view = new EditorView(editorRoot.current, {
state: EditorState.create({
schema,
plugins,
doc,
}),
})
setView(view)
}
return () => {
if (view) {
view.destroy()
setView(null)
}
}
}, [editorRoot, loaded])
return
}
export default App
```
--------------------------------
### Assigning and Deleting Simple Values in Automerge
Source: https://automerge.org/docs/reference/documents/values
Demonstrates assigning string, number, boolean, null, and nested object values. Also shows how to delete properties and use ImmutableString for non-collaborative strings.
```javascript
newDoc = Automerge.change(currentDoc, (doc) => {
doc.property = "value"; // assigns a string value to a property
doc["property"] = "value"; // equivalent to the previous line
delete doc["property"]; // removes a property
doc.stringValue = "value";
doc.numberValue = 1;
doc.boolValue = true;
doc.nullValue = null;
doc.nestedObject = {}; // creates a nested object
doc.nestedObject.property = "value";
// you can also assign an object that already has some properties
doc.otherObject = { key: "value", number: 42 };
// By default, strings are collaborative sequences of characters. There are
// cases where you want a string which is not collaborative - URLs for example
// should generally be updated in one go. In this case you can use `ImmutableString`,
// which does not allow concurrent updates.
doc.atomicStringValue = new Automerge.ImmutableString("")
});
```
--------------------------------
### Use Fine-Grained Mutable Updates in Automerge
Source: https://automerge.org/docs/reference/documents/lists
Demonstrates the recommended mutable update pattern for Automerge. This approach uses fine-grained operations like push and direct property assignment, which are better for concurrent merge handling.
```javascript
state = Automerge.change(state, "Add card", (doc) => {
const newItem = { id: 123, title: "Rewrite everything in Rust", done: false };
doc.cards.ids.push(newItem.id);
doc.cards.entities[newItem.id] = newItem;
});
```
--------------------------------
### Integrate Sync Controls in App Component
Source: https://automerge.org/docs/tutorial/multi-device-root-doc
Includes the SyncControls component in the application's footer, passing the current document URL as a prop.
```typescript
// ..
import { useHash } from "react-use";
import { SyncControls } from "./SyncControls";
// ..
```
--------------------------------
### Find an Automerge Document
Source: https://automerge.org/docs/tutorial/local-sync
Retrieve an Automerge document using its URL with `window.repo.find()`. This method checks local storage and connected peers for the document. It's useful for accessing documents in different browser tabs.
```javascript
const handle = await window.repo.find("")
console.log(handle.doc())
```
--------------------------------
### Reference Object by Pattern Matching
Source: https://automerge.org/docs/reference/repositories/refs
Create a Ref to an object within an array by providing a matching object as the path argument. This allows for selection based on properties.
```javascript
handle = repo.create({
users: [
{ id: "a", name: "Alice" },
{ id: "b", name: "Bob" },
{ id: "c", name: "Charlie" },
]
})
const bobRef = handle.ref("users", { id: "b" })
bobRef.remove()
// Logs the objects for Alice and Charlie
console.log(handle.doc().users)
```
--------------------------------
### Referencing String Ranges with Cursors
Source: https://automerge.org/docs/reference/repositories/refs
Illustrates using cursors to create references to specific ranges within a string and modifying that range.
```APIDOC
## Referencing String Ranges with Cursors
### Description
Use cursors to create a reference to a specific subsection of a string and modify the text within that range.
### Method
`handle.ref(path, cursor(start, end))`
### Parameters
#### Path Parameters
- **path** (string) - The path to the string property.
- **cursor(start, end)** (function) - Defines the start and end indices of the string range.
### Request Example
```javascript
let handle = repo.create({
message: "Hello world"
})
const rangeRef = handle.ref("message", cursor(0, 5))
// Logs 'Hello'
console.log(rangeRef.value())
rangeRef.change(() => "Hi")
// The text is replaced at the range; logs "Hi world"
console.log(handle.doc().message)
```
### Response
#### Success Response
- **value** (string) - The substring at the specified range.
#### Response Example
```javascript
'Hello'
```
```
--------------------------------
### Configure Vite for WASM and Top-Level Await
Source: https://automerge.org/docs/reference/library-initialization
Add the vite-plugin-wasm and vite-plugin-top-level-await to your Vite configuration. This enables proper handling of WebAssembly and top-level await statements.
```javascript
import { defineConfig } from 'vite'
import wasm from 'vite-plugin-wasm'
import topLevelAwait from 'vite-plugin-top-level-await'
export default defineConfig({
...
plugins: [wasm(), topLevelAwait()],
...
})
```
--------------------------------
### Update App Component to Use Root Document
Source: https://automerge.org/docs/tutorial/multiple-task-lists
Modifies the App component to use the root document's URL and render the first task list found within it. This ensures the application correctly loads and displays the task list.
```typescript
// ..
import { TaskList } from "./TaskList";
import { type AutomergeUrl, useDocument } from "@automerge/react";
import { RootDocument } from "../rootDoc";
// ..
function App({ docUrl }: { docUrl: AutomergeUrl }) {
const [doc] = useDocument(docUrl, {
suspense: true,
});
return (
<>