Use real persistence to IndexedDB
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
|
<link rel="icon" href="data:;base64,iVBORw0KGgo=" />
|
||||||
<title>Story Editor</title>
|
<title>Story Editor</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
|
<link rel="icon" href="data:;base64,iVBORw0KGgo=" />
|
||||||
<title>Story Editor</title>
|
<title>Story Editor</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
+22
-15
@@ -1,4 +1,3 @@
|
|||||||
import * as Y from 'yjs';
|
|
||||||
import { EditorState } from '@codemirror/state';
|
import { EditorState } from '@codemirror/state';
|
||||||
import { keymap, EditorView } from '@codemirror/view';
|
import { keymap, EditorView } from '@codemirror/view';
|
||||||
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands';
|
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands';
|
||||||
@@ -7,29 +6,35 @@ import {
|
|||||||
syntaxHighlighting,
|
syntaxHighlighting,
|
||||||
} from '@codemirror/language';
|
} from '@codemirror/language';
|
||||||
import { yCollab } from 'y-codemirror.next';
|
import { yCollab } from 'y-codemirror.next';
|
||||||
import { onCleanup, onMount } from 'solid-js';
|
import { createEffect, createResource, onCleanup, Show } from 'solid-js';
|
||||||
import { syncDoc } from '../sync';
|
import { LocalDocument } from '../sync';
|
||||||
|
|
||||||
interface EditorProps {
|
interface EditorProps {
|
||||||
file: string;
|
file: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Editor(props: EditorProps) {
|
function Editor(props: EditorProps) {
|
||||||
let doc: Y.Doc;
|
|
||||||
let editorDiv;
|
let editorDiv;
|
||||||
let editorView: EditorView;
|
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({
|
const state = EditorState.create({
|
||||||
doc: doc.getText().toString(),
|
doc: ydoc.getText().toString(),
|
||||||
extensions: [
|
extensions: [
|
||||||
EditorView.lineWrapping,
|
EditorView.lineWrapping,
|
||||||
history(),
|
history(),
|
||||||
keymap.of([...defaultKeymap, ...historyKeymap]),
|
keymap.of([...defaultKeymap, ...historyKeymap]),
|
||||||
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
|
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
|
||||||
yCollab(doc.getText(), null),
|
yCollab(ydoc.getText(), null),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
editorView = new EditorView({ state, parent: editorDiv });
|
editorView = new EditorView({ state, parent: editorDiv });
|
||||||
@@ -37,17 +42,19 @@ function Editor(props: EditorProps) {
|
|||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
editorView?.destroy();
|
editorView?.destroy();
|
||||||
doc?.destroy();
|
syncedDoc()?.finish();
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>You are editing {props.file}.</p>
|
<p>You are editing {props.file}.</p>
|
||||||
<div
|
<Show when={syncedDoc()}>
|
||||||
ref={editorDiv}
|
<div
|
||||||
class="editor-component"
|
ref={editorDiv}
|
||||||
style={{ border: '1px solid black' }}
|
class="editor-component"
|
||||||
/>
|
style={{ border: '1px solid black' }}
|
||||||
|
/>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+50
-9
@@ -1,15 +1,56 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
import * as Y from 'yjs';
|
import * as Y from 'yjs';
|
||||||
|
import { openDB } from 'idb';
|
||||||
|
|
||||||
const fakeStore = new Map<string, Uint8Array[]>();
|
const DB_NAME = 'synced-docs';
|
||||||
|
const LOCAL_UPDATE_STORE = 'local-updates';
|
||||||
|
|
||||||
export function syncDoc(id: string, doc: Y.Doc) {
|
const db = await openDB(DB_NAME, 2, {
|
||||||
const updates = fakeStore.get(id) ?? [];
|
upgrade(db, oldVersion, newVersion) {
|
||||||
fakeStore.set(id, updates);
|
console.log(`Running upgrade from ${oldVersion} to ${newVersion}`);
|
||||||
for (const update of updates) {
|
db.createObjectStore(LOCAL_UPDATE_STORE);
|
||||||
Y.applyUpdate(doc, update);
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
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) => {
|
public static async load(id: string): Promise<LocalDocument> {
|
||||||
updates.push(update);
|
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<void> {
|
||||||
|
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<void> {
|
||||||
|
await this.flush();
|
||||||
|
this.doc.destroy();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user