From 7ba6a9379be7ec2bcf5540d2137f7128347809b9 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Tue, 28 Mar 2023 22:13:42 -0400 Subject: [PATCH] Removal of non-collaborative editing code paths (#4210) --- app/actions/definitions/revisions.tsx | 21 +--- app/components/Collaborators.tsx | 5 +- .../Sidebar/components/DocumentLink.tsx | 15 +-- app/models/Document.ts | 17 +-- app/models/Team.ts | 9 +- app/scenes/Document/components/DataLoader.tsx | 12 +- app/scenes/Document/components/Document.tsx | 110 ++---------------- .../Document/components/SocketPresence.ts | 87 -------------- app/scenes/Document/index.tsx | 24 +--- app/scenes/Settings/Features.tsx | 29 +++-- app/stores/AuthStore.ts | 1 - app/stores/DocumentsStore.ts | 1 - server/commands/documentUpdater.ts | 8 +- server/commands/teamUpdater.ts | 4 - server/models/Team.ts | 4 - server/presenters/team.ts | 1 - .../processors/BacklinksProcessor.test.ts | 43 ------- .../queues/processors/BacklinksProcessor.ts | 47 +------- .../__snapshots__/documents.test.ts.snap | 9 -- server/routes/api/documents/documents.test.ts | 30 ----- server/routes/api/documents/documents.ts | 5 - server/routes/api/documents/schema.ts | 3 - server/routes/api/teams/schema.ts | 2 - server/services/websockets.ts | 104 +---------------- server/test/factories.ts | 1 - server/test/support.ts | 1 - shared/i18n/locales/en_US/translation.json | 4 +- 27 files changed, 45 insertions(+), 552 deletions(-) delete mode 100644 app/scenes/Document/components/SocketPresence.ts diff --git a/app/actions/definitions/revisions.tsx b/app/actions/definitions/revisions.tsx index 93f3be098..55f1ba1cd 100644 --- a/app/actions/definitions/revisions.tsx +++ b/app/actions/definitions/revisions.tsx @@ -15,7 +15,7 @@ export const restoreRevision = createAction({ section: RevisionSection, visible: ({ activeDocumentId, stores }) => !!activeDocumentId && stores.policies.abilities(activeDocumentId).update, - perform: async ({ t, event, location, activeDocumentId }) => { + perform: async ({ event, location, activeDocumentId }) => { event?.preventDefault(); if (!activeDocumentId) { return; @@ -26,26 +26,15 @@ export const restoreRevision = createAction({ }); const revisionId = match?.params.revisionId; - const { team } = stores.auth; const document = stores.documents.get(activeDocumentId); if (!document) { return; } - if (team?.collaborativeEditing) { - history.push(document.url, { - restore: true, - revisionId, - }); - } else { - await document.restore({ - revisionId, - }); - stores.toasts.showToast(t("Document restored"), { - type: "success", - }); - history.push(document.url); - } + history.push(document.url, { + restore: true, + revisionId, + }); }, }); diff --git a/app/components/Collaborators.tsx b/app/components/Collaborators.tsx index 6709db33e..59ed15993 100644 --- a/app/components/Collaborators.tsx +++ b/app/components/Collaborators.tsx @@ -9,7 +9,6 @@ import DocumentViews from "~/components/DocumentViews"; import Facepile from "~/components/Facepile"; import NudeButton from "~/components/NudeButton"; import Popover from "~/components/Popover"; -import useCurrentTeam from "~/hooks/useCurrentTeam"; import useCurrentUser from "~/hooks/useCurrentUser"; import useStores from "~/hooks/useStores"; @@ -20,7 +19,6 @@ type Props = { function Collaborators(props: Props) { const { t } = useTranslation(); const user = useCurrentUser(); - const team = useCurrentTeam(); const currentUserId = user?.id; const [requestedUserIds, setRequestedUserIds] = React.useState([]); const { users, presence, ui } = useStores(); @@ -79,8 +77,7 @@ function Collaborators(props: Props) { const isPresent = presentIds.includes(collaborator.id); const isEditing = editingIds.includes(collaborator.id); const isObserving = ui.observingUserId === collaborator.id; - const isObservable = - team.collaborativeEditing && collaborator.id !== user.id; + const isObservable = collaborator.id !== user.id; return ( { const params = this.toAPI(); - const collaborativeEditing = this.store.rootStore.auth.team - ?.collaborativeEditing; - - if (collaborativeEditing) { - delete params.text; - } - this.isSaving = true; try { - const model = await this.store.save( - { ...params, id: this.id }, - { - lastRevision: options?.lastRevision || this.revision, - ...options, - } - ); + const model = await this.store.save({ ...params, id: this.id }, options); // if saving is successful set the new values on the model itself set(this, { ...params, ...model }); diff --git a/app/models/Team.ts b/app/models/Team.ts index 7e9271a45..42cd31be8 100644 --- a/app/models/Team.ts +++ b/app/models/Team.ts @@ -25,10 +25,6 @@ class Team extends BaseModel { @observable inviteRequired: boolean; - @Field - @observable - collaborativeEditing: boolean; - @Field @observable commenting: boolean; @@ -92,10 +88,7 @@ class Team extends BaseModel { */ @computed get seamlessEditing(): boolean { - return ( - this.collaborativeEditing && - !!this.getPreference(TeamPreference.SeamlessEdit, true) - ); + return !!this.getPreference(TeamPreference.SeamlessEdit, true); } /** diff --git a/app/scenes/Document/components/DataLoader.tsx b/app/scenes/Document/components/DataLoader.tsx index 78d13fcc2..ccbd9a953 100644 --- a/app/scenes/Document/components/DataLoader.tsx +++ b/app/scenes/Document/components/DataLoader.tsx @@ -30,7 +30,6 @@ type Children = (options: { document: Document; revision: Revision | undefined; abilities: Record; - isEditing: boolean; readOnly: boolean; onCreateLink: (title: string) => Promise; sharedTree: NavigationNode | undefined; @@ -186,21 +185,12 @@ function DataLoader({ match, children }: Props) { ); } - // We do not want to remount the document when changing from view->edit - // on the multiplayer flag as the doc is guaranteed to be upto date. - const key = team.collaborativeEditing - ? "" - : isEditing - ? "editing" - : "read-only"; - return ( - + {children({ document, revision, abilities: can, - isEditing, readOnly: !isEditing || !can.update || document.isArchived || !!revisionId, onCreateLink, diff --git a/app/scenes/Document/components/Document.tsx b/app/scenes/Document/components/Document.tsx index 3d55a359e..7f39fd903 100644 --- a/app/scenes/Document/components/Document.tsx +++ b/app/scenes/Document/components/Document.tsx @@ -39,7 +39,6 @@ import { isModKey } from "~/utils/keyboard"; import { documentHistoryUrl, editDocumentUrl, - documentUrl, updateDocumentUrl, } from "~/utils/routeHelpers"; import Container from "./Container"; @@ -100,9 +99,6 @@ class DocumentScene extends React.Component { @observable isEmpty = true; - @observable - lastRevision: number = this.props.document.revision; - @observable title: string = this.props.document.title; @@ -116,39 +112,9 @@ class DocumentScene extends React.Component { } componentDidUpdate(prevProps: Props) { - const { auth, document, t } = this.props; - if (prevProps.readOnly && !this.props.readOnly) { this.updateIsDirty(); } - - if (this.props.readOnly || auth.team?.collaborativeEditing) { - this.lastRevision = document.revision; - } - - if ( - !this.props.readOnly && - !auth.team?.collaborativeEditing && - prevProps.document.revision !== this.lastRevision - ) { - if (auth.user && document.updatedBy.id !== auth.user.id) { - this.props.toasts.showToast( - t(`Document updated by {{userName}}`, { - userName: document.updatedBy.name, - }), - { - timeout: 30 * 1000, - type: "warning", - action: { - text: "Reload", - onClick: () => { - window.location.href = documentUrl(document); - }, - }, - } - ); - } - } } componentWillUnmount() { @@ -332,13 +298,8 @@ class DocumentScene extends React.Component { this.isPublishing = !!options.publish; try { - const savedDocument = await document.save({ - ...options, - lastRevision: this.lastRevision, - }); - + const savedDocument = await document.save(options); this.isEditorDirty = false; - this.lastRevision = savedDocument.revision; if (options.done) { this.props.history.push(savedDocument.url); @@ -385,7 +346,7 @@ class DocumentScene extends React.Component { }; onChange = (getEditorText: () => string) => { - const { document, auth } = this.props; + const { document } = this.props; this.getEditorText = getEditorText; // Keep derived task list in sync @@ -393,25 +354,6 @@ class DocumentScene extends React.Component { const total = tasks?.length ?? 0; const completed = tasks?.filter((t) => t.completed).length ?? 0; document.updateTasks(total, completed); - - // If the multiplayer editor is enabled we're done here as changes are saved - // through the persistence protocol. The rest of this method is legacy. - if (auth.team?.collaborativeEditing) { - return; - } - - // document change while read only is presumed to be a checkbox edit, - // in that case we don't delay in saving for a better user experience. - if (this.props.readOnly) { - this.updateIsDirty(); - this.onSave({ - done: false, - autosave: true, - }); - } else { - this.updateIsDirtyDebounced(); - this.autosave(); - } }; onHeadingsChange = (headings: Heading[]) => { @@ -449,14 +391,9 @@ class DocumentScene extends React.Component { const hasHeadings = this.headings.length > 0; const showContents = - ui.tocVisible && - ((readOnly && hasHeadings) || team?.collaborativeEditing); - const collaborativeEditing = - team?.collaborativeEditing && - !document.isArchived && - !document.isDeleted && - !revision && - !isShare; + ui.tocVisible && ((readOnly && hasHeadings) || !readOnly); + const multiplayerEditor = + !document.isArchived && !document.isDeleted && !revision && !isShare; const canonicalUrl = shareId ? this.props.match.url @@ -506,35 +443,12 @@ class DocumentScene extends React.Component { {(this.isUploading || this.isSaving) && } {!readOnly && ( - <> - { - if ( - // a URL replace matching the current document indicates a title change - // no guard is needed for this transition - action === "REPLACE" && - location.pathname === editDocumentUrl(document) - ) { - return true; - } - - return t( - `You have unsaved changes.\nAre you sure you want to discard them?` - ) as string; - }} - /> - - + )}
{ id={document.id} key={embedsDisabled ? "disabled" : "enabled"} ref={this.editor} - multiplayer={collaborativeEditing} + multiplayer={multiplayerEditor} shareId={shareId} isDraft={document.isDraft} template={document.isTemplate} diff --git a/app/scenes/Document/components/SocketPresence.ts b/app/scenes/Document/components/SocketPresence.ts deleted file mode 100644 index ce374493a..000000000 --- a/app/scenes/Document/components/SocketPresence.ts +++ /dev/null @@ -1,87 +0,0 @@ -import * as React from "react"; -import { USER_PRESENCE_INTERVAL } from "@shared/constants"; -import { WebsocketContext } from "~/components/WebsocketProvider"; - -type Props = { - documentId: string; - isEditing: boolean; - presence: boolean; -}; - -export default class SocketPresence extends React.Component { - static contextType = WebsocketContext; - - previousContext: typeof WebsocketContext; - - editingInterval: ReturnType | undefined; - - componentDidMount() { - this.editingInterval = this.props.presence - ? setInterval(() => { - if (this.props.isEditing) { - this.emitPresence(); - } - }, USER_PRESENCE_INTERVAL) - : undefined; - this.setupOnce(); - } - - componentDidUpdate(prevProps: Props) { - this.setupOnce(); - - if (prevProps.isEditing !== this.props.isEditing) { - this.emitPresence(); - } - } - - componentWillUnmount() { - if (this.context) { - this.context.emit("leave", { - documentId: this.props.documentId, - }); - this.context.off("authenticated", this.emitJoin); - } - - if (this.editingInterval) { - clearInterval(this.editingInterval); - } - } - - setupOnce = () => { - if (this.context && this.context !== this.previousContext) { - this.previousContext = this.context; - - if (this.context.authenticated) { - this.emitJoin(); - } - - this.context.on("authenticated", () => { - this.emitJoin(); - }); - } - }; - - emitJoin = () => { - if (!this.context) { - return; - } - this.context.emit("join", { - documentId: this.props.documentId, - isEditing: this.props.isEditing, - }); - }; - - emitPresence = () => { - if (!this.context) { - return; - } - this.context.emit("presence", { - documentId: this.props.documentId, - isEditing: this.props.isEditing, - }); - }; - - render() { - return this.props.children || null; - } -} diff --git a/app/scenes/Document/index.tsx b/app/scenes/Document/index.tsx index 0938a1110..211431cce 100644 --- a/app/scenes/Document/index.tsx +++ b/app/scenes/Document/index.tsx @@ -1,12 +1,10 @@ import * as React from "react"; import { StaticContext } from "react-router"; import { RouteComponentProps } from "react-router-dom"; -import useCurrentTeam from "~/hooks/useCurrentTeam"; import useLastVisitedPath from "~/hooks/useLastVisitedPath"; import useStores from "~/hooks/useStores"; import DataLoader from "./components/DataLoader"; import Document from "./components/Document"; -import SocketPresence from "./components/SocketPresence"; type Params = { documentSlug: string; @@ -24,7 +22,6 @@ type Props = RouteComponentProps; export default function DocumentScene(props: Props) { const { ui } = useStores(); - const team = useCurrentTeam(); const { documentSlug, revisionId } = props.match.params; const currentPath = props.location.pathname; const [, setLastVisitedPath] = useLastVisitedPath(); @@ -52,26 +49,7 @@ export default function DocumentScene(props: Props) { history={props.history} location={props.location} > - {({ document, isEditing, ...rest }) => { - const isActive = - !document.isArchived && !document.isDeleted && !revisionId; - - // TODO: Remove once multiplayer is 100% rollout, SocketPresence will - // no longer be required - if (isActive && !team.collaborativeEditing) { - return ( - - - - ); - } - - return ; - }} + {(rest) => } ); } diff --git a/app/scenes/Settings/Features.tsx b/app/scenes/Settings/Features.tsx index a92941bb7..10b173d31 100644 --- a/app/scenes/Settings/Features.tsx +++ b/app/scenes/Settings/Features.tsx @@ -43,22 +43,20 @@ function Features() { the experience for all members of the workspace. - {team.collaborativeEditing && ( - + - - - )} + checked={team.getPreference(TeamPreference.SeamlessEdit, true)} + onChange={handlePreferenceChange} + /> + diff --git a/app/stores/AuthStore.ts b/app/stores/AuthStore.ts index 876abd4df..a07528168 100644 --- a/app/stores/AuthStore.ts +++ b/app/stores/AuthStore.ts @@ -270,7 +270,6 @@ export default class AuthStore { name?: string; avatarUrl?: string | null | undefined; sharing?: boolean; - collaborativeEditing?: boolean; defaultCollectionId?: string | null; subdomain?: string | null | undefined; allowedDomains?: string[] | null | undefined; diff --git a/app/stores/DocumentsStore.ts b/app/stores/DocumentsStore.ts index de8453f7d..ef208aa6d 100644 --- a/app/stores/DocumentsStore.ts +++ b/app/stores/DocumentsStore.ts @@ -678,7 +678,6 @@ export default class DocumentsStore extends BaseStore { publish?: boolean; done?: boolean; autosave?: boolean; - lastRevision: number; } ) { this.isSaving = true; diff --git a/server/commands/documentUpdater.ts b/server/commands/documentUpdater.ts index be3d7496d..2b12abd1f 100644 --- a/server/commands/documentUpdater.ts +++ b/server/commands/documentUpdater.ts @@ -65,13 +65,7 @@ export default async function documentUpdater({ document.fullWidth = fullWidth; } if (text !== undefined) { - if (user.team?.collaborativeEditing) { - document = DocumentHelper.applyMarkdownToDocument(document, text, append); - } else if (append) { - document.text += text; - } else { - document.text = text; - } + document = DocumentHelper.applyMarkdownToDocument(document, text, append); } const changed = document.changed(); diff --git a/server/commands/teamUpdater.ts b/server/commands/teamUpdater.ts index 9db0ee4d1..0485f1b7b 100644 --- a/server/commands/teamUpdater.ts +++ b/server/commands/teamUpdater.ts @@ -21,7 +21,6 @@ const teamUpdater = async ({ params, user, team, ip }: TeamUpdaterProps) => { guestSignin, documentEmbeds, memberCollectionCreate, - collaborativeEditing, defaultCollectionId, defaultUserRole, inviteRequired, @@ -56,9 +55,6 @@ const teamUpdater = async ({ params, user, team, ip }: TeamUpdaterProps) => { if (defaultCollectionId !== undefined) { team.defaultCollectionId = defaultCollectionId; } - if (collaborativeEditing !== undefined) { - team.collaborativeEditing = collaborativeEditing; - } if (defaultUserRole !== undefined) { team.defaultUserRole = defaultUserRole; } diff --git a/server/models/Team.ts b/server/models/Team.ts index 6379e739f..13726e1b9 100644 --- a/server/models/Team.ts +++ b/server/models/Team.ts @@ -136,10 +136,6 @@ class Team extends ParanoidModel { @Column memberCollectionCreate: boolean; - @Default(true) - @Column - collaborativeEditing: boolean; - @Default("member") @IsIn([["viewer", "member"]]) @Column diff --git a/server/presenters/team.ts b/server/presenters/team.ts index 27cfcf61f..e9fa4e640 100644 --- a/server/presenters/team.ts +++ b/server/presenters/team.ts @@ -7,7 +7,6 @@ export default function presentTeam(team: Team) { avatarUrl: team.avatarUrl, sharing: team.sharing, memberCollectionCreate: team.memberCollectionCreate, - collaborativeEditing: team.collaborativeEditing, defaultCollectionId: team.defaultCollectionId, documentEmbeds: team.documentEmbeds, guestSignin: team.emailSigninEnabled, diff --git a/server/queues/processors/BacklinksProcessor.test.ts b/server/queues/processors/BacklinksProcessor.test.ts index 4c88207c9..bde73a555 100644 --- a/server/queues/processors/BacklinksProcessor.test.ts +++ b/server/queues/processors/BacklinksProcessor.test.ts @@ -220,46 +220,3 @@ describe("documents.delete", () => { expect(backlinks.length).toBe(0); }); }); - -describe("documents.title_change", () => { - test("should update titles in backlinked documents", async () => { - const newTitle = "test"; - const document = await buildDocument(); - const otherDocument = await buildDocument(); - const previousTitle = otherDocument.title; - // create a doc with a link back - document.text = `[${otherDocument.title}](${otherDocument.url})`; - await document.save(); - // ensure the backlinks are created - const processor = new BacklinksProcessor(); - await processor.perform({ - name: "documents.update", - documentId: document.id, - collectionId: document.collectionId, - teamId: document.teamId, - actorId: document.createdById, - createdAt: new Date().toISOString(), - data: { title: document.title, autosave: false, done: true }, - ip, - }); - // change the title of the linked doc - otherDocument.title = newTitle; - await otherDocument.save(); - // does the text get updated with the new title - await processor.perform({ - name: "documents.title_change", - documentId: otherDocument.id, - collectionId: otherDocument.collectionId, - teamId: otherDocument.teamId, - actorId: otherDocument.createdById, - createdAt: new Date().toISOString(), - data: { - previousTitle, - title: newTitle, - }, - ip, - }); - await document.reload(); - expect(document.text).toBe(`[${newTitle}](${otherDocument.url})`); - }); -}); diff --git a/server/queues/processors/BacklinksProcessor.ts b/server/queues/processors/BacklinksProcessor.ts index b90ce6f03..57e182b62 100644 --- a/server/queues/processors/BacklinksProcessor.ts +++ b/server/queues/processors/BacklinksProcessor.ts @@ -1,15 +1,14 @@ import { Op } from "sequelize"; -import { Document, Backlink, Team } from "@server/models"; +import { Document, Backlink } from "@server/models"; import { Event, DocumentEvent, RevisionEvent } from "@server/types"; import parseDocumentIds from "@server/utils/parseDocumentIds"; -import slugify from "@server/utils/slugify"; import BaseProcessor from "./BaseProcessor"; export default class BacklinksProcessor extends BaseProcessor { static applicableEvents: Event["name"][] = [ "documents.publish", "documents.update", - "documents.title_change", + //"documents.title_change", "documents.delete", ]; @@ -98,49 +97,7 @@ export default class BacklinksProcessor extends BaseProcessor { break; } - const document = await Document.findByPk(event.documentId); - if (!document) { - return; - } - // TODO: Handle re-writing of titles into CRDT - const team = await Team.findByPk(document.teamId); - - if (team?.collaborativeEditing) { - break; - } - - // update any link titles in documents that lead to this one - const backlinks = await Backlink.findAll({ - where: { - documentId: event.documentId, - }, - include: [ - { - model: Document, - as: "reverseDocument", - }, - ], - }); - await Promise.all( - backlinks.map(async (backlink) => { - const previousUrl = `/doc/${slugify(previousTitle)}-${ - document.urlId - }`; - - // find links in the other document that lead to this one and have - // the old title as anchor text. Go ahead and update those to the - // new title automatically - backlink.reverseDocument.text = backlink.reverseDocument.text.replace( - `[${previousTitle}](${previousUrl})`, - `[${title}](${document.url})` - ); - await backlink.reverseDocument.save({ - silent: true, - hooks: false, - }); - }) - ); break; } diff --git a/server/routes/api/documents/__snapshots__/documents.test.ts.snap b/server/routes/api/documents/__snapshots__/documents.test.ts.snap index 3a0ab9f4f..bd99e53f3 100644 --- a/server/routes/api/documents/__snapshots__/documents.test.ts.snap +++ b/server/routes/api/documents/__snapshots__/documents.test.ts.snap @@ -45,15 +45,6 @@ exports[`#documents.search should require authentication 1`] = ` } `; -exports[`#documents.update should fail if document lastRevision does not match 1`] = ` -{ - "error": "invalid_request", - "message": "Document has changed since last revision", - "ok": false, - "status": 400, -} -`; - exports[`#documents.update should require authentication 1`] = ` { "error": "authentication_required", diff --git a/server/routes/api/documents/documents.test.ts b/server/routes/api/documents/documents.test.ts index 29a71aac6..a57e7cfa4 100644 --- a/server/routes/api/documents/documents.test.ts +++ b/server/routes/api/documents/documents.test.ts @@ -2456,7 +2456,6 @@ describe("#documents.update", () => { id: document.id, title: "Updated title", text: "Updated text", - lastRevision: document.revisionCount, }, }); const body = await res.json(); @@ -2478,7 +2477,6 @@ describe("#documents.update", () => { id: document.id, title: "Updated title", text: "Updated text", - lastRevision: document.revisionCount, publish: true, }, }); @@ -2503,7 +2501,6 @@ describe("#documents.update", () => { id: document.id, title: "Updated title", text: "Updated text", - lastRevision: document.revisionCount, collectionId: collection.id, publish: true, }, @@ -2526,7 +2523,6 @@ describe("#documents.update", () => { id: document.id, title: "Updated title", text: "Updated text", - lastRevision: document.revisionCount, collectionId: collection.id, publish: true, }, @@ -2551,7 +2547,6 @@ describe("#documents.update", () => { id: template.id, title: "Updated title", text: "Updated text", - lastRevision: template.revisionCount, publish: true, }, }); @@ -2582,7 +2577,6 @@ describe("#documents.update", () => { id: document.id, title: "Updated title", text: "Updated text", - lastRevision: document.revisionCount, publish: true, }, }); @@ -2603,27 +2597,11 @@ describe("#documents.update", () => { id: document.id, title: "Updated title", text: "Updated text", - lastRevision: document.revisionCount, }, }); expect(res.status).toEqual(403); }); - it("should fail if document lastRevision does not match", async () => { - const { user, document } = await seed(); - const res = await server.post("/api/documents.update", { - body: { - token: user.getJwtToken(), - id: document.id, - text: "Updated text", - lastRevision: 123, - }, - }); - const body = await res.json(); - expect(res.status).toEqual(400); - expect(body).toMatchSnapshot(); - }); - it("should update document details for children", async () => { const { user, document, collection } = await seed(); collection.documentStructure = [ @@ -2670,7 +2648,6 @@ describe("#documents.update", () => { token: admin.getJwtToken(), id: document.id, text: "Changed text", - lastRevision: document.revisionCount, }, }); const body = await res.json(); @@ -2694,7 +2671,6 @@ describe("#documents.update", () => { token: user.getJwtToken(), id: document.id, text: "Changed text", - lastRevision: document.revisionCount, }, }); expect(res.status).toEqual(403); @@ -2709,7 +2685,6 @@ describe("#documents.update", () => { token: user.getJwtToken(), id: document.id, text: "Changed text", - lastRevision: document.revisionCount, }, }); expect(res.status).toEqual(403); @@ -2722,7 +2697,6 @@ describe("#documents.update", () => { token: user.getJwtToken(), id: document.id, text: "Additional text", - lastRevision: document.revisionCount, append: true, }, }); @@ -2738,7 +2712,6 @@ describe("#documents.update", () => { body: { token: user.getJwtToken(), id: document.id, - lastRevision: document.revisionCount, title: "Updated Title", append: true, }, @@ -2754,7 +2727,6 @@ describe("#documents.update", () => { body: { token: user.getJwtToken(), id: document.id, - lastRevision: document.revisionCount, title: "Updated Title", text: "", }, @@ -2770,7 +2742,6 @@ describe("#documents.update", () => { body: { token: user.getJwtToken(), id: document.id, - lastRevision: document.revisionCount, title: document.title, text: document.text, }, @@ -2851,7 +2822,6 @@ describe("#documents.update", () => { id: document.id, title: "Updated title", text: "Updated text", - lastRevision: document.revisionCount, collectionId: collection.id, publish: true, }, diff --git a/server/routes/api/documents/documents.ts b/server/routes/api/documents/documents.ts index 75002d335..b58e0e426 100644 --- a/server/routes/api/documents/documents.ts +++ b/server/routes/api/documents/documents.ts @@ -880,7 +880,6 @@ router.post( text, fullWidth, publish, - lastRevision, templateId, collectionId, append, @@ -908,10 +907,6 @@ router.post( authorize(user, "publish", collection); } - if (lastRevision && lastRevision !== document.revisionCount) { - throw InvalidRequestError("Document has changed since last revision"); - } - collection = await sequelize.transaction(async (transaction) => { await documentUpdater({ document, diff --git a/server/routes/api/documents/schema.ts b/server/routes/api/documents/schema.ts index 656935af3..d41539cdf 100644 --- a/server/routes/api/documents/schema.ts +++ b/server/routes/api/documents/schema.ts @@ -190,9 +190,6 @@ export const DocumentsUpdateSchema = BaseSchema.extend({ /** Boolean to denote if the doc should be published */ publish: z.boolean().optional(), - /** Revision to compare against document revision count */ - lastRevision: z.number().optional(), - /** Doc template Id */ templateId: z.string().uuid().nullish(), diff --git a/server/routes/api/teams/schema.ts b/server/routes/api/teams/schema.ts index 5f1d020f8..8c70cf797 100644 --- a/server/routes/api/teams/schema.ts +++ b/server/routes/api/teams/schema.ts @@ -18,8 +18,6 @@ export const TeamsUpdateSchema = BaseSchema.extend({ documentEmbeds: z.boolean().optional(), /** Whether team members are able to create new collections */ memberCollectionCreate: z.boolean().optional(), - /** Whether collaborative editing is enabled */ - collaborativeEditing: z.boolean().optional(), /** The default landing collection for the team */ defaultCollectionId: z.string().uuid().nullish(), /** The default user role */ diff --git a/server/services/websockets.ts b/server/services/websockets.ts index fee81e6d9..1619a2dd3 100644 --- a/server/services/websockets.ts +++ b/server/services/websockets.ts @@ -8,7 +8,7 @@ import Logger from "@server/logging/Logger"; import Metrics from "@server/logging/Metrics"; import * as Tracing from "@server/logging/tracer"; import { traceFunction } from "@server/logging/tracing"; -import { Document, Collection, View, User } from "@server/models"; +import { Collection, User } from "@server/models"; import { can } from "@server/policies"; import ShutdownHelper, { ShutdownOrder } from "@server/utils/ShutdownHelper"; import { getUserForJWT } from "@server/utils/jwt"; @@ -186,58 +186,6 @@ async function authenticated(io: IO.Server, socket: SocketWithAuth) { Metrics.increment("websockets.collections.join"); } } - - // user is joining a document channel, because they have navigated to - // view a document. - if (event.documentId) { - const document = await Document.findByPk(event.documentId, { - userId: user.id, - }); - - if (can(user, "read", document)) { - const room = `document-${event.documentId}`; - await View.touch(event.documentId, user.id, event.isEditing); - const editing = await View.findRecentlyEditingByDocument( - event.documentId - ); - - await socket.join(room); - Metrics.increment("websockets.documents.join"); - - // let everyone else in the room know that a new user joined - io.to(room).emit("user.join", { - userId: user.id, - documentId: event.documentId, - isEditing: event.isEditing, - }); - - // let this user know who else is already present in the room - try { - const socketIds = await io.in(room).allSockets(); - - // because a single user can have multiple socket connections we - // need to make sure that only unique userIds are returned. A Map - // makes this easy. - const userIds = new Map(); - - for (const socketId of socketIds) { - const userId = await Redis.defaultClient.hget(socketId, "userId"); - userIds.set(userId, userId); - } - - socket.emit("document.presence", { - documentId: event.documentId, - userIds: Array.from(userIds.keys()), - editingIds: editing.map((view) => view.userId), - }); - } catch (err) { - if (err) { - Logger.error("Error getting clients for room", err); - return; - } - } - } - } }); // allow the client to request to leave rooms @@ -246,56 +194,6 @@ async function authenticated(io: IO.Server, socket: SocketWithAuth) { await socket.leave(`collection-${event.collectionId}`); Metrics.increment("websockets.collections.leave"); } - - if (event.documentId) { - const room = `document-${event.documentId}`; - - await socket.leave(room); - Metrics.increment("websockets.documents.leave"); - io.to(room).emit("user.leave", { - userId: user.id, - documentId: event.documentId, - }); - } - }); - - socket.on("disconnecting", () => { - socket.rooms.forEach((room) => { - if (room.startsWith("document-")) { - const documentId = room.replace("document-", ""); - io.to(room).emit("user.leave", { - userId: user.id, - documentId, - }); - } - }); - }); - - socket.on("presence", async (event) => { - Metrics.increment("websockets.presence"); - const room = `document-${event.documentId}`; - - if (event.documentId && socket.rooms.has(room)) { - await View.touch(event.documentId, user.id, event.isEditing); - - io.to(room).emit("user.presence", { - userId: user.id, - documentId: event.documentId, - isEditing: event.isEditing, - }); - - socket.on("typing", async (event) => { - const room = `document-${event.documentId}`; - - if (event.documentId && socket.rooms[room]) { - io.to(room).emit("user.typing", { - userId: user.id, - documentId: event.documentId, - commentId: event.commentId, - }); - } - }); - } }); } diff --git a/server/test/factories.ts b/server/test/factories.ts index f1779bed4..b92810be4 100644 --- a/server/test/factories.ts +++ b/server/test/factories.ts @@ -123,7 +123,6 @@ export function buildTeam(overrides: Record = {}) { return Team.create( { name: `Team ${count}`, - collaborativeEditing: false, authenticationProviders: [ { name: "slack", diff --git a/server/test/support.ts b/server/test/support.ts index ff43e6831..bcff67785 100644 --- a/server/test/support.ts +++ b/server/test/support.ts @@ -11,7 +11,6 @@ export const seed = async () => { const team = await Team.create( { name: "Team", - collaborativeEditing: false, authenticationProviders: [ { name: "slack", diff --git a/shared/i18n/locales/en_US/translation.json b/shared/i18n/locales/en_US/translation.json index c9d75f18d..9cc860cfb 100644 --- a/shared/i18n/locales/en_US/translation.json +++ b/shared/i18n/locales/en_US/translation.json @@ -70,7 +70,6 @@ "Download {{ platform }} app": "Download {{ platform }} app", "Log out": "Log out", "Restore revision": "Restore revision", - "Document restored": "Document restored", "Copy link": "Copy link", "Link copied": "Link copied", "Dark": "Dark", @@ -326,6 +325,7 @@ "Manual sort": "Manual sort", "Delete comment": "Delete comment", "Comment options": "Comment options", + "Document restored": "Document restored", "Document options": "Document options", "Restore": "Restore", "Choose a collection": "Choose a collection", @@ -442,8 +442,6 @@ "Cancel": "Cancel", "No comments yet": "No comments yet", "Error updating comment": "Error updating comment", - "Document updated by {{userName}}": "Document updated by {{userName}}", - "You have unsaved changes.\nAre you sure you want to discard them?": "You have unsaved changes.\nAre you sure you want to discard them?", "Images are still uploading.\nAre you sure you want to discard them?": "Images are still uploading.\nAre you sure you want to discard them?", "Viewed by": "Viewed by", "only you": "only you",