diff --git a/app.html b/app.html
index b1e617d..67ec011 100644
--- a/app.html
+++ b/app.html
@@ -3,7 +3,7 @@
-
+
Story Editor
diff --git a/index.html b/index.html
index d0e6b72..b7354cf 100644
--- a/index.html
+++ b/index.html
@@ -3,7 +3,7 @@
-
+
Story Editor
diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx
index 44e1f7f..c49594d 100644
--- a/src/components/Editor.tsx
+++ b/src/components/Editor.tsx
@@ -1,4 +1,3 @@
-import * as Y from 'yjs';
import { EditorState } from '@codemirror/state';
import { keymap, EditorView } from '@codemirror/view';
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands';
@@ -7,29 +6,35 @@ import {
syntaxHighlighting,
} from '@codemirror/language';
import { yCollab } from 'y-codemirror.next';
-import { onCleanup, onMount } from 'solid-js';
-import { syncDoc } from '../sync';
+import { createEffect, createResource, onCleanup, Show } from 'solid-js';
+import { LocalDocument } from '../sync';
interface EditorProps {
file: string;
}
function Editor(props: EditorProps) {
- let doc: Y.Doc;
let editorDiv;
let editorView: EditorView;
+ const [syncedDoc] = createResource(async () =>
+ LocalDocument.load(props.file)
+ );
+
+ createEffect(() => {
+ const doc = syncedDoc();
+ if (!doc) {
+ return;
+ }
+ const ydoc = doc.doc;
- onMount(() => {
- doc = new Y.Doc();
- syncDoc(props.file, doc);
const state = EditorState.create({
- doc: doc.getText().toString(),
+ doc: ydoc.getText().toString(),
extensions: [
EditorView.lineWrapping,
history(),
keymap.of([...defaultKeymap, ...historyKeymap]),
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
- yCollab(doc.getText(), null),
+ yCollab(ydoc.getText(), null),
],
});
editorView = new EditorView({ state, parent: editorDiv });
@@ -37,17 +42,19 @@ function Editor(props: EditorProps) {
onCleanup(() => {
editorView?.destroy();
- doc?.destroy();
+ syncedDoc()?.finish();
});
return (
You are editing {props.file}.
-
+
+
+
);
}
diff --git a/src/sync.ts b/src/sync.ts
index 0e2ac23..c2eefff 100644
--- a/src/sync.ts
+++ b/src/sync.ts
@@ -1,15 +1,56 @@
+import _ from 'lodash';
import * as Y from 'yjs';
+import { openDB } from 'idb';
-const fakeStore = new Map();
+const DB_NAME = 'synced-docs';
+const LOCAL_UPDATE_STORE = 'local-updates';
-export function syncDoc(id: string, doc: Y.Doc) {
- const updates = fakeStore.get(id) ?? [];
- fakeStore.set(id, updates);
- for (const update of updates) {
- Y.applyUpdate(doc, update);
+const db = await openDB(DB_NAME, 2, {
+ upgrade(db, oldVersion, newVersion) {
+ console.log(`Running upgrade from ${oldVersion} to ${newVersion}`);
+ db.createObjectStore(LOCAL_UPDATE_STORE);
+ },
+});
+
+export class LocalDocument {
+ readonly id: string;
+ readonly doc: Y.Doc;
+ private updates: Uint8Array[];
+
+ constructor(id: string) {
+ this.id = id;
+ this.doc = new Y.Doc();
+ this.updates = [];
+
+ const flush = _.debounce(() => this.flush(), 1000);
+ this.doc.on('update', (update: Uint8Array) => {
+ this.updates.push(update);
+ flush();
+ });
}
- doc.on('update', (update: Uint8Array) => {
- updates.push(update);
- });
+ public static async load(id: string): Promise {
+ const updates: Uint8Array[] = await db.getAll(
+ LOCAL_UPDATE_STORE,
+ IDBKeyRange.bound([id, -Infinity], [id, Infinity])
+ );
+ const result = new LocalDocument(id);
+ Y.applyUpdate(result.doc, Y.mergeUpdates(updates));
+ return result;
+ }
+
+ public async flush(): Promise {
+ const batch = this.updates;
+ this.updates = [];
+ if (batch.length > 0) {
+ const update = Y.mergeUpdates(batch);
+ const key = [this.id, new Date().valueOf()];
+ await db.add(LOCAL_UPDATE_STORE, update, key);
+ }
+ }
+
+ public async finish(): Promise {
+ await this.flush();
+ this.doc.destroy();
+ }
}