From bd662d72bf1b29d7a33d60779e70bb3afe199704 Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Wed, 6 May 2026 15:41:41 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B(frontend)=20fix=20loading=20commen?= =?UTF-8?q?ts=20transaction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When we load the comments we have to notify the subscribers of the DocsThreadStore. This generates a Yjs transaction that is currently treated as a user-initiated content change that will trigger a patch request when the doc will try to save. We now update the transaction origin when we notify the subscribers so that we can reliably identify and ignore those transactions in the useSaveDoc hook. --- CHANGELOG.md | 1 + .../docs/doc-editor/components/DocEditor.tsx | 1 - .../components/comments/DocsThreadStore.tsx | 39 +++++++++++++++---- .../components/comments/useComments.ts | 9 ++++- .../docs/doc-editor/hook/useSaveDoc.tsx | 11 ++++++ 5 files changed, 51 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6e56805a..45ae147ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to ### Fixed +- 🐛(frontend) fix patch and comments #2273 - 🐛(frontend) interlinking are exported correctly in print mode #2269 - 💬(frontend) add missing link in onboarding description #2233 - 🐛(frontend) sanitize pasted and dropped content in document title #2210 diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocEditor.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocEditor.tsx index ffcca8d67..32082b03a 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocEditor.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocEditor.tsx @@ -144,7 +144,6 @@ interface DocCoreEditorProps { } export const DocCoreEditor = ({ doc, readOnly }: DocCoreEditorProps) => { - useCollaboration(doc.id); const { provider, isReady } = useProviderStore(); const isProviderReady = isReady && provider; const showContent = !!( diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/comments/DocsThreadStore.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/comments/DocsThreadStore.tsx index 43374b78b..9a86bd86a 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/comments/DocsThreadStore.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/comments/DocsThreadStore.tsx @@ -1,5 +1,6 @@ import { CommentBody, ThreadStore } from '@blocknote/core/comments'; import type { Awareness } from 'y-protocols/awareness'; +import * as Y from 'yjs'; import { APIError, errorCauses, fetchAPI } from '@/api'; import { Doc } from '@/features/docs/doc-management'; @@ -17,6 +18,13 @@ import { type ServerThreadListResponse = ServerThread[]; +/** + * notifySubscribers generate a transaction, to distinguish + * the origin of the update, we use a specific origin "commentMarkUpdate" + * for the updates coming from the comment mark changes. + */ +export const COMMENT_UPDATE_ORIGIN = 'commentMarkUpdate'; + export class DocsThreadStore extends ThreadStore { protected static COMMENTS_PING = 'commentsPing'; protected threads: Map = new Map(); @@ -24,6 +32,7 @@ export class DocsThreadStore extends ThreadStore { (threads: Map) => void >(); private awareness?: Awareness; + private yDoc?: Y.Doc; private lastPingAt = 0; private pingTimer?: ReturnType; @@ -31,11 +40,13 @@ export class DocsThreadStore extends ThreadStore { protected docId: Doc['id'], awareness: Awareness | undefined, protected docAuth: DocsThreadStoreAuth, + yDoc?: Y.Doc, ) { super(docAuth); if (docAuth.canSee) { this.awareness = awareness; + this.yDoc = yDoc; this.awareness?.on('update', this.onAwarenessUpdate); this.refreshThreads(); @@ -134,18 +145,30 @@ export class DocsThreadStore extends ThreadStore { } /** - * Notifies all subscribers about the current thread state + * Notifies all subscribers about the current thread state. + * We trigger the transaction with a specific origin so we will be able + * to flag that the update comes from a comment update. + * The inner ydoc.transact calls from y-prosemirror will see there's already + * an active transaction and reuse it. */ private notifySubscribers() { // Always emit a new Map reference to help consumers detect changes const threads = new Map(this.threads); - this.subscribers.forEach((cb) => { - try { - cb(threads); - } catch (e) { - console.warn('DocsThreadStore subscriber threw', e); - } - }); + const notify = () => { + this.subscribers.forEach((cb) => { + try { + cb(threads); + } catch (e) { + console.warn('DocsThreadStore subscriber threw', e); + } + }); + }; + + if (this.yDoc) { + this.yDoc.transact(notify, COMMENT_UPDATE_ORIGIN); + } else { + notify(); + } } private upsertClientThreadData(thread: ClientThreadData) { diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/comments/useComments.ts b/src/frontend/apps/impress/src/features/docs/doc-editor/components/comments/useComments.ts index 6dbd3cbfb..35863ff1e 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/comments/useComments.ts +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/comments/useComments.ts @@ -27,8 +27,15 @@ export function useComments( encodeURIComponent(user?.full_name || ''), canComment, ), + provider?.document, ); - }, [docId, canComment, provider?.awareness, user?.full_name]); + }, [ + docId, + canComment, + provider?.awareness, + provider?.document, + user?.full_name, + ]); useEffect(() => { if (canComment) { diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useSaveDoc.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useSaveDoc.tsx index 85ac5bc1b..f434ba3df 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useSaveDoc.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useSaveDoc.tsx @@ -2,6 +2,7 @@ import { useRouter } from 'next/router'; import { useCallback, useEffect, useRef, useState } from 'react'; import * as Y from 'yjs'; +import { COMMENT_UPDATE_ORIGIN } from '@/docs/doc-editor/components/comments/DocsThreadStore'; import { useDocContentUpdate } from '@/docs/doc-management/api/useDocContentUpdate'; import { useProviderStore } from '@/docs/doc-management/stores/useProviderStore'; import { KEY_LIST_DOC_VERSIONS } from '@/docs/doc-versioning/api/useDocVersions'; @@ -65,6 +66,16 @@ export const useSaveDoc = (docId: string, yDoc: Y.Doc) => { const isAIChange = !transaction.local && transactionOrigin !== PROVIDER_ORIGIN_CONSTRUCTOR; + /** + * notifySubscribers generate a transaction that can be + * interpreted as a local change. + * We intercept the update with this origin to + * avoid marking the change as local. + */ + if (transaction.origin === COMMENT_UPDATE_ORIGIN) { + return; + } + setIsLocalChange(transaction.local || isAIChange); };