From cd6a8e392e5a9a29daf50630e9a32d3d9d71e63c Mon Sep 17 00:00:00 2001 From: Shibo Lyu Date: Wed, 2 Apr 2025 01:17:09 +0800 Subject: [PATCH] refactor[wip]: replace typewriter-editor with ProseMirror for rich text editing --- package.json | 9 +- pnpm-lock.yaml | 149 ++++++++++++------ .../components/RichTextInput/editorState.ts | 23 +++ src/lib/components/RichTextInput/schema.ts | 31 ++++ 4 files changed, 167 insertions(+), 45 deletions(-) create mode 100644 src/lib/components/RichTextInput/editorState.ts create mode 100644 src/lib/components/RichTextInput/schema.ts diff --git a/package.json b/package.json index 30244c6..60535a4 100644 --- a/package.json +++ b/package.json @@ -44,11 +44,18 @@ "bits-ui": "^1.3.15", "canonicalize": "^2.1.0", "idb": "^8.0.2", + "prosemirror-commands": "^1.7.0", + "prosemirror-history": "^1.4.1", + "prosemirror-keymap": "^1.2.2", + "prosemirror-model": "^1.25.0", + "prosemirror-schema-basic": "^1.2.4", + "prosemirror-state": "^1.4.3", + "prosemirror-transform": "^1.10.3", + "prosemirror-view": "^1.38.1", "svelte-boring-avatars": "^1.2.6", "svelte-hero-icons": "^5.2.0", "svelte-persisted-store": "^0.12.0", "tailwind-merge": "^3.1.0", - "typewriter-editor": "^0.12.9", "virtua": "^0.40.3", "zod": "^3.24.2" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 184a349..f11f652 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,30 @@ importers: idb: specifier: ^8.0.2 version: 8.0.2 + prosemirror-commands: + specifier: ^1.7.0 + version: 1.7.0 + prosemirror-history: + specifier: ^1.4.1 + version: 1.4.1 + prosemirror-keymap: + specifier: ^1.2.2 + version: 1.2.2 + prosemirror-model: + specifier: ^1.25.0 + version: 1.25.0 + prosemirror-schema-basic: + specifier: ^1.2.4 + version: 1.2.4 + prosemirror-state: + specifier: ^1.4.3 + version: 1.4.3 + prosemirror-transform: + specifier: ^1.10.3 + version: 1.10.3 + prosemirror-view: + specifier: ^1.38.1 + version: 1.38.1 svelte-boring-avatars: specifier: ^1.2.6 version: 1.2.6 @@ -38,9 +62,6 @@ importers: tailwind-merge: specifier: ^3.1.0 version: 3.1.0 - typewriter-editor: - specifier: ^0.12.9 - version: 0.12.9(svelte@5.25.6) virtua: specifier: ^0.40.3 version: 0.40.3(svelte@5.25.6) @@ -531,9 +552,6 @@ packages: '@polka/url@1.0.0-next.28': resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==} - '@popperjs/core@2.11.8': - resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} - '@rollup/pluginutils@4.2.1': resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} engines: {node: '>= 8.0.0'} @@ -818,12 +836,6 @@ packages: resolution: {integrity: sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typewriter/delta@1.2.4': - resolution: {integrity: sha512-mxWgwvahh2eZD1odJjedQT8Xv7qhSfiKhom5TlDmO8XBeYZNqjKbNIkyYdO+Lj5anwiYjqmSIq72D/dUD0bKMA==} - - '@typewriter/document@0.9.5': - resolution: {integrity: sha512-+9so8jKn26M+A4iTPFvY8izVVe2/viyIPwu4V2QYQnMuwSnybUjFMnzUp/wWlgz19CRPiM8KurGWc9oGzGh0Ig==} - '@vercel/nft@0.26.5': resolution: {integrity: sha512-NHxohEqad6Ra/r4lGknO52uc/GrWILXAMs1BB4401GTqww0fw1bAqzpG1XHuDO+dprg4GvsD9ZLLSsdo78p9hQ==} engines: {node: '>=16'} @@ -1044,9 +1056,6 @@ packages: devalue@5.1.1: resolution: {integrity: sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==} - easy-signal@4.1.6: - resolution: {integrity: sha512-CGHBWRS53EvTL5BN7t45qTvtfJNO4DkmFSuCdhdDZoSWGxOka3bvnHb0/Hv5dXT9jdbtw+u0lVl2qIwk17ZmSA==} - emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1154,9 +1163,6 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fast-diff@1.3.0: - resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} - fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} @@ -1521,6 +1527,9 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + orderedmap@2.1.1: + resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -1665,6 +1674,30 @@ packages: engines: {node: '>=14'} hasBin: true + prosemirror-commands@1.7.0: + resolution: {integrity: sha512-6toodS4R/Aah5pdsrIwnTYPEjW70SlO5a66oo5Kk+CIrgJz3ukOoS+FYDGqvQlAX5PxoGWDX1oD++tn5X3pyRA==} + + prosemirror-history@1.4.1: + resolution: {integrity: sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ==} + + prosemirror-keymap@1.2.2: + resolution: {integrity: sha512-EAlXoksqC6Vbocqc0GtzCruZEzYgrn+iiGnNjsJsH4mrnIGex4qbLdWWNza3AW5W36ZRrlBID0eM6bdKH4OStQ==} + + prosemirror-model@1.25.0: + resolution: {integrity: sha512-/8XUmxWf0pkj2BmtqZHYJipTBMHIdVjuvFzMvEoxrtyGNmfvdhBiRwYt/eFwy2wA9DtBW3RLqvZnjurEkHaFCw==} + + prosemirror-schema-basic@1.2.4: + resolution: {integrity: sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==} + + prosemirror-state@1.4.3: + resolution: {integrity: sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==} + + prosemirror-transform@1.10.3: + resolution: {integrity: sha512-Nhh/+1kZGRINbEHmVu39oynhcap4hWTs/BlU7NnxWj3+l0qi8I1mu67v6mMdEe/ltD8hHvU4FV6PHiCw2VSpMw==} + + prosemirror-view@1.38.1: + resolution: {integrity: sha512-4FH/uM1A4PNyrxXbD+RAbAsf0d/mM0D/wAKSVVWK7o0A9Q/oOXJBrw786mBf2Vnrs/Edly6dH6Z2gsb7zWwaUw==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -1702,6 +1735,9 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rope-sequence@1.3.4: + resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -1895,11 +1931,6 @@ packages: engines: {node: '>=14.17'} hasBin: true - typewriter-editor@0.12.9: - resolution: {integrity: sha512-Mq+WyCsd4JWSib3JMWF9odyRdD3UeIRA98ybRw/SvlpC+3L+Muwo3Nl5n2ntE3YUuOhqeUPemi1pMlnqY09q3A==} - peerDependencies: - svelte: '>=3.43.0 <6' - uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -2007,6 +2038,9 @@ packages: jsdom: optional: true + w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -2342,8 +2376,6 @@ snapshots: '@polka/url@1.0.0-next.28': {} - '@popperjs/core@2.11.8': {} - '@rollup/pluginutils@4.2.1': dependencies: estree-walker: 2.0.2 @@ -2621,14 +2653,6 @@ snapshots: '@typescript-eslint/types': 8.29.0 eslint-visitor-keys: 4.2.0 - '@typewriter/delta@1.2.4': - dependencies: - fast-diff: 1.3.0 - - '@typewriter/document@0.9.5': - dependencies: - '@typewriter/delta': 1.2.4 - '@vercel/nft@0.26.5': dependencies: '@mapbox/node-pre-gyp': 1.0.11 @@ -2843,8 +2867,6 @@ snapshots: devalue@5.1.1: {} - easy-signal@4.1.6: {} - emoji-regex@8.0.0: {} enhanced-resolve@5.18.1: @@ -3022,8 +3044,6 @@ snapshots: fast-deep-equal@3.1.3: {} - fast-diff@1.3.0: {} - fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -3329,6 +3349,8 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + orderedmap@2.1.1: {} + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -3401,6 +3423,48 @@ snapshots: prettier@3.5.3: {} + prosemirror-commands@1.7.0: + dependencies: + prosemirror-model: 1.25.0 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.3 + + prosemirror-history@1.4.1: + dependencies: + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.3 + prosemirror-view: 1.38.1 + rope-sequence: 1.3.4 + + prosemirror-keymap@1.2.2: + dependencies: + prosemirror-state: 1.4.3 + w3c-keyname: 2.2.8 + + prosemirror-model@1.25.0: + dependencies: + orderedmap: 2.1.1 + + prosemirror-schema-basic@1.2.4: + dependencies: + prosemirror-model: 1.25.0 + + prosemirror-state@1.4.3: + dependencies: + prosemirror-model: 1.25.0 + prosemirror-transform: 1.10.3 + prosemirror-view: 1.38.1 + + prosemirror-transform@1.10.3: + dependencies: + prosemirror-model: 1.25.0 + + prosemirror-view@1.38.1: + dependencies: + prosemirror-model: 1.25.0 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.3 + punycode@2.3.1: {} queue-microtask@1.2.3: {} @@ -3449,6 +3513,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.38.0 fsevents: 2.3.3 + rope-sequence@1.3.4: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -3633,13 +3699,6 @@ snapshots: typescript@5.8.2: {} - typewriter-editor@0.12.9(svelte@5.25.6): - dependencies: - '@popperjs/core': 2.11.8 - '@typewriter/document': 0.9.5 - easy-signal: 4.1.6 - svelte: 5.25.6 - uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -3721,6 +3780,8 @@ snapshots: - tsx - yaml + w3c-keyname@2.2.8: {} + webidl-conversions@3.0.1: {} whatwg-url@5.0.0: diff --git a/src/lib/components/RichTextInput/editorState.ts b/src/lib/components/RichTextInput/editorState.ts new file mode 100644 index 0000000..f5385b0 --- /dev/null +++ b/src/lib/components/RichTextInput/editorState.ts @@ -0,0 +1,23 @@ +import { EditorState } from 'prosemirror-state'; +import { history, undo, redo } from 'prosemirror-history'; +import { keymap } from 'prosemirror-keymap'; +import { baseKeymap } from 'prosemirror-commands'; + +import { messageSchema } from './schema'; + +export function createMessageEditorState() { + const state = EditorState.create({ + schema: messageSchema, + plugins: [ + history(), + keymap({ + 'Mod-z': undo, + 'Mod-y': redo, + 'Mod-Shift-z': redo + }), + keymap(baseKeymap) + ] + }); + + return state; +} diff --git a/src/lib/components/RichTextInput/schema.ts b/src/lib/components/RichTextInput/schema.ts new file mode 100644 index 0000000..6fc9051 --- /dev/null +++ b/src/lib/components/RichTextInput/schema.ts @@ -0,0 +1,31 @@ +import { Schema } from 'prosemirror-model'; +import { nodes as basicNodes, marks as basicMarks } from 'prosemirror-schema-basic'; + +export const messageSchema = new Schema({ + nodes: { + doc: { content: 'block' }, // For now we only support a single block + paragragh: { + content: 'inline*' + }, + text: basicNodes.text + }, + marks: { + ...basicMarks, + underline: { + parseDOM: [{ tag: 'u' }], + toDOM: () => ['u', 0] + }, + strikethrough: { + parseDOM: [{ tag: 's' }], + toDOM: () => ['s', 0] + }, + tag: { + parseDOM: [{ tag: 'span[data-weblah-tag]' }], + toDOM: () => ['span', { 'data-weblah-richtext-tag': true }, 0] + }, + spoiler: { + parseDOM: [{ tag: 'span[data-weblah-spoiler]' }], + toDOM: () => ['span', { 'data-weblah-richtext-spoiler': true }, 0] + } + } +});