Просмотр исходного кода

enh: proper undo/redo note editor support

Timothy Jaeryang Baek 2 месяцев назад
Родитель
Сommit
3bc5485867

+ 4 - 3
package-lock.json

@@ -24,6 +24,7 @@
 				"@tiptap/extension-code-block-lowlight": "^2.11.9",
 				"@tiptap/extension-floating-menu": "^2.25.0",
 				"@tiptap/extension-highlight": "^2.10.0",
+				"@tiptap/extension-history": "^2.25.1",
 				"@tiptap/extension-link": "^2.25.0",
 				"@tiptap/extension-placeholder": "^2.10.0",
 				"@tiptap/extension-table": "^2.12.0",
@@ -3330,9 +3331,9 @@
 			}
 		},
 		"node_modules/@tiptap/extension-history": {
-			"version": "2.10.0",
-			"resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.10.0.tgz",
-			"integrity": "sha512-5aYOmxqaCnw7e7wmWqFZmkpYCxxDjEzFbgVI6WknqNwqeOizR4+YJf3aAt/lTbksLJe47XF+NBX51gOm/ZBCiw==",
+			"version": "2.25.1",
+			"resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.25.1.tgz",
+			"integrity": "sha512-ZoxxOAObk1U8H3d+XEG0MjccJN0ViGIKEZqnLUSswmVweYPdkJG2WF2pEif9hpwJONslvLTKa+f8jwK5LEnJLQ==",
 			"license": "MIT",
 			"funding": {
 				"type": "github",

+ 1 - 0
package.json

@@ -68,6 +68,7 @@
 		"@tiptap/extension-code-block-lowlight": "^2.11.9",
 		"@tiptap/extension-floating-menu": "^2.25.0",
 		"@tiptap/extension-highlight": "^2.10.0",
+		"@tiptap/extension-history": "^2.25.1",
 		"@tiptap/extension-link": "^2.25.0",
 		"@tiptap/extension-placeholder": "^2.10.0",
 		"@tiptap/extension-table": "^2.12.0",

+ 73 - 0
src/lib/components/common/RichTextInput.svelte

@@ -50,16 +50,20 @@
 
 	import { onMount, onDestroy, tick, getContext } from 'svelte';
 	import { createEventDispatcher } from 'svelte';
+	import { socket } from '$lib/stores';
 
 	const i18n = getContext('i18n');
 	const eventDispatch = createEventDispatcher();
 
 	import { Fragment, DOMParser } from 'prosemirror-model';
 	import { EditorState, Plugin, PluginKey, TextSelection, Selection } from 'prosemirror-state';
+	import { receiveTransaction, sendableSteps, getVersion } from 'prosemirror-collab';
+	import { Step } from 'prosemirror-transform';
 	import { Decoration, DecorationSet } from 'prosemirror-view';
 	import { Editor } from '@tiptap/core';
 
 	import { AIAutocompletion } from './RichTextInput/AutoCompletion.js';
+	import History from '@tiptap/extension-history';
 	import Table from '@tiptap/extension-table';
 	import TableRow from '@tiptap/extension-table-row';
 	import TableHeader from '@tiptap/extension-table-header';
@@ -118,6 +122,75 @@
 	export let largeTextAsFile = false;
 	export let insertPromptAsRichText = false;
 
+	let isConnected = false;
+	let collaborators = new Map();
+	let version = 0;
+
+	// Custom collaboration plugin
+	const collaborationPlugin = () => {
+		return new Plugin({
+			key: new PluginKey('collaboration'),
+			state: {
+				init: () => ({ version: 0 }),
+				apply: (tr, pluginState) => {
+					const newState = { ...pluginState };
+
+					if (tr.getMeta('collaboration')) {
+						newState.version = tr.getMeta('collaboration').version;
+					}
+
+					return newState;
+				}
+			},
+			view: () => ({
+				update: (view, prevState) => {
+					const sendable = sendableSteps(view.state);
+					if (sendable) {
+						$socket.emit('document_steps', {
+							document_id: documentId,
+							user_id: userId,
+							version: sendable.version,
+							steps: sendable.steps.map((step) => step.toJSON()),
+							clientID: sendable.clientID
+						});
+					}
+				}
+			})
+		});
+	};
+
+	function handleDocumentSteps(data) {
+		if (data.user_id !== userId && editor) {
+			const steps = data.steps.map((stepJSON) => Step.fromJSON(editor.schema, stepJSON));
+			const tr = receiveTransaction(editor.state, steps, data.clientID);
+
+			if (tr) {
+				editor.view.dispatch(tr);
+			}
+		}
+	}
+
+	function handleDocumentState(data) {
+		version = data.version;
+		if (data.content && editor) {
+			editor.commands.setContent(data.content);
+		}
+		isConnected = true;
+	}
+
+	function handleUserJoined(data) {
+		collaborators.set(data.user_id, {
+			name: data.user_name,
+			color: data.user_color
+		});
+		collaborators = collaborators;
+	}
+
+	function handleUserLeft(data) {
+		collaborators.delete(data.user_id);
+		collaborators = collaborators;
+	}
+
 	let floatingMenuElement = null;
 	let bubbleMenuElement = null;
 	let element;

+ 7 - 6
src/lib/components/notes/NoteEditor.svelte

@@ -772,16 +772,16 @@ Provide the enhanced notes in markdown format. Use markdown syntax for headings,
 							/>
 
 							<div class="flex items-center gap-0.5 translate-x-1">
-								{#if note.data?.versions?.length > 0}
+								{#if editor}
 									<div>
 										<div class="flex items-center gap-0.5 self-center min-w-fit" dir="ltr">
 											<button
 												class="self-center p-1 hover:enabled:bg-black/5 dark:hover:enabled:bg-white/5 dark:hover:enabled:text-white hover:enabled:text-black rounded-md transition disabled:cursor-not-allowed disabled:text-gray-500 disabled:hover:text-gray-500"
 												on:click={() => {
-													versionNavigateHandler('prev');
+													editor.chain().focus().undo().run();
+													// versionNavigateHandler('prev');
 												}}
-												disabled={(versionIdx === null && note.data.versions.length === 0) ||
-													versionIdx === 0}
+												disabled={!editor.can().undo()}
 											>
 												<ArrowUturnLeft className="size-4" />
 											</button>
@@ -789,9 +789,10 @@ Provide the enhanced notes in markdown format. Use markdown syntax for headings,
 											<button
 												class="self-center p-1 hover:enabled:bg-black/5 dark:hover:enabled:bg-white/5 dark:hover:enabled:text-white hover:enabled:text-black rounded-md transition disabled:cursor-not-allowed disabled:text-gray-500 disabled:hover:text-gray-500"
 												on:click={() => {
-													versionNavigateHandler('next');
+													editor.chain().focus().redo().run();
+													// versionNavigateHandler('next');
 												}}
-												disabled={versionIdx >= note.data.versions.length || versionIdx === null}
+												disabled={!editor.can().redo()}
 											>
 												<ArrowUturnRight className="size-4" />
 											</button>