From b6c6fc82178438b46b03483e499dd1d1fbdd0169 Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Fri, 10 Apr 2026 14:35:05 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=91=94(frontend)=20integrate=20dedicated?= =?UTF-8?q?=20content=20endpoint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To improve the performance of loading document content, we have implemented a dedicated endpoint for fetching document content. This allows us to load the document metadata and content separately. We updated the different components to utilize this new endpoint, ensuring that the document content is fetched and updated correctly. --- .../e2e/__tests__/app-impress/utils-common.ts | 23 +++--- .../e2e/__tests__/app-impress/utils-export.ts | 21 ++--- .../docs/doc-editor/components/DocEditor.tsx | 2 + .../hook/__tests__/useSaveDoc.test.tsx | 34 +++++---- .../docs/doc-editor/hook/useSaveDoc.tsx | 21 +++-- .../docs/doc-management/api/useDocContent.tsx | 41 ++++++++++ .../api/useDocContentUpdate.tsx | 76 +++++++++++++++++++ .../doc-management/api/useDuplicateDoc.tsx | 9 ++- .../docs/doc-management/api/useUpdateDoc.tsx | 18 ++--- .../components/ModalRemoveDoc.tsx | 3 +- .../doc-management/hooks/useCollaboration.tsx | 70 +++++++++++++++-- .../features/docs/doc-management/types.tsx | 4 +- .../components/ModalConfirmationVersion.tsx | 12 +-- .../src/features/docs/doc-versioning/types.ts | 4 +- .../service-worker/plugins/ApiPlugin.ts | 4 +- .../impress/src/pages/docs/[id]/index.tsx | 34 +-------- 16 files changed, 258 insertions(+), 118 deletions(-) create mode 100644 src/frontend/apps/impress/src/features/docs/doc-management/api/useDocContent.tsx create mode 100644 src/frontend/apps/impress/src/features/docs/doc-management/api/useDocContentUpdate.tsx 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;