From 4b27feff61455e0c670556ab635a1750fe3f030a Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Mon, 20 Jun 2022 16:36:25 +0300 Subject: [PATCH] fix: Enable `documents.update` with collab editing (#3647) * fix: Enable documents.update with collab editing * jest cannot deal with ESM deps --- server/commands/documentUpdater.ts | 8 +++++--- server/models/Document.ts | 28 +++++++++++++++++++++++++++- server/routes/api/documents.ts | 1 + server/test/setup.ts | 2 ++ 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/server/commands/documentUpdater.ts b/server/commands/documentUpdater.ts index 80e3ff1bb..61d65ecc7 100644 --- a/server/commands/documentUpdater.ts +++ b/server/commands/documentUpdater.ts @@ -60,10 +60,12 @@ export default async function documentUpdater({ if (fullWidth !== undefined) { document.fullWidth = fullWidth; } - if (!user.team?.collaborativeEditing) { - if (append) { + if (text !== undefined) { + if (user.team?.collaborativeEditing) { + document.updateFromMarkdown(text, append); + } else if (append) { document.text += text; - } else if (text !== undefined) { + } else { document.text = text; } } diff --git a/server/models/Document.ts b/server/models/Document.ts index 55f0b8801..5af07d18f 100644 --- a/server/models/Document.ts +++ b/server/models/Document.ts @@ -1,3 +1,4 @@ +import { updateYFragment } from "@getoutline/y-prosemirror"; import removeMarkdown from "@tommoor/remove-markdown"; import invariant from "invariant"; import { compact, find, map, uniq } from "lodash"; @@ -31,12 +32,14 @@ import { } from "sequelize-typescript"; import MarkdownSerializer from "slate-md-serializer"; import isUUID from "validator/lib/isUUID"; +import * as Y from "yjs"; import { MAX_TITLE_LENGTH } from "@shared/constants"; import { DateFilter } from "@shared/types"; import getTasks from "@shared/utils/getTasks"; import parseTitle from "@shared/utils/parseTitle"; import unescape from "@shared/utils/unescape"; import { SLUG_URL_REGEX } from "@shared/utils/urlHelpers"; +import { parser } from "@server/editor"; import slugify from "@server/utils/slugify"; import Backlink from "./Backlink"; import Collection from "./Collection"; @@ -413,12 +416,13 @@ class Document extends ParanoidModel { id: string, options: FindOptions & { userId?: string; + includeState?: boolean; } = {} ): Promise { // allow default preloading of collection membership if `userId` is passed in find options // almost every endpoint needs the collection membership to determine policy permissions. const scope = this.scope([ - "withoutState", + ...(options.includeState ? [] : ["withoutState"]), "withDrafts", { method: ["withCollectionPermissions", options.userId, options.paranoid], @@ -704,6 +708,28 @@ class Document extends ParanoidModel { // instance methods + updateFromMarkdown = (text: string, append = false) => { + this.text = append ? this.text + text : text; + + if (this.state) { + const ydoc = new Y.Doc(); + Y.applyUpdate(ydoc, this.state); + const type = ydoc.get("default", Y.XmlFragment) as Y.XmlFragment; + const doc = parser.parse(this.text); + + if (!type.doc) { + throw new Error("type.doc not found"); + } + + // apply new document to existing ydoc + updateYFragment(type.doc, type, doc, new Map()); + + const state = Y.encodeStateAsUpdate(ydoc); + this.state = Buffer.from(state); + this.changed("state", true); + } + }; + toMarkdown = () => { const text = unescape(this.text); diff --git a/server/routes/api/documents.ts b/server/routes/api/documents.ts index c63bbc795..739b6873d 100644 --- a/server/routes/api/documents.ts +++ b/server/routes/api/documents.ts @@ -853,6 +853,7 @@ router.post("documents.update", auth(), async (ctx) => { const document = await sequelize.transaction(async (transaction) => { const document = await Document.findByPk(id, { userId: user.id, + includeState: true, transaction, }); authorize(user, "update", document); diff --git a/server/test/setup.ts b/server/test/setup.ts index 1a0d93fbd..cd8dcbfc4 100644 --- a/server/test/setup.ts +++ b/server/test/setup.ts @@ -33,3 +33,5 @@ jest.mock("aws-sdk", () => { Endpoint: jest.fn(), }; }); + +jest.mock("@getoutline/y-prosemirror", () => ({}));