diff --git a/package.json b/package.json index 153d103f6f..858cca796c 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/patches/y-prosemirror+1.3.7.patch b/patches/y-prosemirror+1.3.7.patch new file mode 100644 index 0000000000..ae8654c0ba --- /dev/null +++ b/patches/y-prosemirror+1.3.7.patch @@ -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} 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>} ++ */ ++ 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} 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. diff --git a/server/models/helpers/DocumentHelper.tsx b/server/models/helpers/DocumentHelper.tsx index c89115ff84..24ca0b281b 100644 --- a/server/models/helpers/DocumentHelper.tsx +++ b/server/models/helpers/DocumentHelper.tsx @@ -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); diff --git a/server/scripts/20221008000000-backfill-crdt.ts b/server/scripts/20221008000000-backfill-crdt.ts index f1feda6157..d83d36884a 100644 --- a/server/scripts/20221008000000-backfill-crdt.ts +++ b/server/scripts/20221008000000-backfill-crdt.ts @@ -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); diff --git a/yarn.lock b/yarn.lock index cf4009434b..5df5de117e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"