diff --git a/CHANGELOG.md b/CHANGELOG.md index c3f130b65..db10c1608 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to ### Fixed +- 🐛(frontend) fix loading comments transaction #2273 - 💬(frontend) add missing link in onboarding description #2233 - 🐛(frontend) sanitize pasted and dropped content in document title #2210 - 🐛(frontend) Emoji menu doesn't display above comment box #2229 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); };