refactor[wip]: replace typewriter-editor with ProseMirror for rich text editing

This commit is contained in:
Shibo Lyu 2025-04-02 01:17:09 +08:00
parent 7299c1dee0
commit cd6a8e392e
4 changed files with 167 additions and 45 deletions

View file

@ -44,11 +44,18 @@
"bits-ui": "^1.3.15", "bits-ui": "^1.3.15",
"canonicalize": "^2.1.0", "canonicalize": "^2.1.0",
"idb": "^8.0.2", "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-boring-avatars": "^1.2.6",
"svelte-hero-icons": "^5.2.0", "svelte-hero-icons": "^5.2.0",
"svelte-persisted-store": "^0.12.0", "svelte-persisted-store": "^0.12.0",
"tailwind-merge": "^3.1.0", "tailwind-merge": "^3.1.0",
"typewriter-editor": "^0.12.9",
"virtua": "^0.40.3", "virtua": "^0.40.3",
"zod": "^3.24.2" "zod": "^3.24.2"
} }

149
pnpm-lock.yaml generated
View file

@ -26,6 +26,30 @@ importers:
idb: idb:
specifier: ^8.0.2 specifier: ^8.0.2
version: 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: svelte-boring-avatars:
specifier: ^1.2.6 specifier: ^1.2.6
version: 1.2.6 version: 1.2.6
@ -38,9 +62,6 @@ importers:
tailwind-merge: tailwind-merge:
specifier: ^3.1.0 specifier: ^3.1.0
version: 3.1.0 version: 3.1.0
typewriter-editor:
specifier: ^0.12.9
version: 0.12.9(svelte@5.25.6)
virtua: virtua:
specifier: ^0.40.3 specifier: ^0.40.3
version: 0.40.3(svelte@5.25.6) version: 0.40.3(svelte@5.25.6)
@ -531,9 +552,6 @@ packages:
'@polka/url@1.0.0-next.28': '@polka/url@1.0.0-next.28':
resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==} resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==}
'@popperjs/core@2.11.8':
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
'@rollup/pluginutils@4.2.1': '@rollup/pluginutils@4.2.1':
resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==}
engines: {node: '>= 8.0.0'} engines: {node: '>= 8.0.0'}
@ -818,12 +836,6 @@ packages:
resolution: {integrity: sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg==} resolution: {integrity: sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 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': '@vercel/nft@0.26.5':
resolution: {integrity: sha512-NHxohEqad6Ra/r4lGknO52uc/GrWILXAMs1BB4401GTqww0fw1bAqzpG1XHuDO+dprg4GvsD9ZLLSsdo78p9hQ==} resolution: {integrity: sha512-NHxohEqad6Ra/r4lGknO52uc/GrWILXAMs1BB4401GTqww0fw1bAqzpG1XHuDO+dprg4GvsD9ZLLSsdo78p9hQ==}
engines: {node: '>=16'} engines: {node: '>=16'}
@ -1044,9 +1056,6 @@ packages:
devalue@5.1.1: devalue@5.1.1:
resolution: {integrity: sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==} 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: emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@ -1154,9 +1163,6 @@ packages:
fast-deep-equal@3.1.3: fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
fast-diff@1.3.0:
resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==}
fast-glob@3.3.3: fast-glob@3.3.3:
resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
engines: {node: '>=8.6.0'} engines: {node: '>=8.6.0'}
@ -1521,6 +1527,9 @@ packages:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
orderedmap@2.1.1:
resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==}
p-limit@3.1.0: p-limit@3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -1665,6 +1674,30 @@ packages:
engines: {node: '>=14'} engines: {node: '>=14'}
hasBin: true 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: punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -1702,6 +1735,9 @@ packages:
engines: {node: '>=18.0.0', npm: '>=8.0.0'} engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true hasBin: true
rope-sequence@1.3.4:
resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==}
run-parallel@1.2.0: run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
@ -1895,11 +1931,6 @@ packages:
engines: {node: '>=14.17'} engines: {node: '>=14.17'}
hasBin: true 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: uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
@ -2007,6 +2038,9 @@ packages:
jsdom: jsdom:
optional: true optional: true
w3c-keyname@2.2.8:
resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
webidl-conversions@3.0.1: webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
@ -2342,8 +2376,6 @@ snapshots:
'@polka/url@1.0.0-next.28': {} '@polka/url@1.0.0-next.28': {}
'@popperjs/core@2.11.8': {}
'@rollup/pluginutils@4.2.1': '@rollup/pluginutils@4.2.1':
dependencies: dependencies:
estree-walker: 2.0.2 estree-walker: 2.0.2
@ -2621,14 +2653,6 @@ snapshots:
'@typescript-eslint/types': 8.29.0 '@typescript-eslint/types': 8.29.0
eslint-visitor-keys: 4.2.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': '@vercel/nft@0.26.5':
dependencies: dependencies:
'@mapbox/node-pre-gyp': 1.0.11 '@mapbox/node-pre-gyp': 1.0.11
@ -2843,8 +2867,6 @@ snapshots:
devalue@5.1.1: {} devalue@5.1.1: {}
easy-signal@4.1.6: {}
emoji-regex@8.0.0: {} emoji-regex@8.0.0: {}
enhanced-resolve@5.18.1: enhanced-resolve@5.18.1:
@ -3022,8 +3044,6 @@ snapshots:
fast-deep-equal@3.1.3: {} fast-deep-equal@3.1.3: {}
fast-diff@1.3.0: {}
fast-glob@3.3.3: fast-glob@3.3.3:
dependencies: dependencies:
'@nodelib/fs.stat': 2.0.5 '@nodelib/fs.stat': 2.0.5
@ -3329,6 +3349,8 @@ snapshots:
type-check: 0.4.0 type-check: 0.4.0
word-wrap: 1.2.5 word-wrap: 1.2.5
orderedmap@2.1.1: {}
p-limit@3.1.0: p-limit@3.1.0:
dependencies: dependencies:
yocto-queue: 0.1.0 yocto-queue: 0.1.0
@ -3401,6 +3423,48 @@ snapshots:
prettier@3.5.3: {} 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: {} punycode@2.3.1: {}
queue-microtask@1.2.3: {} queue-microtask@1.2.3: {}
@ -3449,6 +3513,8 @@ snapshots:
'@rollup/rollup-win32-x64-msvc': 4.38.0 '@rollup/rollup-win32-x64-msvc': 4.38.0
fsevents: 2.3.3 fsevents: 2.3.3
rope-sequence@1.3.4: {}
run-parallel@1.2.0: run-parallel@1.2.0:
dependencies: dependencies:
queue-microtask: 1.2.3 queue-microtask: 1.2.3
@ -3633,13 +3699,6 @@ snapshots:
typescript@5.8.2: {} 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: uri-js@4.4.1:
dependencies: dependencies:
punycode: 2.3.1 punycode: 2.3.1
@ -3721,6 +3780,8 @@ snapshots:
- tsx - tsx
- yaml - yaml
w3c-keyname@2.2.8: {}
webidl-conversions@3.0.1: {} webidl-conversions@3.0.1: {}
whatwg-url@5.0.0: whatwg-url@5.0.0:

View file

@ -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;
}

View file

@ -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]
}
}
});