fix: Remote table modifications cause CellSelection to be lost (#9596)

* fix: Table modifications lose any existing CellSelection

* tsc

* doc

* Remove unused imports
This commit is contained in:
Tom Moor
2025-07-10 08:46:08 -04:00
committed by GitHub
parent 210e960a98
commit 7dd0616b8c
5 changed files with 192 additions and 19 deletions
+3 -3
View File
@@ -90,13 +90,13 @@
"@outlinewiki/passport-azure-ad-oauth2": "^0.1.0",
"@radix-ui/react-collapsible": "^1.1.11",
"@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-one-time-password-field": "^0.1.7",
"@radix-ui/react-popover": "^1.1.14",
"@radix-ui/react-select": "^2.1.4",
"@radix-ui/react-switch": "^1.2.5",
"@radix-ui/react-tabs": "^1.1.12",
"@radix-ui/react-tooltip": "^1.2.7",
"@radix-ui/react-visually-hidden": "^1.2.2",
"@radix-ui/react-one-time-password-field": "^0.1.7",
"@renderlesskit/react": "^0.11.0",
"@sentry/node": "^7.120.3",
"@sentry/react": "^7.120.3",
@@ -183,7 +183,7 @@
"passport-google-oauth2": "^0.2.0",
"passport-oauth2": "^1.8.0",
"passport-slack-oauth2": "^1.2.0",
"patch-package": "^7.0.2",
"patch-package": "^8.0.0",
"pg": "^8.15.6",
"pg-tsquery": "^8.4.2",
"pluralize": "^8.0.0",
@@ -265,7 +265,7 @@
"winston": "^3.17.0",
"ws": "^7.5.10",
"y-indexeddb": "^9.0.11",
"y-prosemirror": "^1.2.12",
"y-prosemirror": "^1.3.7",
"y-protocols": "^1.0.6",
"yauzl": "^2.10.0",
"yjs": "^13.6.1",
+150
View File
@@ -0,0 +1,150 @@
diff --git a/node_modules/y-prosemirror/src/plugins/sync-plugin.js b/node_modules/y-prosemirror/src/plugins/sync-plugin.js
index dccfb76..45b6f1a 100644
--- a/node_modules/y-prosemirror/src/plugins/sync-plugin.js
+++ b/node_modules/y-prosemirror/src/plugins/sync-plugin.js
@@ -4,7 +4,7 @@
import { createMutex } from 'lib0/mutex'
import * as PModel from 'prosemirror-model'
-import { AllSelection, Plugin, TextSelection, NodeSelection } from "prosemirror-state"; // eslint-disable-line
+import { Plugin, TextSelection } from "prosemirror-state"; // eslint-disable-line
import * as math from 'lib0/math'
import * as object from 'lib0/object'
import * as set from 'lib0/set'
@@ -242,40 +242,24 @@ export const ySyncPlugin = (yXmlFragment, {
}
/**
- * @param {import('prosemirror-state').Transaction} tr
- * @param {ReturnType<typeof getRelativeSelection>} relSel
- * @param {ProsemirrorBinding} binding
+ * NOTE: restoreRelativeSelection, getRelativeSelection, and relativePositionStore are
+ * from the PR here: https://github.com/yjs/y-prosemirror/pull/182 in order to support
+ * CellSelection and other selection types.
+ *
+ * If the PR is merged this patch can be removed.
*/
-const restoreRelativeSelection = (tr, relSel, binding) => {
- if (relSel !== null && relSel.anchor !== null && relSel.head !== null) {
- if (relSel.type === 'all') {
- tr.setSelection(new AllSelection(tr.doc))
- } else if (relSel.type === 'node') {
- const anchor = relativePositionToAbsolutePosition(
- binding.doc,
- binding.type,
- relSel.anchor,
- binding.mapping
- )
- tr.setSelection(NodeSelection.create(tr.doc, anchor))
- } else {
- const anchor = relativePositionToAbsolutePosition(
- binding.doc,
- binding.type,
- relSel.anchor,
- binding.mapping
- )
- const head = relativePositionToAbsolutePosition(
- binding.doc,
- binding.type,
- relSel.head,
- binding.mapping
- )
- if (anchor !== null && head !== null) {
- const sel = TextSelection.between(tr.doc.resolve(anchor), tr.doc.resolve(head))
- tr.setSelection(sel)
- }
- }
+
+/**
+ * This will return a function that can be used to convert a relative position to an absolute position.
+ * @param {ProsemirrorBinding} pmbinding
+ * @param {number} pos
+ * @returns {(binding?: ProsemirrorBinding) => number}
+ */
+export const relativePositionStore = (pmbinding, pos) => {
+ const relPos = absolutePositionToRelativePosition(pos, pmbinding.type, pmbinding.mapping)
+
+ return (binding = pmbinding) => {
+ return relativePositionToAbsolutePosition(binding.doc, binding.type, relPos, binding.mapping)
}
}
@@ -283,19 +267,65 @@ const restoreRelativeSelection = (tr, relSel, binding) => {
* @param {ProsemirrorBinding} pmbinding
* @param {import('prosemirror-state').EditorState} state
*/
-export const getRelativeSelection = (pmbinding, state) => ({
- type: /** @type {any} */ (state.selection).jsonID,
- anchor: absolutePositionToRelativePosition(
- state.selection.anchor,
- pmbinding.type,
- pmbinding.mapping
- ),
- head: absolutePositionToRelativePosition(
- state.selection.head,
- pmbinding.type,
- pmbinding.mapping
- )
-})
+export const getRelativeSelection = (pmbinding, state) => {
+ /**
+ * @type {Map<number, (binding?: ProsemirrorBinding) => number>}
+ */
+ const mapping = new Map()
+ /**
+ * We take a bookmark of the current selection
+ * and map it to it's relative positions,
+ * so we can restore the selection in the future.
+ */
+ const bookmark = state.selection.getBookmark().map({
+ map (pos) {
+ // Store the relative position using the position as the key
+ mapping.set(pos, relativePositionStore(pmbinding, pos))
+
+ // Pass through the position unchanged, since we are just using it to store the relative position
+ return pos
+ },
+ mapResult (pos) {
+ // Call the map function to store the relative position
+ return { pos: this.map(pos), deleted: false, deletedAcross: false, deletedAfter: false, deletedBefore: false }
+ }
+ })
+
+ return {
+ mapping,
+ bookmark
+ }
+}
+
+/**
+ * Restores the relative selection to the prosemirror view.
+ * @param {import('prosemirror-state').Transaction} tr
+ * @param {ReturnType<typeof getRelativeSelection>} relSel
+ * @param {ProsemirrorBinding} pmbinding
+ */
+const restoreRelativeSelection = (tr, { mapping, bookmark }, pmbinding) => {
+ /**
+ * We can now try to map the bookmark into the appropriate absolute positions.
+ */
+ const selection = bookmark.map({
+ map (pos) {
+ const getPos = mapping.get(pos)
+ if (!getPos) {
+ throw new Error('Relative position not set')
+ }
+ return getPos(pmbinding)
+ },
+ mapResult (originalPos) {
+ const mappedPos = this.map(originalPos)
+ if (mappedPos === null) {
+ return { pos: originalPos, deleted: true, deletedAcross: true, deletedAfter: true, deletedBefore: true }
+ }
+ return { pos: mappedPos, deleted: false, deletedAcross: false, deletedAfter: false, deletedBefore: false }
+ }
+ }).resolve(tr.doc)
+
+ tr.setSelection(selection)
+}
/**
* Binding for prosemirror.
+4 -1
View File
@@ -478,7 +478,10 @@ export class DocumentHelper {
}
// apply new document to existing ydoc
updateYFragment(type.doc, type, doc, new Map());
updateYFragment(type.doc, type, doc, {
mapping: new Map(),
isOMark: new Map(),
});
const state = Y.encodeStateAsUpdate(ydoc);
@@ -47,7 +47,10 @@ export default async function main(exit = false) {
}
// apply new document to existing ydoc
updateYFragment(type.doc, type, doc, new Map());
updateYFragment(type.doc, type, doc, {
mapping: new Map(),
isOMark: new Map(),
});
const state = Y.encodeStateAsUpdate(ydoc);
document.state = Buffer.from(state);
+31 -14
View File
@@ -11219,6 +11219,17 @@ json-stable-stringify-without-jsonify@^1.0.1:
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
integrity "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="
json-stable-stringify@^1.0.2:
version "1.3.0"
resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz#8903cfac42ea1a0f97f35d63a4ce0518f0cc6a70"
integrity sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==
dependencies:
call-bind "^1.0.8"
call-bound "^1.0.4"
isarray "^2.0.5"
jsonify "^0.0.1"
object-keys "^1.1.1"
json5@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
@@ -11259,6 +11270,11 @@ jsonfile@^6.0.1:
optionalDependencies:
graceful-fs "^4.1.6"
jsonify@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.1.tgz#2aa3111dae3d34a0f151c63f3a45d995d9420978"
integrity sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==
jsonpointer@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.1.tgz#2110e0af0900fd37467b5907ecd13a7884a1b559"
@@ -11631,10 +11647,10 @@ levn@~0.3.0:
prelude-ls "~1.1.2"
type-check "~0.3.2"
lib0@^0.2.42, lib0@^0.2.46, lib0@^0.2.47, lib0@^0.2.74, lib0@^0.2.85:
version "0.2.88"
resolved "https://registry.yarnpkg.com/lib0/-/lib0-0.2.88.tgz#18618e0c3b63f6260255eb760f9247d9cc6c6a5b"
integrity sha512-KyroiEvCeZcZEMx5Ys+b4u4eEBbA1ch7XUaBhYpwa/nPMrzTjUhI4RfcytmQfYoTBPcdyx+FX6WFNIoNuJzJfQ==
lib0@^0.2.109, lib0@^0.2.46, lib0@^0.2.47, lib0@^0.2.74, lib0@^0.2.85:
version "0.2.109"
resolved "https://registry.yarnpkg.com/lib0/-/lib0-0.2.109.tgz#78a4a15cfb9de489a031396c6497eefe620ca794"
integrity sha512-jP0gbnyW0kwlx1Atc4dcHkBbrVAkdHjuyHxtClUPYla7qCmwIif1qZ6vQeJdR5FrOVdn26HvQT0ko01rgW7/Xw==
dependencies:
isomorphic.js "^0.2.4"
@@ -12913,10 +12929,10 @@ passthrough-counter@^1.0.0:
resolved "https://registry.yarnpkg.com/passthrough-counter/-/passthrough-counter-1.0.0.tgz#1967d9e66da572b5c023c787db112a387ab166fa"
integrity "sha1-GWfZ5m2lcrXAI8eH2xEqOHqxZvo= sha512-Wy8PXTLqPAN0oEgBrlnsXPMww3SYJ44tQ8aVrGAI4h4JZYCS0oYqsPqtPR8OhJpv6qFbpbB7XAn0liKV7EXubA=="
patch-package@^7.0.2:
version "7.0.2"
resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-7.0.2.tgz#c01589bb6964854b5210506a5845d47900641f5a"
integrity "sha1-wBWJu2lkhUtSEFBqWEXUeQBkH1o= sha512-PMYfL8LXxGIRmxXLqlEaBxzKPu7/SdP13ld6GSfAUJUZRmBDPp8chZs0dpzaAFn9TSPnFiMwkC6PJt6pBiAl8Q=="
patch-package@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-8.0.0.tgz#d191e2f1b6e06a4624a0116bcb88edd6714ede61"
integrity sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==
dependencies:
"@yarnpkg/lockfile" "^1.1.0"
chalk "^4.1.2"
@@ -12924,6 +12940,7 @@ patch-package@^7.0.2:
cross-spawn "^7.0.3"
find-yarn-workspace-root "^2.0.0"
fs-extra "^9.0.0"
json-stable-stringify "^1.0.2"
klaw-sync "^6.0.0"
minimist "^1.2.6"
open "^7.4.2"
@@ -13237,7 +13254,7 @@ postgres-interval@^1.1.0:
postinstall-postinstall@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz#4f7f77441ef539d1512c40bd04c71b06a4704ca3"
integrity "sha1-T393RB71OdFRLEC9BMcbBqRwTKM= sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ=="
integrity sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ==
pprof-format@^2.1.0:
version "2.1.0"
@@ -16476,12 +16493,12 @@ y-indexeddb@^9.0.11:
dependencies:
lib0 "^0.2.74"
y-prosemirror@^1.2.12:
version "1.2.12"
resolved "https://registry.yarnpkg.com/y-prosemirror/-/y-prosemirror-1.2.12.tgz#3bb3bd9def611a90e8b7ac6a7e46a6230f238746"
integrity sha512-UMnUIR5ppVn30n2kzeeBQEaesWGe4fsbnlch1HnNa3/snJMoOn7M7dieuS+o1OQwKs1dqx2eT3gFfGF2aOaQdw==
y-prosemirror@^1.3.7:
version "1.3.7"
resolved "https://registry.yarnpkg.com/y-prosemirror/-/y-prosemirror-1.3.7.tgz#f88e553da4ea33278b114cf0b6a0ea978b154e84"
integrity sha512-NpM99WSdD4Fx4if5xOMDpPtU3oAmTSjlzh5U4353ABbRHl1HtAFUx6HlebLZfyFxXN9jzKMDkVbcRjqOZVkYQg==
dependencies:
lib0 "^0.2.42"
lib0 "^0.2.109"
y-protocols@^1.0.6:
version "1.0.6"