### 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

Powered by Automerge + Vite + React + TypeScript

); } ``` -------------------------------- ### 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"; // ..

Powered by Automerge + Vite + React + TypeScript

``` -------------------------------- ### 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 ( <>

Automerge Task List

// .. ```