diff --git a/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts b/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts index d6debab1b..dd98aff9e 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts @@ -250,22 +250,16 @@ export const waitForResponseCreateDoc = (page: Page) => { }; export const mockedDocument = async (page: Page, data: object) => { - await page.route(/\**\/documents\/\**/, async (route) => { + // document/[ID]/ or document/[ID]/tree/ routes + await page.route(/.*\/documents\/[^/]+\/(?:$|tree\/.*)/, async (route) => { const request = route.request(); - if ( - request.method().includes('GET') && - !request.url().includes('page=') && - !request.url().includes('versions') && - !request.url().includes('accesses') && - !request.url().includes('invitations') - ) { + if (request.method().includes('GET') && !request.url().includes('page=')) { const { abilities, ...doc } = data as unknown as { abilities?: Record; }; await route.fulfill({ json: { id: 'mocked-document-id', - content: '', title: 'Mocked document', path: '000000', abilities: { @@ -299,6 +293,17 @@ export const mockedDocument = async (page: Page, data: object) => { await route.continue(); } }); + + await page.route(/.*\/documents\/[^/]+\/content\/$/, async (route) => { + const request = route.request(); + if (request.method().includes('GET')) { + await route.fulfill({ + body: '', + }); + } else { + await route.continue(); + } + }); }; export const mockedListDocs = async (page: Page, data: object[] = []) => { diff --git a/src/frontend/apps/e2e/__tests__/app-impress/utils-export.ts b/src/frontend/apps/e2e/__tests__/app-impress/utils-export.ts index 54395c63f..c952866ce 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/utils-export.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/utils-export.ts @@ -27,25 +27,16 @@ export const overrideDocContent = async ({ browserName: BrowserName; }) => { // Override content prop with assets/base-content-test-pdf.txt - await page.route(/\**\/documents\/\**/, async (route) => { + await page.route(/.*\/documents\/[^/]+\/content\/$/, async (route) => { const request = route.request(); - if ( - request.method().includes('GET') && - !request.url().includes('page=') && - !request.url().includes('versions') && - !request.url().includes('accesses') && - !request.url().includes('invitations') - ) { + if (request.method() === 'GET') { const response = await route.fetch(); - const json = await response.json(); - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - json.content = fs.readFileSync( - path.join(__dirname, 'assets/base-content-test-pdf.txt'), - 'utf-8', - ); void route.fulfill({ response, - body: JSON.stringify(json), + body: fs.readFileSync( + path.join(__dirname, 'assets/base-content-test-pdf.txt'), + 'utf-8', + ), }); } else { await route.continue(); 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 580ad9e44..243be257a 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 @@ -7,6 +7,7 @@ import { Doc, LinkReach, getDocLinkReach, + useCollaboration, useIsCollaborativeEditable, useProviderStore, } from '@/docs/doc-management'; @@ -79,6 +80,7 @@ interface DocEditorProps { } export const DocEditor = ({ doc }: DocEditorProps) => { + useCollaboration(doc.id); const { isDesktop } = useResponsiveStore(); const { provider, isReady } = useProviderStore(); const { isEditable, isLoading } = useIsCollaborativeEditable(doc); diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/hook/__tests__/useSaveDoc.test.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/hook/__tests__/useSaveDoc.test.tsx index e532c8049..f5eac9eba 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/hook/__tests__/useSaveDoc.test.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/hook/__tests__/useSaveDoc.test.tsx @@ -67,13 +67,15 @@ describe('useSaveDoc', () => { const yDoc = new Y.Doc(); const docId = 'test-doc-id'; - fetchMock.patch('http://test.jest/api/v1.0/documents/test-doc-id/', { - body: JSON.stringify({ - id: 'test-doc-id', - content: 'test-content', - title: 'test-title', - }), - }); + fetchMock.patch( + 'http://test.jest/api/v1.0/documents/test-doc-id/content/', + { + body: JSON.stringify({ + id: 'test-doc-id', + content: 'test-content', + }), + }, + ); renderHook(() => useSaveDoc(docId, yDoc, true), { wrapper: AppWrapper, @@ -94,7 +96,7 @@ describe('useSaveDoc', () => { await waitFor(() => { expect(fetchMock.lastCall()?.[0]).toBe( - 'http://test.jest/api/v1.0/documents/test-doc-id/', + 'http://test.jest/api/v1.0/documents/test-doc-id/content/', ); }); }); @@ -104,13 +106,15 @@ describe('useSaveDoc', () => { const yDoc = new Y.Doc(); const docId = 'test-doc-id'; - fetchMock.patch('http://test.jest/api/v1.0/documents/test-doc-id/', { - body: JSON.stringify({ - id: 'test-doc-id', - content: 'test-content', - title: 'test-title', - }), - }); + fetchMock.patch( + 'http://test.jest/api/v1.0/documents/test-doc-id/content/', + { + body: JSON.stringify({ + id: 'test-doc-id', + content: 'test-content', + }), + }, + ); renderHook(() => useSaveDoc(docId, yDoc, true), { wrapper: AppWrapper, 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 1954c0b7a..b8e9c69f8 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 @@ -1,8 +1,9 @@ import { useRouter } from 'next/router'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import * as Y from 'yjs'; -import { useUpdateDoc } from '@/docs/doc-management/'; +import { KEY_DOC_CONTENT } from '@/docs//doc-management/api/useDocContent'; +import { useDocContentUpdate } from '@/docs/doc-management/api/useDocContentUpdate'; import { KEY_LIST_DOC_VERSIONS } from '@/docs/doc-versioning/api/useDocVersions'; import { toBase64 } from '@/utils/string'; import { isFirefox } from '@/utils/userAgent'; @@ -14,11 +15,16 @@ export const useSaveDoc = ( yDoc: Y.Doc, isConnectedToCollabServer: boolean, ) => { - const { mutate: updateDoc } = useUpdateDoc({ - listInvalidQueries: [KEY_LIST_DOC_VERSIONS], + const isSavingRef = useRef(false); + const { mutate: updateDocContent } = useDocContentUpdate({ + listInvalidQueries: [KEY_LIST_DOC_VERSIONS, KEY_DOC_CONTENT], onSuccess: () => { + isSavingRef.current = false; setIsLocalChange(false); }, + onError: () => { + isSavingRef.current = false; + }, }); const [isLocalChange, setIsLocalChange] = useState(false); @@ -64,18 +70,19 @@ export const useSaveDoc = ( }, [yDoc]); const saveDoc = useCallback(() => { - if (!isLocalChange) { + if (!isLocalChange || isSavingRef.current) { return false; } - updateDoc({ + isSavingRef.current = true; + updateDocContent({ id: docId, content: toBase64(Y.encodeStateAsUpdate(yDoc)), websocket: isConnectedToCollabServer, }); return true; - }, [isLocalChange, updateDoc, docId, yDoc, isConnectedToCollabServer]); + }, [isLocalChange, updateDocContent, docId, yDoc, isConnectedToCollabServer]); const router = useRouter(); diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/api/useDocContent.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/api/useDocContent.tsx new file mode 100644 index 000000000..8b9882a6e --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-management/api/useDocContent.tsx @@ -0,0 +1,41 @@ +import { UseQueryOptions, useQuery } from '@tanstack/react-query'; +import { validate as uuidValidate } from 'uuid'; + +import { APIError, errorCauses, fetchAPI } from '@/api'; + +export type DocContentParams = { + id: string; +}; + +export const getDocContent = async ({ + id, +}: DocContentParams): Promise => { + if (!uuidValidate(id)) { + throw new Error(`Invalid doc id in getDocContent: ${id}`); + } + + const response = await fetchAPI(`documents/${id}/content/`, { + headers: { + accept: 'text/plain,application/json', + }, + }); + + if (!response.ok) { + throw new APIError('Failed to get the doc', await errorCauses(response)); + } + + return response.text(); +}; + +export const KEY_DOC_CONTENT = 'doc-content'; + +export function useDocContent( + param: DocContentParams, + queryConfig?: UseQueryOptions, +) { + return useQuery({ + queryKey: queryConfig?.queryKey ?? [KEY_DOC_CONTENT, param], + queryFn: () => getDocContent(param), + ...queryConfig, + }); +} diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/api/useDocContentUpdate.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/api/useDocContentUpdate.tsx new file mode 100644 index 000000000..ff73d0f6e --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-management/api/useDocContentUpdate.tsx @@ -0,0 +1,76 @@ +import { + UseMutationOptions, + useMutation, + useQueryClient, +} from '@tanstack/react-query'; + +import { APIError, errorCauses, fetchAPI } from '@/api'; + +import { Doc } from '../types'; + +import { KEY_CAN_EDIT } from './useDocCanEdit'; + +interface UpdateDocContentParams { + id: Doc['id']; + content: string; // Base64 encoded content + websocket?: boolean; +} + +export const updateDocContent = async ({ + id, + content, + websocket, +}: UpdateDocContentParams): Promise => { + const response = await fetchAPI(`documents/${id}/content/`, { + method: 'PATCH', + body: JSON.stringify({ + content, + websocket, + }), + }); + + if (!response.ok) { + throw new APIError( + 'Failed to update the doc content', + await errorCauses(response), + ); + } +}; + +type UseDocContentUpdate = UseMutationOptions< + void, + APIError, + UpdateDocContentParams +> & { + listInvalidQueries?: string[]; +}; + +export function useDocContentUpdate(queryConfig?: UseDocContentUpdate) { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: updateDocContent, + ...queryConfig, + onSuccess: (data, variables, onMutateResult, context) => { + queryConfig?.listInvalidQueries?.forEach((queryKey) => { + void queryClient.resetQueries({ + queryKey: [queryKey], + }); + }); + + if (queryConfig?.onSuccess) { + void queryConfig.onSuccess(data, variables, onMutateResult, context); + } + }, + onError: (error, variables, onMutateResult, context) => { + // If error it means the user is probably not allowed to edit the doc + // so we invalidate the canEdit query to update the UI accordingly + void queryClient.invalidateQueries({ + queryKey: [KEY_CAN_EDIT], + }); + + if (queryConfig?.onError) { + queryConfig.onError(error, variables, onMutateResult, context); + } + }, + }); +} diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/api/useDuplicateDoc.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/api/useDuplicateDoc.tsx index 7085cb4dc..87f90a51a 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/api/useDuplicateDoc.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/api/useDuplicateDoc.tsx @@ -17,8 +17,9 @@ import { toBase64 } from '@/utils/string'; import { useProviderStore } from '../stores'; import { Doc } from '../types'; +import { KEY_DOC_CONTENT } from './useDocContent'; +import { useDocContentUpdate } from './useDocContentUpdate'; import { KEY_LIST_DOC } from './useDocs'; -import { useUpdateDoc } from './useUpdateDoc'; interface DuplicateDocPayload { docId: string; @@ -62,8 +63,8 @@ export function useDuplicateDoc(options?: DuplicateDocOptions) { const { t } = useTranslation(); const { provider } = useProviderStore(); - const { mutateAsync: updateDoc } = useUpdateDoc({ - listInvalidQueries: [KEY_LIST_DOC_VERSIONS], + const { mutateAsync: updateDocContent } = useDocContentUpdate({ + listInvalidQueries: [KEY_LIST_DOC_VERSIONS, KEY_DOC_CONTENT], }); return useMutation({ @@ -75,7 +76,7 @@ export function useDuplicateDoc(options?: DuplicateDocOptions) { provider.document.guid === variables.docId; if (canSave) { - await updateDoc({ + await updateDocContent({ id: variables.docId, content: toBase64(Y.encodeStateAsUpdate(provider.document)), }); diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/api/useUpdateDoc.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/api/useUpdateDoc.tsx index b18c5ac94..63791ea88 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/api/useUpdateDoc.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/api/useUpdateDoc.tsx @@ -8,12 +8,10 @@ import { APIError, errorCauses, fetchAPI } from '@/api'; import { Doc } from '../types'; -import { KEY_CAN_EDIT } from './useDocCanEdit'; - -export type UpdateDocParams = Pick & - Partial> & { - websocket?: boolean; - }; +export interface UpdateDocParams { + id: Doc['id']; + title?: string; +} export const updateDoc = async ({ id, @@ -33,7 +31,7 @@ export const updateDoc = async ({ return response.json() as Promise; }; -type UseUpdateDoc = UseMutationOptions> & { +type UseUpdateDoc = UseMutationOptions & { listInvalidQueries?: string[]; }; @@ -54,12 +52,6 @@ export function useUpdateDoc(queryConfig?: UseUpdateDoc) { } }, onError: (error, variables, onMutateResult, context) => { - // If error it means the user is probably not allowed to edit the doc - // so we invalidate the canEdit query to update the UI accordingly - void queryClient.invalidateQueries({ - queryKey: [KEY_CAN_EDIT], - }); - if (queryConfig?.onError) { queryConfig.onError(error, variables, onMutateResult, context); } diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/components/ModalRemoveDoc.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/components/ModalRemoveDoc.tsx index e529d6539..32e7de28c 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/components/ModalRemoveDoc.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/components/ModalRemoveDoc.tsx @@ -15,6 +15,7 @@ import { useConfig } from '@/core'; import { KEY_LIST_DOC_TRASHBIN } from '@/docs/docs-grid'; import { useKeyboardAction } from '@/hooks'; +import { KEY_DOC } from '../api'; import { KEY_LIST_DOC } from '../api/useDocs'; import { useRemoveDoc } from '../api/useRemoveDoc'; import { useDocUtils } from '../hooks'; @@ -44,7 +45,7 @@ export const ModalRemoveDoc = ({ isError, error, } = useRemoveDoc({ - listInvalidQueries: [KEY_LIST_DOC, KEY_LIST_DOC_TRASHBIN], + listInvalidQueries: [KEY_LIST_DOC, KEY_LIST_DOC_TRASHBIN, KEY_DOC], options: { onSuccess: () => { if (onSuccess) { diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/hooks/useCollaboration.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/hooks/useCollaboration.tsx index 4c839ca92..d9bbcc4f2 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/hooks/useCollaboration.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/hooks/useCollaboration.tsx @@ -1,29 +1,83 @@ +import { useQueryClient } from '@tanstack/react-query'; import { useEffect } from 'react'; import { useCollaborationUrl } from '@/core/config'; +import { + KEY_DOC_CONTENT, + useDocContent, +} from '@/docs/doc-management/api/useDocContent'; +import { useProviderStore } from '@/docs/doc-management/stores/useProviderStore'; import { useBroadcastStore } from '@/stores/useBroadcastStore'; -import { useProviderStore } from '../stores/useProviderStore'; -import { Base64 } from '../types'; +import { KEY_DOC } from '../api'; -export const useCollaboration = (room?: string, initialContent?: Base64) => { +export const useCollaboration = (room: string) => { const collaborationUrl = useCollaborationUrl(room); + const { addTask } = useBroadcastStore(); + const queryClient = useQueryClient(); const { setBroadcastProvider, cleanupBroadcast } = useBroadcastStore(); - const { provider, createProvider, destroyProvider } = useProviderStore(); + const { + provider, + createProvider, + destroyProvider, + isReady, + hasLostConnection, + resetLostConnection, + } = useProviderStore(); + const { data: docContent } = useDocContent( + { id: room }, + { + staleTime: 30000, // 30 seconds - We keep the data fresh as it is a highly collaborative page + queryKey: [KEY_DOC_CONTENT, { id: room }], + }, + ); + /** + * When the provider detects a lost connection, we invalidate the document query to trigger a refetch. + * Because it can be because the user has access to the document that are modified + * (e.g., permissions changed, document deleted, user removed) + */ useEffect(() => { - if (!room || !collaborationUrl || provider) { + if (hasLostConnection && room) { + void queryClient.invalidateQueries({ + queryKey: [KEY_DOC, { id: room }], + }); + resetLostConnection(); + } + }, [hasLostConnection, room, queryClient, resetLostConnection]); + + /** + * We add a broadcast task to reset the query cache + * when the document visibility changes. + */ + useEffect(() => { + if (!room || !isReady) { return; } - const newProvider = createProvider(collaborationUrl, room, initialContent); + addTask(`${KEY_DOC}-${room}`, () => { + void queryClient.invalidateQueries({ + queryKey: [KEY_DOC, { id: room }], + }); + }); + }, [addTask, room, queryClient, isReady]); + + /** + * Set the provider when the collaboration URL and the document content are available. + */ + useEffect(() => { + if (!room || !collaborationUrl || provider || docContent === undefined) { + return; + } + + const newProvider = createProvider(collaborationUrl, room, docContent); setBroadcastProvider(newProvider); }, [ provider, collaborationUrl, - room, - initialContent, createProvider, + docContent, + room, setBroadcastProvider, ]); diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/types.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/types.tsx index fcf93edd4..44f800cc8 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/types.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/types.tsx @@ -53,7 +53,6 @@ export interface Doc { title?: string; children?: Doc[]; childrenCount?: number; - content?: Base64; created_at: string; creator: string; deleted_at: string | null; @@ -82,9 +81,12 @@ export interface Doc { children_list: boolean; collaboration_auth: boolean; comment: boolean; + content_patch: boolean; + content_retrieve: boolean; destroy: boolean; duplicate: boolean; favorite: boolean; + formatted_content: boolean; invite_owner: boolean; link_configuration: boolean; media_auth: boolean; diff --git a/src/frontend/apps/impress/src/features/docs/doc-versioning/components/ModalConfirmationVersion.tsx b/src/frontend/apps/impress/src/features/docs/doc-versioning/components/ModalConfirmationVersion.tsx index c48f6eaf6..aaf9ef8c8 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-versioning/components/ModalConfirmationVersion.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-versioning/components/ModalConfirmationVersion.tsx @@ -10,12 +10,8 @@ import { createGlobalStyle } from 'styled-components'; import { Box, Text } from '@/components'; import { useEditorStore } from '@/docs/doc-editor/stores'; -import { - Doc, - base64ToYDoc, - useProviderStore, - useUpdateDoc, -} from '@/docs/doc-management/'; +import { Doc, base64ToYDoc, useProviderStore } from '@/docs/doc-management/'; +import { useDocContentUpdate } from '@/docs/doc-management/api/useDocContentUpdate'; import { useDocVersion } from '../api'; import { KEY_LIST_DOC_VERSIONS } from '../api/useDocVersions'; @@ -49,7 +45,7 @@ export const ModalConfirmationVersion = ({ const { toast } = useToastProvider(); const { provider } = useProviderStore(); const { threadStore } = useEditorStore(); - const { mutate: updateDoc } = useUpdateDoc({ + const { mutate: updateDocContent } = useDocContentUpdate({ listInvalidQueries: [KEY_LIST_DOC_VERSIONS], onSuccess: () => { const onDisplaySuccess = () => { @@ -104,7 +100,7 @@ export const ModalConfirmationVersion = ({ return; } - updateDoc({ + updateDocContent({ id: docId, content: version.content, }); diff --git a/src/frontend/apps/impress/src/features/docs/doc-versioning/types.ts b/src/frontend/apps/impress/src/features/docs/doc-versioning/types.ts index 9d71474bb..87359070b 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-versioning/types.ts +++ b/src/frontend/apps/impress/src/features/docs/doc-versioning/types.ts @@ -1,5 +1,3 @@ -import { Doc } from '../doc-management/types'; - export interface APIListVersions { count: number; is_truncated: boolean; @@ -15,7 +13,7 @@ export interface Versions { } export interface Version { - content: Doc['content']; + content: string; // Base64 encoded content last_modified: string; id: string; } diff --git a/src/frontend/apps/impress/src/features/service-worker/plugins/ApiPlugin.ts b/src/frontend/apps/impress/src/features/service-worker/plugins/ApiPlugin.ts index ecb64dd1f..18b9d3ce0 100644 --- a/src/frontend/apps/impress/src/features/service-worker/plugins/ApiPlugin.ts +++ b/src/frontend/apps/impress/src/features/service-worker/plugins/ApiPlugin.ts @@ -169,7 +169,6 @@ export class ApiPlugin implements WorkboxPlugin { const newResponse: Doc = { title: '', id: uuid, - content: '', created_at: new Date().toISOString(), creator: 'dummy-id', deleted_at: null, @@ -190,9 +189,12 @@ export class ApiPlugin implements WorkboxPlugin { children_list: true, collaboration_auth: true, comment: true, + content_patch: true, + content_retrieve: true, destroy: true, duplicate: true, favorite: true, + formatted_content: true, invite_owner: true, link_configuration: true, media_auth: true, diff --git a/src/frontend/apps/impress/src/pages/docs/[id]/index.tsx b/src/frontend/apps/impress/src/pages/docs/[id]/index.tsx index 3cb4ffb9d..76008b7fd 100644 --- a/src/frontend/apps/impress/src/pages/docs/[id]/index.tsx +++ b/src/frontend/apps/impress/src/pages/docs/[id]/index.tsx @@ -12,10 +12,8 @@ import { Doc, DocPage403, KEY_DOC, - useCollaboration, useDoc, useDocStore, - useProviderStore, useTrans, } from '@/docs/doc-management/'; import { KEY_AUTH, setAuthUrl, useAuth } from '@/features/auth'; @@ -24,7 +22,6 @@ import { getDocChildren, subPageToTree } from '@/features/docs/doc-tree/'; import { DocEditorSkeleton, useSkeletonStore } from '@/features/skeletons'; import { MainLayout } from '@/layouts'; import { MAIN_LAYOUT_ID } from '@/layouts/conf'; -import { useBroadcastStore } from '@/stores/useBroadcastStore'; import { NextPageWithLayout } from '@/types/next'; const DocEditor = dynamic( @@ -78,7 +75,6 @@ interface DocProps { } const DocPage = ({ id }: DocProps) => { - const { hasLostConnection, resetLostConnection } = useProviderStore(); const { isSkeletonVisible, setIsSkeletonVisible } = useSkeletonStore(); const { data: docQuery, @@ -88,7 +84,7 @@ const DocPage = ({ id }: DocProps) => { } = useDoc( { id }, { - staleTime: 0, + staleTime: 30000, // 30 seconds - We keep the data fresh as it is a highly collaborative page queryKey: [KEY_DOC, { id }], retryDelay: 1000, retry: (failureCount, error) => { @@ -103,10 +99,8 @@ const DocPage = ({ id }: DocProps) => { const [doc, setDoc] = useState(); const { setCurrentDoc } = useDocStore(); - const { addTask } = useBroadcastStore(); const queryClient = useQueryClient(); const { replace, asPath } = useRouter(); - useCollaboration(doc?.id, doc?.content); const { t } = useTranslation(); const { authenticated } = useAuth(); const { untitledDocument } = useTrans(); @@ -144,16 +138,6 @@ const DocPage = ({ id }: DocProps) => { }; }, [id]); - // Invalidate when provider store reports a lost connection - useEffect(() => { - if (hasLostConnection && doc?.id) { - void queryClient.invalidateQueries({ - queryKey: [KEY_DOC, { id: doc.id }], - }); - resetLostConnection(); - } - }, [hasLostConnection, doc?.id, queryClient, resetLostConnection]); - useEffect(() => { if (!docQuery || isFetching) { return; @@ -174,22 +158,6 @@ const DocPage = ({ id }: DocProps) => { }; }, [setCurrentDoc, setIsSkeletonVisible]); - /** - * We add a broadcast task to reset the query cache - * when the document visibility changes. - */ - useEffect(() => { - if (!doc?.id) { - return; - } - - addTask(`${KEY_DOC}-${doc.id}`, () => { - void queryClient.invalidateQueries({ - queryKey: [KEY_DOC, { id: doc.id }], - }); - }); - }, [addTask, doc?.id, queryClient]); - useEffect(() => { if (!isError || !error?.status || [403].includes(error.status)) { return;