From 3b11484837f574566a0c6335358d2af6fefdf19f Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Fri, 6 Dec 2024 09:17:09 +0100 Subject: [PATCH 1/5] =?UTF-8?q?=F0=9F=94=8A(changelog)=20add=20missing=20l?= =?UTF-8?q?ogs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some logs were missing or not at the good place. This commit replaces them correctly. --- CHANGELOG.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11b14a3b07..4a5491f977 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,9 +9,16 @@ and this project adheres to ## [Unreleased] +## Added + +- ✨(backend) annotate number of accesses on documents in list view #411 +- ✨(backend) allow users to mark/unmark documents as favorite #411 + ## Changed - 🔨(frontend) encapsulated title to its own component #474 +- 🐛(frontend) Fix hidden menu on Firefox #468 +- ⚡️(backend) optimize number of queries on document list view #411 ## [1.8.2] - 2024-11-28 @@ -32,8 +39,6 @@ and this project adheres to ## Added -- ✨(backend) annotate number of accesses on documents in list view #411 -- ✨(backend) allow users to mark/unmark documents as favorite #411 - 🌐(backend) add German translation #259 - 🌐(frontend) add German translation #255 - ✨(frontend) add a broadcast store #387 @@ -45,7 +50,6 @@ and this project adheres to ## Changed -- ⚡️(backend) optimize number of queries on document list view #411 - 🚸(backend) improve users similarity search and sort results #391 - ♻️(frontend) simplify stores #402 - ✨(frontend) update $css Box props type to add styled components RuleSet #423 From 7d2a1a76d0f0e810d1a289d4e8d44d8765c87b12 Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Tue, 3 Dec 2024 15:07:21 +0100 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=9A=9A(collaboration)=20change=20the?= =?UTF-8?q?=20websocket=20url=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We will have 2 urls targeting the server, better to improve the naming to avoid confusion. --- CHANGELOG.md | 1 + env.d/development/common.dist | 2 +- src/backend/core/api/viewsets.py | 2 +- src/backend/core/tests/test_api_config.py | 4 ++-- src/backend/impress/settings.py | 4 ++-- .../apps/e2e/__tests__/app-impress/config.spec.ts | 2 +- .../apps/impress/src/core/config/api/useConfig.tsx | 2 +- .../impress/src/core/config/hooks/useCollaborationUrl.tsx | 8 +++++--- 8 files changed, 14 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a5491f977..981e3b80fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to - 🔨(frontend) encapsulated title to its own component #474 - 🐛(frontend) Fix hidden menu on Firefox #468 - ⚡️(backend) optimize number of queries on document list view #411 +- 🚚(collaboration) change the websocket key name #480 ## [1.8.2] - 2024-11-28 diff --git a/env.d/development/common.dist b/env.d/development/common.dist index a52e45ade3..6833f09be5 100644 --- a/env.d/development/common.dist +++ b/env.d/development/common.dist @@ -53,7 +53,7 @@ AI_API_KEY=password AI_MODEL=llama # Collaboration -COLLABORATION_SERVER_URL=ws://localhost:4444 +COLLABORATION_WS_URL=ws://localhost:4444 # Frontend FRONTEND_THEME=dsfr diff --git a/src/backend/core/api/viewsets.py b/src/backend/core/api/viewsets.py index 75c0f5c4c8..7b63a7f3a6 100644 --- a/src/backend/core/api/viewsets.py +++ b/src/backend/core/api/viewsets.py @@ -1002,7 +1002,7 @@ def get(self, request): Return a dictionary of public settings. """ array_settings = [ - "COLLABORATION_SERVER_URL", + "COLLABORATION_WS_URL", "CRISP_WEBSITE_ID", "ENVIRONMENT", "FRONTEND_THEME", diff --git a/src/backend/core/tests/test_api_config.py b/src/backend/core/tests/test_api_config.py index 96888be54a..a5eb151f5f 100644 --- a/src/backend/core/tests/test_api_config.py +++ b/src/backend/core/tests/test_api_config.py @@ -16,7 +16,7 @@ @override_settings( - COLLABORATION_SERVER_URL="http://testcollab/", + COLLABORATION_WS_URL="http://testcollab/", CRISP_WEBSITE_ID="123", FRONTEND_THEME="test-theme", MEDIA_BASE_URL="http://testserver/", @@ -34,7 +34,7 @@ def test_api_config(is_authenticated): response = client.get("/api/v1.0/config/") assert response.status_code == HTTP_200_OK assert response.json() == { - "COLLABORATION_SERVER_URL": "http://testcollab/", + "COLLABORATION_WS_URL": "http://testcollab/", "CRISP_WEBSITE_ID": "123", "ENVIRONMENT": "test", "FRONTEND_THEME": "test-theme", diff --git a/src/backend/impress/settings.py b/src/backend/impress/settings.py index 3bb1d830a7..50140620f8 100755 --- a/src/backend/impress/settings.py +++ b/src/backend/impress/settings.py @@ -372,8 +372,8 @@ class Base(Configuration): SENTRY_DSN = values.Value(None, environ_name="SENTRY_DSN", environ_prefix=None) # Collaboration - COLLABORATION_SERVER_URL = values.Value( - None, environ_name="COLLABORATION_SERVER_URL", environ_prefix=None + COLLABORATION_WS_URL = values.Value( + None, environ_name="COLLABORATION_WS_URL", environ_prefix=None ) # Frontend diff --git a/src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts index e005ca3a57..1d8604df6c 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts @@ -6,7 +6,7 @@ import { createDoc } from './common'; const config = { CRISP_WEBSITE_ID: null, - COLLABORATION_SERVER_URL: 'ws://localhost:4444', + COLLABORATION_WS_URL: 'ws://localhost:4444', ENVIRONMENT: 'development', FRONTEND_THEME: 'dsfr', MEDIA_BASE_URL: 'http://localhost:8083', diff --git a/src/frontend/apps/impress/src/core/config/api/useConfig.tsx b/src/frontend/apps/impress/src/core/config/api/useConfig.tsx index 04bb06c6af..5cb6d3a964 100644 --- a/src/frontend/apps/impress/src/core/config/api/useConfig.tsx +++ b/src/frontend/apps/impress/src/core/config/api/useConfig.tsx @@ -7,7 +7,7 @@ interface ConfigResponse { LANGUAGES: [string, string][]; LANGUAGE_CODE: string; ENVIRONMENT: string; - COLLABORATION_SERVER_URL?: string; + COLLABORATION_WS_URL?: string; CRISP_WEBSITE_ID?: string; FRONTEND_THEME?: Theme; MEDIA_BASE_URL?: string; diff --git a/src/frontend/apps/impress/src/core/config/hooks/useCollaborationUrl.tsx b/src/frontend/apps/impress/src/core/config/hooks/useCollaborationUrl.tsx index d945cb2331..b06683729b 100644 --- a/src/frontend/apps/impress/src/core/config/hooks/useCollaborationUrl.tsx +++ b/src/frontend/apps/impress/src/core/config/hooks/useCollaborationUrl.tsx @@ -8,8 +8,10 @@ export const useCollaborationUrl = (room?: string) => { } const base = - conf?.COLLABORATION_SERVER_URL || - (typeof window !== 'undefined' ? `wss://${window.location.host}/ws` : ''); + conf?.COLLABORATION_WS_URL || + (typeof window !== 'undefined' + ? `wss://${window.location.host}/collaboration/ws/` + : ''); - return `${base}/${room}`; + return `${base}?room=${room}`; }; From e8987b211e65f7d551a8c226c9ffbc1889a8dbae Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Tue, 3 Dec 2024 15:24:38 +0100 Subject: [PATCH 3/5] =?UTF-8?q?=E2=99=BB=EF=B8=8F(frontend)=20stop=20to=20?= =?UTF-8?q?use=20provider=20with=20version?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Version are not editable, we don't need to activate the collaboration provider for them. Simplify the code by removing the provider from the version. --- CHANGELOG.md | 1 + .../doc-editor/components/BlockNoteEditor.tsx | 55 ++++++++++++++++--- .../docs/doc-editor/components/DocEditor.tsx | 49 ++++++++--------- .../docs/doc-header/components/DocHeader.tsx | 6 +- .../docs/doc-header/components/DocToolBox.tsx | 11 ++-- .../src/features/docs/doc-management/utils.ts | 11 ++++ .../components/ModalVersion.tsx | 27 +++++---- 7 files changed, 108 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 981e3b80fe..2a6657a777 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to - 🔨(frontend) encapsulated title to its own component #474 - 🐛(frontend) Fix hidden menu on Firefox #468 - ⚡️(backend) optimize number of queries on document list view #411 +- ♻️(frontend) stop to use provider with version #480 - 🚚(collaboration) change the websocket key name #480 diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx index 8c04127b8a..cafecdbf0d 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx @@ -6,6 +6,7 @@ import { useCreateBlockNote } from '@blocknote/react'; import { HocuspocusProvider } from '@hocuspocus/provider'; import React, { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; +import * as Y from 'yjs'; import { Box, TextErrors } from '@/components'; import { useAuthStore } from '@/core/auth'; @@ -65,19 +66,14 @@ const cssEditor = (readonly: boolean) => ` interface BlockNoteEditorProps { doc: Doc; provider: HocuspocusProvider; - storeId: string; } -export const BlockNoteEditor = ({ - doc, - provider, - storeId, -}: BlockNoteEditorProps) => { - const isVersion = doc.id !== storeId; +export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => { const { userData } = useAuthStore(); const { setEditor } = useEditorStore(); const { t } = useTranslation(); - const readOnly = !doc.abilities.partial_update || isVersion; + + const readOnly = !doc.abilities.partial_update; useSaveDoc(doc.id, provider.document, !readOnly); const { setHeadings, resetHeadings } = useHeadingStore(); const { i18n } = useTranslation(); @@ -174,3 +170,46 @@ export const BlockNoteEditor = ({ ); }; + +interface BlockNoteEditorVersionProps { + initialContent: Y.XmlFragment; +} + +export const BlockNoteEditorVersion = ({ + initialContent, +}: BlockNoteEditorVersionProps) => { + const readOnly = true; + const { setHeadings, resetHeadings } = useHeadingStore(); + + const editor = useCreateBlockNote( + { + collaboration: { + fragment: initialContent, + user: { + name: '', + color: '', + }, + provider: undefined, + }, + }, + [initialContent], + ); + + useEffect(() => { + setHeadings(editor); + + editor?.onEditorContentChange(() => { + setHeadings(editor); + }); + + return () => { + resetHeadings(); + }; + }, [editor, resetHeadings, setHeadings]); + + return ( + + + + ); +}; 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 50b2b73aa1..58cce28f6f 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 @@ -1,19 +1,23 @@ import { Alert, Loader, VariantType } from '@openfun/cunningham-react'; import { useRouter } from 'next/router'; -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import * as Y from 'yjs'; import { Box, Card, Text, TextErrors } from '@/components'; -import { useCollaborationUrl } from '@/core'; import { useCunninghamTheme } from '@/cunningham'; import { DocHeader } from '@/features/docs/doc-header'; -import { Doc, useDocStore } from '@/features/docs/doc-management'; +import { + Doc, + base64ToBlocknoteXmlFragment, + useDocStore, +} from '@/features/docs/doc-management'; import { Versions, useDocVersion } from '@/features/docs/doc-versioning/'; import { useResponsiveStore } from '@/stores'; import { useHeadingStore } from '../stores'; -import { BlockNoteEditor } from './BlockNoteEditor'; +import { BlockNoteEditor, BlockNoteEditorVersion } from './BlockNoteEditor'; import { IconOpenPanelEditor, PanelEditor } from './PanelEditor'; interface DocEditorProps { @@ -41,7 +45,7 @@ export const DocEditor = ({ doc }: DocEditorProps) => { return ( <> - + {!doc.abilities.partial_update && ( @@ -71,9 +75,9 @@ export const DocEditor = ({ doc }: DocEditorProps) => { $position="relative" > {isVersion ? ( - + ) : ( - + )} {!isMobile && } @@ -84,35 +88,34 @@ export const DocEditor = ({ doc }: DocEditorProps) => { }; interface DocVersionEditorProps { - doc: Doc; + docId: Doc['id']; versionId: Versions['version_id']; } -export const DocVersionEditor = ({ doc, versionId }: DocVersionEditorProps) => { +export const DocVersionEditor = ({ + docId, + versionId, +}: DocVersionEditorProps) => { const { data: version, isLoading, isError, error, } = useDocVersion({ - docId: doc.id, + docId, versionId, }); - const { createProvider, providers } = useDocStore(); - const collaborationUrl = useCollaborationUrl(versionId); const { replace } = useRouter(); + const [initialContent, setInitialContent] = useState(); useEffect(() => { - if (!version?.id || !collaborationUrl) { + if (!version?.content) { return; } - const provider = providers?.[version.id]; - if (!provider || provider.document.guid !== version.id) { - createProvider(collaborationUrl, version.id, version.content); - } - }, [createProvider, providers, version, collaborationUrl]); + setInitialContent(base64ToBlocknoteXmlFragment(version.content)); + }, [version?.content]); if (isError && error) { if (error.status === 404) { @@ -136,7 +139,7 @@ export const DocVersionEditor = ({ doc, versionId }: DocVersionEditorProps) => { ); } - if (isLoading || !version) { + if (isLoading || !version || !initialContent) { return ( @@ -144,11 +147,5 @@ export const DocVersionEditor = ({ doc, versionId }: DocVersionEditorProps) => { ); } - const provider = providers?.[version.id]; - - if (!provider) { - return null; - } - - return ; + return ; }; diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeader.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeader.tsx index 0e565b10fa..d169044aae 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeader.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeader.tsx @@ -5,7 +5,6 @@ import { css } from 'styled-components'; import { Box, Card, StyledLink, Text } from '@/components'; import { useCunninghamTheme } from '@/cunningham'; import { Doc, currentDocRole, useTrans } from '@/features/docs/doc-management'; -import { Versions } from '@/features/docs/doc-versioning'; import { useDate } from '@/hook'; import { useResponsiveStore } from '@/stores'; @@ -15,10 +14,9 @@ import { DocToolBox } from './DocToolBox'; interface DocHeaderProps { doc: Doc; - versionId?: Versions['version_id']; } -export const DocHeader = ({ doc, versionId }: DocHeaderProps) => { +export const DocHeader = ({ doc }: DocHeaderProps) => { const { colorsTokens } = useCunninghamTheme(); const { t } = useTranslation(); const { formatDate } = useDate(); @@ -69,7 +67,7 @@ export const DocHeader = ({ doc, versionId }: DocHeaderProps) => { $align="center" > - + { +export const DocToolBox = ({ doc }: DocToolBoxProps) => { + const { + query: { versionId }, + } = useRouter(); const { t } = useTranslation(); const [isModalShareOpen, setIsModalShareOpen] = useState(false); const [isModalRemoveOpen, setIsModalRemoveOpen] = useState(false); @@ -194,7 +197,7 @@ export const DocToolBox = ({ doc, versionId }: DocToolBoxProps) => { setIsModalVersionOpen(false)} docId={doc.id} - versionId={versionId} + versionId={versionId as string} /> )} diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/utils.ts b/src/frontend/apps/impress/src/features/docs/doc-management/utils.ts index 7ba6571a90..198c913c5d 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/utils.ts +++ b/src/frontend/apps/impress/src/features/docs/doc-management/utils.ts @@ -28,3 +28,14 @@ export const blocksToYDoc = (blocks: BasicBlock[], doc: Y.Doc) => { xmlFragment.push([xmlElement]); }); }; + +export const base64ToYDoc = (base64: string) => { + const uint8Array = Buffer.from(base64, 'base64'); + const ydoc = new Y.Doc(); + Y.applyUpdate(ydoc, uint8Array); + return ydoc; +}; + +export const base64ToBlocknoteXmlFragment = (base64: string) => { + return base64ToYDoc(base64).getXmlFragment('document-store'); +}; diff --git a/src/frontend/apps/impress/src/features/docs/doc-versioning/components/ModalVersion.tsx b/src/frontend/apps/impress/src/features/docs/doc-versioning/components/ModalVersion.tsx index 7bc4504696..8ffba2db38 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-versioning/components/ModalVersion.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-versioning/components/ModalVersion.tsx @@ -8,12 +8,16 @@ import { } from '@openfun/cunningham-react'; import { useRouter } from 'next/router'; import { useTranslation } from 'react-i18next'; -import * as Y from 'yjs'; import { Box, Text } from '@/components'; -import { toBase64 } from '@/features/docs/doc-editor'; -import { Doc, useDocStore, useUpdateDoc } from '@/features/docs/doc-management'; +import { + Doc, + base64ToYDoc, + useDocStore, + useUpdateDoc, +} from '@/features/docs/doc-management'; +import { useDocVersion } from '../api'; import { KEY_LIST_DOC_VERSIONS } from '../api/useDocVersions'; import { Versions } from '../types'; import { revertUpdate } from '../utils'; @@ -21,7 +25,6 @@ import { revertUpdate } from '../utils'; interface ModalVersionProps { onClose: () => void; docId: Doc['id']; - versionId: Versions['version_id']; } @@ -30,6 +33,10 @@ export const ModalVersion = ({ docId, versionId, }: ModalVersionProps) => { + const { data: version } = useDocVersion({ + docId, + versionId, + }); const { t } = useTranslation(); const { toast } = useToastProvider(); const { push } = useRouter(); @@ -42,7 +49,7 @@ export const ModalVersion = ({ void push(`/docs/${docId}`); }; - if (!providers?.[docId] || !providers?.[versionId]) { + if (!providers?.[docId] || !version?.content) { onDisplaySuccess(); return; } @@ -50,7 +57,7 @@ export const ModalVersion = ({ revertUpdate( providers[docId].document, providers[docId].document, - providers[versionId].document, + base64ToYDoc(version.content), ); onDisplaySuccess(); @@ -79,13 +86,13 @@ export const ModalVersion = ({ color="primary" fullWidth onClick={() => { - const newDoc = toBase64( - Y.encodeStateAsUpdate(providers?.[versionId].document), - ); + if (!version?.content) { + return; + } updateDoc({ id: docId, - content: newDoc, + content: version.content, }); onClose(); From dda4eea097a28e3c2f502a51ff222f07bdeb5896 Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Thu, 5 Dec 2024 23:10:25 +0100 Subject: [PATCH 4/5] =?UTF-8?q?=F0=9F=A9=BA(CI)=20wait=20for=20services=20?= =?UTF-8?q?to=20be=20ready?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We add a check to be sure all the services are ready before starting the e2e tests. --- .github/workflows/impress-frontend.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.github/workflows/impress-frontend.yml b/.github/workflows/impress-frontend.yml index 427b81bc22..60c35696ce 100644 --- a/.github/workflows/impress-frontend.yml +++ b/.github/workflows/impress-frontend.yml @@ -96,6 +96,28 @@ jobs: - name: Install Playwright Browsers run: cd src/frontend/apps/e2e && yarn install-playwright chromium + # Tool to wait for a service to be ready + - name: Install Dockerize + run: | + curl -sSL https://github.com/jwilder/dockerize/releases/download/v0.8.0/dockerize-linux-amd64-v0.8.0.tar.gz | sudo tar -C /usr/local/bin -xzv + + - name: Wait for services to be ready + run: | + printf "Minio check...\n" + dockerize -wait tcp://localhost:9000 -timeout 20s + printf "Keyclock check...\n" + dockerize -wait tcp://localhost:8080 -timeout 20s + printf "Server collaboration check...\n" + dockerize -wait tcp://localhost:4444 -timeout 20s + printf "Ngnix check...\n" + dockerize -wait tcp://localhost:8083 -timeout 20s + printf "DRF check...\n" + dockerize -wait tcp://localhost:8071 -timeout 20s + printf "Postgres Keyclock check...\n" + dockerize -wait tcp://localhost:5433 -timeout 20s + printf "Postgres back check...\n" + dockerize -wait tcp://localhost:15432 -timeout 20s + - name: Run e2e tests run: cd src/frontend/ && yarn e2e:test --project='chromium' From 8508fcddce586f8563771d4f201106155e732268 Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Fri, 6 Dec 2024 14:47:54 +0100 Subject: [PATCH 5/5] =?UTF-8?q?=E2=99=BB=EF=B8=8F(frontend)=20create=20use?= =?UTF-8?q?Headings=20hook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - We create the useHeadings hook to manage the headings of the document and staty DRY. - We use the headings store in IconOpenPanelEditor and TableContent, to avoid prop drilling. - We add a debounce on the onEditorContentChange to improve a bit the performance. --- .../doc-editor/components/BlockNoteEditor.tsx | 31 +++++-------------- .../docs/doc-editor/components/DocEditor.tsx | 7 ++--- .../doc-editor/components/PanelEditor.tsx | 20 ++++-------- .../docs/doc-editor/hook/useHeadings.tsx | 23 ++++++++++++++ .../components/TableContent.tsx | 9 ++---- 5 files changed, 42 insertions(+), 48 deletions(-) create mode 100644 src/frontend/apps/impress/src/features/docs/doc-editor/hook/useHeadings.tsx diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx index cafecdbf0d..6ab5294e4e 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx @@ -13,8 +13,9 @@ import { useAuthStore } from '@/core/auth'; import { Doc } from '@/features/docs/doc-management'; import { useUploadFile } from '../hook'; +import { useHeadings } from '../hook/useHeadings'; import useSaveDoc from '../hook/useSaveDoc'; -import { useEditorStore, useHeadingStore } from '../stores'; +import { useEditorStore } from '../stores'; import { randomColor } from '../utils'; import { BlockNoteToolbar } from './BlockNoteToolbar'; @@ -75,7 +76,6 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => { const readOnly = !doc.abilities.partial_update; useSaveDoc(doc.id, provider.document, !readOnly); - const { setHeadings, resetHeadings } = useHeadingStore(); const { i18n } = useTranslation(); const lang = i18n.language; @@ -126,6 +126,7 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => { }, [collabName, lang, provider, uploadFile], ); + useHeadings(editor); useEffect(() => { setEditor(editor); @@ -135,18 +136,6 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => { }; }, [setEditor, editor]); - useEffect(() => { - setHeadings(editor); - - editor?.onEditorContentChange(() => { - setHeadings(editor); - }); - - return () => { - resetHeadings(); - }; - }, [editor, resetHeadings, setHeadings]); - return ( {errorAttachment && ( @@ -179,8 +168,7 @@ export const BlockNoteEditorVersion = ({ initialContent, }: BlockNoteEditorVersionProps) => { const readOnly = true; - const { setHeadings, resetHeadings } = useHeadingStore(); - + const { setEditor } = useEditorStore(); const editor = useCreateBlockNote( { collaboration: { @@ -194,18 +182,15 @@ export const BlockNoteEditorVersion = ({ }, [initialContent], ); + useHeadings(editor); useEffect(() => { - setHeadings(editor); - - editor?.onEditorContentChange(() => { - setHeadings(editor); - }); + setEditor(editor); return () => { - resetHeadings(); + setEditor(undefined); }; - }, [editor, resetHeadings, setHeadings]); + }, [setEditor, editor]); return ( 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 58cce28f6f..935e193acd 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 @@ -15,8 +15,6 @@ import { import { Versions, useDocVersion } from '@/features/docs/doc-versioning/'; import { useResponsiveStore } from '@/stores'; -import { useHeadingStore } from '../stores'; - import { BlockNoteEditor, BlockNoteEditorVersion } from './BlockNoteEditor'; import { IconOpenPanelEditor, PanelEditor } from './PanelEditor'; @@ -29,7 +27,6 @@ export const DocEditor = ({ doc }: DocEditorProps) => { query: { versionId }, } = useRouter(); const { t } = useTranslation(); - const { headings } = useHeadingStore(); const { isMobile } = useResponsiveStore(); const isVersion = versionId && typeof versionId === 'string'; @@ -79,9 +76,9 @@ export const DocEditor = ({ doc }: DocEditorProps) => { ) : ( )} - {!isMobile && } + {!isMobile && } - + ); diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/PanelEditor.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/PanelEditor.tsx index 5a79622d82..0f325f9512 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/PanelEditor.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/PanelEditor.tsx @@ -8,18 +8,13 @@ import { TableContent } from '@/features/docs/doc-table-content'; import { VersionList } from '@/features/docs/doc-versioning'; import { useResponsiveStore } from '@/stores'; -import { usePanelEditorStore } from '../stores'; -import { HeadingBlock } from '../types'; +import { useHeadingStore, usePanelEditorStore } from '../stores'; interface PanelProps { doc: Doc; - headings: HeadingBlock[]; } -export const PanelEditor = ({ - doc, - headings, -}: PropsWithChildren) => { +export const PanelEditor = ({ doc }: PropsWithChildren) => { const { t } = useTranslation(); const { colorsTokens } = useCunninghamTheme(); const { isMobile } = useResponsiveStore(); @@ -63,7 +58,7 @@ export const PanelEditor = ({ `} $maxHeight="99vh" > - {isMobile && } + {isMobile && } )} - {isPanelTableContentOpen && } + {isPanelTableContentOpen && } {!isPanelTableContentOpen && doc.abilities.versions_list && ( )} @@ -136,11 +131,8 @@ export const PanelEditor = ({ ); }; -interface IconOpenPanelEditorProps { - headings: HeadingBlock[]; -} - -export const IconOpenPanelEditor = ({ headings }: IconOpenPanelEditorProps) => { +export const IconOpenPanelEditor = () => { + const { headings } = useHeadingStore(); const { t } = useTranslation(); const { setIsPanelOpen, isPanelOpen, setIsPanelTableContentOpen } = usePanelEditorStore(); diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useHeadings.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useHeadings.tsx new file mode 100644 index 0000000000..9c60848731 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useHeadings.tsx @@ -0,0 +1,23 @@ +import { BlockNoteEditor } from '@blocknote/core'; +import { useEffect } from 'react'; + +import { useHeadingStore } from '../stores'; + +export const useHeadings = (editor: BlockNoteEditor) => { + const { setHeadings, resetHeadings } = useHeadingStore(); + + useEffect(() => { + setHeadings(editor); + + let timeout: NodeJS.Timeout; + editor?.onEditorContentChange(() => { + clearTimeout(timeout); + timeout = setTimeout(() => setHeadings(editor), 200); + }); + + return () => { + clearTimeout(timeout); + resetHeadings(); + }; + }, [editor, resetHeadings, setHeadings]); +}; diff --git a/src/frontend/apps/impress/src/features/docs/doc-table-content/components/TableContent.tsx b/src/frontend/apps/impress/src/features/docs/doc-table-content/components/TableContent.tsx index f765eb7990..559b268b09 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-table-content/components/TableContent.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-table-content/components/TableContent.tsx @@ -2,16 +2,13 @@ import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Box, BoxButton, Text } from '@/components'; -import { HeadingBlock, useEditorStore } from '@/features/docs/doc-editor'; +import { useEditorStore, useHeadingStore } from '@/features/docs/doc-editor'; import { useResponsiveStore } from '@/stores'; import { Heading } from './Heading'; -interface TableContentProps { - headings: HeadingBlock[]; -} - -export const TableContent = ({ headings }: TableContentProps) => { +export const TableContent = () => { + const { headings } = useHeadingStore(); const { editor } = useEditorStore(); const { isMobile } = useResponsiveStore(); const { t } = useTranslation();