diff --git a/src/components/document-read-only-page/document-read-only-page.tsx b/src/components/document-read-only-page/document-read-only-page.tsx index 07327bf5b..f3f3afc71 100644 --- a/src/components/document-read-only-page/document-read-only-page.tsx +++ b/src/components/document-read-only-page/document-read-only-page.tsx @@ -19,10 +19,11 @@ import { RenderIframe } from '../editor-page/renderer-pane/render-iframe' import { DocumentInfobar } from './document-infobar' import { ErrorWhileLoadingNoteAlert } from './ErrorWhileLoadingNoteAlert' import { LoadingNoteAlert } from './LoadingNoteAlert' -import { RendererType } from '../render-page/rendering-message' +import { RendererType } from '../render-page/window-post-message-communicator/rendering-message' import { useApplicationState } from '../../hooks/common/use-application-state' -import { IframeEditorToRendererCommunicatorContextProvider } from '../editor-page/render-context/iframe-editor-to-renderer-communicator-context-provider' import { useNoteMarkdownContentWithoutFrontmatter } from '../../hooks/common/use-note-markdown-content-without-frontmatter' +import { EditorToRendererCommunicatorContextProvider } from '../editor-page/render-context/editor-to-renderer-communicator-context-provider' +import { useSendFrontmatterInfoFromReduxToRenderer } from '../editor-page/renderer-pane/hooks/use-send-frontmatter-info-from-redux-to-renderer' export const DocumentReadOnlyPage: React.FC = () => { useTranslation() @@ -35,9 +36,10 @@ export const DocumentReadOnlyPage: React.FC = () => { const [error, loading] = useLoadNoteFromServer() const markdownContent = useNoteMarkdownContentWithoutFrontmatter() const noteDetails = useApplicationState((state) => state.noteDetails) + useSendFrontmatterInfoFromReduxToRenderer() return ( - +
@@ -63,7 +65,7 @@ export const DocumentReadOnlyPage: React.FC = () => { />
-
+ ) } diff --git a/src/components/editor-page/document-bar/document-info/document-info-line-word-count.tsx b/src/components/editor-page/document-bar/document-info/document-info-line-word-count.tsx index e8338465f..ff3ce93e4 100644 --- a/src/components/editor-page/document-bar/document-info/document-info-line-word-count.tsx +++ b/src/components/editor-page/document-bar/document-info/document-info-line-word-count.tsx @@ -4,13 +4,18 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import React, { useEffect, useState } from 'react' +import React, { useCallback, useState } from 'react' import { Trans, useTranslation } from 'react-i18next' import { ShowIf } from '../../../common/show-if/show-if' import { DocumentInfoLine } from './document-info-line' import { UnitalicBoldText } from './unitalic-bold-text' -import { useIFrameEditorToRendererCommunicator } from '../../render-context/iframe-editor-to-renderer-communicator-context-provider' -import { useApplicationState } from '../../../../hooks/common/use-application-state' +import { useEditorToRendererCommunicator } from '../../render-context/editor-to-renderer-communicator-context-provider' +import { + CommunicationMessageType, + OnWordCountCalculatedMessage +} from '../../../render-page/window-post-message-communicator/rendering-message' +import { useEditorReceiveHandler } from '../../../render-page/window-post-message-communicator/hooks/use-editor-receive-handler' +import { useEffectOnRendererReady } from '../../../render-page/window-post-message-communicator/hooks/use-effect-on-renderer-ready' /** * Creates a new info line for the document information dialog that holds the @@ -18,24 +23,19 @@ import { useApplicationState } from '../../../../hooks/common/use-application-st */ export const DocumentInfoLineWordCount: React.FC = () => { useTranslation() - const iframeEditorToRendererCommunicator = useIFrameEditorToRendererCommunicator() + const editorToRendererCommunicator = useEditorToRendererCommunicator() const [wordCount, setWordCount] = useState(null) - const rendererReady = useApplicationState((state) => state.rendererStatus.rendererReady) - useEffect(() => { - iframeEditorToRendererCommunicator.onWordCountCalculated((words) => { - setWordCount(words) - }) - return () => { - iframeEditorToRendererCommunicator.onWordCountCalculated(undefined) - } - }, [iframeEditorToRendererCommunicator, setWordCount]) + useEditorReceiveHandler( + CommunicationMessageType.ON_WORD_COUNT_CALCULATED, + useCallback((values: OnWordCountCalculatedMessage) => setWordCount(values.words), [setWordCount]) + ) - useEffect(() => { - if (rendererReady) { - iframeEditorToRendererCommunicator.sendGetWordCount() - } - }, [iframeEditorToRendererCommunicator, rendererReady]) + useEffectOnRendererReady( + useCallback(() => { + editorToRendererCommunicator.sendMessageToOtherSide({ type: CommunicationMessageType.GET_WORD_COUNT }) + }, [editorToRendererCommunicator]) + ) return ( diff --git a/src/components/editor-page/editor-document-renderer/editor-document-renderer.tsx b/src/components/editor-page/editor-document-renderer/editor-document-renderer.tsx index 4cc4799f5..dbcd78cbe 100644 --- a/src/components/editor-page/editor-document-renderer/editor-document-renderer.tsx +++ b/src/components/editor-page/editor-document-renderer/editor-document-renderer.tsx @@ -7,6 +7,7 @@ import React from 'react' import { RenderIframe, RenderIframeProps } from '../renderer-pane/render-iframe' import { useNoteMarkdownContentWithoutFrontmatter } from '../../../hooks/common/use-note-markdown-content-without-frontmatter' +import { useSendFrontmatterInfoFromReduxToRenderer } from '../renderer-pane/hooks/use-send-frontmatter-info-from-redux-to-renderer' export type EditorDocumentRendererProps = Omit @@ -17,5 +18,8 @@ export type EditorDocumentRendererProps = Omit = (props) => { const markdownContent = useNoteMarkdownContentWithoutFrontmatter() + + useSendFrontmatterInfoFromReduxToRenderer() + return } diff --git a/src/components/editor-page/editor-page.tsx b/src/components/editor-page/editor-page.tsx index 6fb26455b..cd8e75dcb 100644 --- a/src/components/editor-page/editor-page.tsx +++ b/src/components/editor-page/editor-page.tsx @@ -21,14 +21,14 @@ import { useViewModeShortcuts } from './hooks/useViewModeShortcuts' import { Sidebar } from './sidebar/sidebar' import { Splitter } from './splitter/splitter' import { DualScrollState, ScrollState } from './synced-scroll/scroll-props' -import { RendererType } from '../render-page/rendering-message' +import { RendererType } from '../render-page/window-post-message-communicator/rendering-message' import { useEditorModeFromUrl } from './hooks/useEditorModeFromUrl' import { UiNotifications } from '../notifications/ui-notifications' import { useNotificationTest } from './use-notification-test' -import { IframeEditorToRendererCommunicatorContextProvider } from './render-context/iframe-editor-to-renderer-communicator-context-provider' import { useUpdateLocalHistoryEntry } from './hooks/useUpdateLocalHistoryEntry' import { useApplicationState } from '../../hooks/common/use-application-state' import { EditorDocumentRenderer } from './editor-document-renderer/editor-document-renderer' +import { EditorToRendererCommunicatorContextProvider } from './render-context/editor-to-renderer-communicator-context-provider' export interface EditorPagePathParams { id: string @@ -53,7 +53,11 @@ export const EditorPage: React.FC = () => { const onMarkdownRendererScroll = useCallback( (newScrollState: ScrollState) => { if (scrollSource.current === ScrollSource.RENDERER && editorSyncScroll) { - setScrollState((old) => ({ editorScrollState: newScrollState, rendererScrollState: old.rendererScrollState })) + setScrollState((old) => { + const newState = { editorScrollState: newScrollState, rendererScrollState: old.rendererScrollState } + console.debug('[EditorPage] set scroll state because of renderer scroll', newState) + return newState + }) } }, [editorSyncScroll] @@ -62,7 +66,11 @@ export const EditorPage: React.FC = () => { const onEditorScroll = useCallback( (newScrollState: ScrollState) => { if (scrollSource.current === ScrollSource.EDITOR && editorSyncScroll) { - setScrollState((old) => ({ rendererScrollState: newScrollState, editorScrollState: old.editorScrollState })) + setScrollState((old) => { + const newState = { rendererScrollState: newScrollState, editorScrollState: old.editorScrollState } + console.debug('[EditorPage] set scroll state because of editor scroll', newState) + return newState + }) } }, [editorSyncScroll] @@ -79,10 +87,12 @@ export const EditorPage: React.FC = () => { const setRendererToScrollSource = useCallback(() => { scrollSource.current = ScrollSource.RENDERER + console.debug('[EditorPage] Make renderer scroll source') }, []) const setEditorToScrollSource = useCallback(() => { scrollSource.current = ScrollSource.EDITOR + console.debug('[EditorPage] Make editor scroll source') }, []) useNotificationTest() @@ -114,7 +124,7 @@ export const EditorPage: React.FC = () => { ) return ( - +
@@ -136,7 +146,7 @@ export const EditorPage: React.FC = () => {
-
+ ) } diff --git a/src/components/editor-page/render-context/editor-to-renderer-communicator-context-provider.tsx b/src/components/editor-page/render-context/editor-to-renderer-communicator-context-provider.tsx new file mode 100644 index 000000000..034cf6b93 --- /dev/null +++ b/src/components/editor-page/render-context/editor-to-renderer-communicator-context-provider.tsx @@ -0,0 +1,37 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import React, { createContext, useContext, useMemo } from 'react' +import { EditorToRendererCommunicator } from '../../render-page/window-post-message-communicator/editor-to-renderer-communicator' + +const EditorToRendererCommunicatorContext = createContext(undefined) + +/** + * Provides the {@link EditorToRendererCommunicator editor to renderer iframe communicator} that is set by a {@link EditorToRendererCommunicatorContextProvider context provider}. + * + * @return the received communicator + * @throws {Error} if no communicator was received + */ +export const useEditorToRendererCommunicator: () => EditorToRendererCommunicator = () => { + const communicatorFromContext = useContext(EditorToRendererCommunicatorContext) + if (!communicatorFromContext) { + throw new Error('No editor-to-renderer-iframe-communicator received. Did you forget to use the provider component?') + } + return communicatorFromContext +} + +/** + * Provides a {@link EditorToRendererCommunicator editor to renderer communicator} for the child components via Context. + */ +export const EditorToRendererCommunicatorContextProvider: React.FC = ({ children }) => { + const communicator = useMemo(() => new EditorToRendererCommunicator(), []) + + return ( + + {children} + + ) +} diff --git a/src/components/editor-page/render-context/iframe-editor-to-renderer-communicator-context-provider.tsx b/src/components/editor-page/render-context/iframe-editor-to-renderer-communicator-context-provider.tsx deleted file mode 100644 index ab6255363..000000000 --- a/src/components/editor-page/render-context/iframe-editor-to-renderer-communicator-context-provider.tsx +++ /dev/null @@ -1,39 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import React, { createContext, useContext, useMemo } from 'react' -import { IframeEditorToRendererCommunicator } from '../../render-page/iframe-editor-to-renderer-communicator' - -const IFrameEditorToRendererCommunicatorContext = createContext( - undefined -) - -/** - * Provides the {@link IframeEditorToRendererCommunicator editor to renderer iframe communicator} that is set by a {@link IframeEditorToRendererCommunicatorContextProvider context provider}. - * - * @return the received communicator - * @throws Error if no communicator was received - */ -export const useIFrameEditorToRendererCommunicator: () => IframeEditorToRendererCommunicator = () => { - const communicatorFromContext = useContext(IFrameEditorToRendererCommunicatorContext) - if (!communicatorFromContext) { - throw new Error('No editor-to-renderer-iframe-communicator received. Did you forget to use the provider component?') - } - return communicatorFromContext -} - -export const IframeEditorToRendererCommunicatorContextProvider: React.FC = ({ children }) => { - const currentIFrameCommunicator = useMemo( - () => new IframeEditorToRendererCommunicator(), - [] - ) - - return ( - - {children} - - ) -} diff --git a/src/components/editor-page/render-context/iframe-renderer-to-editor-communicator-context-provider.tsx b/src/components/editor-page/render-context/iframe-renderer-to-editor-communicator-context-provider.tsx deleted file mode 100644 index 2d03fb544..000000000 --- a/src/components/editor-page/render-context/iframe-renderer-to-editor-communicator-context-provider.tsx +++ /dev/null @@ -1,49 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import React, { createContext, useContext, useEffect, useMemo } from 'react' -import { IframeRendererToEditorCommunicator } from '../../render-page/iframe-renderer-to-editor-communicator' -import { useSelector } from 'react-redux' -import { ApplicationState } from '../../../redux' - -const IFrameRendererToEditorCommunicatorContext = createContext( - undefined -) - -/** - * Provides the {@link IframeRendererToEditorCommunicator renderer to editor iframe communicator} that is set by a {@link IframeRendererToEditorCommunicatorContextProvider context provider}. - * - * @return the received communicator - * @throws Error if no communicator was received - */ -export const useIFrameRendererToEditorCommunicator: () => IframeRendererToEditorCommunicator = () => { - const communicatorFromContext = useContext(IFrameRendererToEditorCommunicatorContext) - if (!communicatorFromContext) { - throw new Error('No renderer-to-editor-iframe-communicator received. Did you forget to use the provider component?') - } - return communicatorFromContext -} - -export const IframeRendererToEditorCommunicatorContextProvider: React.FC = ({ children }) => { - const editorOrigin = useSelector((state: ApplicationState) => state.config.iframeCommunication.editorOrigin) - const currentIFrameCommunicator = useMemo(() => { - const newCommunicator = new IframeRendererToEditorCommunicator() - newCommunicator.setMessageTarget(window.parent, editorOrigin) - return newCommunicator - }, [editorOrigin]) - - useEffect(() => { - const currentIFrame = currentIFrameCommunicator - currentIFrame?.sendRendererReady() - return () => currentIFrame?.unregisterEventListener() - }, [currentIFrameCommunicator]) - - return ( - - {children} - - ) -} diff --git a/src/components/editor-page/render-context/renderer-to-editor-communicator-context-provider.tsx b/src/components/editor-page/render-context/renderer-to-editor-communicator-context-provider.tsx new file mode 100644 index 000000000..7199e0a45 --- /dev/null +++ b/src/components/editor-page/render-context/renderer-to-editor-communicator-context-provider.tsx @@ -0,0 +1,54 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import React, { createContext, useContext, useEffect, useMemo } from 'react' +import { useSelector } from 'react-redux' +import { ApplicationState } from '../../../redux' +import { RendererToEditorCommunicator } from '../../render-page/window-post-message-communicator/renderer-to-editor-communicator' +import { CommunicationMessageType } from '../../render-page/window-post-message-communicator/rendering-message' + +const RendererToEditorCommunicatorContext = createContext(undefined) + +/** + * Provides the {@link RendererToEditorCommunicator renderer to editor iframe communicator} that is set by a {@link RendererToEditorCommunicatorContextProvider context provider}. + * + * @return the received communicator + * @throws {Error} if no communicator was received + */ +export const useRendererToEditorCommunicator: () => RendererToEditorCommunicator = () => { + const communicatorFromContext = useContext(RendererToEditorCommunicatorContext) + if (!communicatorFromContext) { + throw new Error('No renderer-to-editor-iframe-communicator received. Did you forget to use the provider component?') + } + return communicatorFromContext +} + +export const RendererToEditorCommunicatorContextProvider: React.FC = ({ children }) => { + const editorOrigin = useSelector((state: ApplicationState) => state.config.iframeCommunication.editorOrigin) + const communicator = useMemo(() => { + const newCommunicator = new RendererToEditorCommunicator() + newCommunicator.setMessageTarget(window.parent, editorOrigin) + return newCommunicator + }, [editorOrigin]) + + useEffect(() => { + const currentCommunicator = communicator + currentCommunicator.enableCommunication() + currentCommunicator.sendMessageToOtherSide({ + type: CommunicationMessageType.RENDERER_READY + }) + return () => currentCommunicator?.unregisterEventListener() + }, [communicator]) + + /** + * Provides a {@link RendererToEditorCommunicator renderer to editor communicator} for the child components via Context. + */ + return ( + + {children} + + ) +} diff --git a/src/components/editor-page/renderer-pane/communicator-image-lightbox.tsx b/src/components/editor-page/renderer-pane/communicator-image-lightbox.tsx new file mode 100644 index 000000000..d6e79fd30 --- /dev/null +++ b/src/components/editor-page/renderer-pane/communicator-image-lightbox.tsx @@ -0,0 +1,44 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import React, { useCallback, useState } from 'react' +import { ImageLightboxModal } from '../../markdown-renderer/replace-components/image/image-lightbox-modal' +import { + CommunicationMessageType, + ImageClickedMessage, + ImageDetails +} from '../../render-page/window-post-message-communicator/rendering-message' +import { useEditorReceiveHandler } from '../../render-page/window-post-message-communicator/hooks/use-editor-receive-handler' + +export const CommunicatorImageLightbox: React.FC = () => { + const [lightboxDetails, setLightboxDetails] = useState(undefined) + const [show, setShow] = useState(false) + + useEditorReceiveHandler( + CommunicationMessageType.IMAGE_CLICKED, + useCallback( + (values: ImageClickedMessage) => { + setLightboxDetails?.(values.details) + setShow(true) + }, + [setLightboxDetails] + ) + ) + + const hideLightbox = useCallback(() => { + setShow(false) + }, []) + + return ( + + ) +} diff --git a/src/components/editor-page/renderer-pane/hooks/use-on-iframe-load.ts b/src/components/editor-page/renderer-pane/hooks/use-on-iframe-load.ts index b70af9603..c3b8a64ca 100644 --- a/src/components/editor-page/renderer-pane/hooks/use-on-iframe-load.ts +++ b/src/components/editor-page/renderer-pane/hooks/use-on-iframe-load.ts @@ -5,11 +5,11 @@ */ import { RefObject, useCallback, useRef } from 'react' -import { IframeEditorToRendererCommunicator } from '../../../render-page/iframe-editor-to-renderer-communicator' +import { EditorToRendererCommunicator } from '../../../render-page/window-post-message-communicator/editor-to-renderer-communicator' export const useOnIframeLoad = ( frameReference: RefObject, - iframeCommunicator: IframeEditorToRendererCommunicator, + iframeCommunicator: EditorToRendererCommunicator, rendererOrigin: string, renderPageUrl: string, onNavigateAway: () => void diff --git a/src/components/editor-page/renderer-pane/hooks/use-send-dark-mode-status-to-renderer.ts b/src/components/editor-page/renderer-pane/hooks/use-send-dark-mode-status-to-renderer.ts new file mode 100644 index 000000000..fd9dbd5e6 --- /dev/null +++ b/src/components/editor-page/renderer-pane/hooks/use-send-dark-mode-status-to-renderer.ts @@ -0,0 +1,29 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { useIsDarkModeActivated } from '../../../../hooks/common/use-is-dark-mode-activated' +import { useMemo } from 'react' +import { CommunicationMessageType } from '../../../render-page/window-post-message-communicator/rendering-message' +import { useSendToRenderer } from '../../../render-page/window-post-message-communicator/hooks/use-send-to-renderer' + +/** + * Sends the current dark mode setting to the renderer. + * + * @param forcedDarkMode Overwrites the value from the global application states if set. + */ +export const useSendDarkModeStatusToRenderer = (forcedDarkMode?: boolean): void => { + const savedDarkMode = useIsDarkModeActivated() + + useSendToRenderer( + useMemo( + () => ({ + type: CommunicationMessageType.SET_DARKMODE, + activated: forcedDarkMode ?? savedDarkMode + }), + [forcedDarkMode, savedDarkMode] + ) + ) +} diff --git a/src/components/editor-page/renderer-pane/hooks/use-send-frontmatter-info-from-redux-to-renderer.ts b/src/components/editor-page/renderer-pane/hooks/use-send-frontmatter-info-from-redux-to-renderer.ts new file mode 100644 index 000000000..1d24fc835 --- /dev/null +++ b/src/components/editor-page/renderer-pane/hooks/use-send-frontmatter-info-from-redux-to-renderer.ts @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { useSendToRenderer } from '../../../render-page/window-post-message-communicator/hooks/use-send-to-renderer' +import { useMemo } from 'react' +import { CommunicationMessageType } from '../../../render-page/window-post-message-communicator/rendering-message' +import { useApplicationState } from '../../../../hooks/common/use-application-state' + +/** + * Extracts the {@link RendererFrontmatterInfo frontmatter data} + * from the global application state and sends it to the renderer. + */ +export const useSendFrontmatterInfoFromReduxToRenderer = (): void => { + const frontmatterInfo = useApplicationState((state) => state.noteDetails.frontmatterRendererInfo) + + return useSendToRenderer( + useMemo( + () => ({ + type: CommunicationMessageType.SET_FRONTMATTER_INFO, + frontmatterInfo + }), + [frontmatterInfo] + ) + ) +} diff --git a/src/components/editor-page/renderer-pane/hooks/use-send-markdown-to-renderer.ts b/src/components/editor-page/renderer-pane/hooks/use-send-markdown-to-renderer.ts new file mode 100644 index 000000000..0f39e78bd --- /dev/null +++ b/src/components/editor-page/renderer-pane/hooks/use-send-markdown-to-renderer.ts @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { useSendToRenderer } from '../../../render-page/window-post-message-communicator/hooks/use-send-to-renderer' +import { useMemo } from 'react' +import { CommunicationMessageType } from '../../../render-page/window-post-message-communicator/rendering-message' + +/** + * Sends the given markdown content to the renderer. + * + * @param markdownContent The markdown content to send. + */ +export const useSendMarkdownToRenderer = (markdownContent: string): void => { + return useSendToRenderer( + useMemo( + () => ({ + type: CommunicationMessageType.SET_MARKDOWN_CONTENT, + content: markdownContent + }), + [markdownContent] + ) + ) +} diff --git a/src/components/editor-page/renderer-pane/hooks/use-send-scroll-state.ts b/src/components/editor-page/renderer-pane/hooks/use-send-scroll-state.ts new file mode 100644 index 000000000..bed102538 --- /dev/null +++ b/src/components/editor-page/renderer-pane/hooks/use-send-scroll-state.ts @@ -0,0 +1,30 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { useCallback, useRef } from 'react' +import { ScrollState } from '../../synced-scroll/scroll-props' +import { CommunicationMessageType } from '../../../render-page/window-post-message-communicator/rendering-message' +import { useEffectOnRendererReady } from '../../../render-page/window-post-message-communicator/hooks/use-effect-on-renderer-ready' +import equal from 'fast-deep-equal' +import { useEditorToRendererCommunicator } from '../../render-context/editor-to-renderer-communicator-context-provider' + +/** + * Sends the given {@link ScrollState scroll state} to the renderer if the content changed. + * @param scrollState The scroll state to send + */ +export const useSendScrollState = (scrollState: ScrollState | undefined): void => { + const iframeCommunicator = useEditorToRendererCommunicator() + const oldScrollState = useRef(undefined) + + useEffectOnRendererReady( + useCallback(() => { + if (scrollState && !equal(scrollState, oldScrollState.current)) { + oldScrollState.current = scrollState + iframeCommunicator.sendMessageToOtherSide({ type: CommunicationMessageType.SET_SCROLL_STATE, scrollState }) + } + }, [iframeCommunicator, scrollState]) + ) +} diff --git a/src/components/editor-page/renderer-pane/render-iframe.tsx b/src/components/editor-page/renderer-pane/render-iframe.tsx index 4a30402fd..05cf7850a 100644 --- a/src/components/editor-page/renderer-pane/render-iframe.tsx +++ b/src/components/editor-page/renderer-pane/render-iframe.tsx @@ -3,18 +3,27 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import equal from 'fast-deep-equal' import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react' import { useApplicationState } from '../../../hooks/common/use-application-state' -import { useIsDarkModeActivated } from '../../../hooks/common/use-is-dark-mode-activated' import { isTestMode } from '../../../utils/test-modes' import { RendererProps } from '../../render-page/markdown-document' -import { ImageDetails, RendererType } from '../../render-page/rendering-message' -import { useIFrameEditorToRendererCommunicator } from '../render-context/iframe-editor-to-renderer-communicator-context-provider' -import { ScrollState } from '../synced-scroll/scroll-props' +import { + CommunicationMessageType, + OnFirstHeadingChangeMessage, + OnHeightChangeMessage, + OnTaskCheckboxChangeMessage, + RendererType, + SetScrollStateMessage +} from '../../render-page/window-post-message-communicator/rendering-message' +import { useEditorToRendererCommunicator } from '../render-context/editor-to-renderer-communicator-context-provider' import { useOnIframeLoad } from './hooks/use-on-iframe-load' -import { ShowOnPropChangeImageLightbox } from './show-on-prop-change-image-lightbox' +import { CommunicatorImageLightbox } from './communicator-image-lightbox' import { setRendererStatus } from '../../../redux/renderer-status/methods' +import { useEditorReceiveHandler } from '../../render-page/window-post-message-communicator/hooks/use-editor-receive-handler' +import { useIsRendererReady } from '../../render-page/window-post-message-communicator/hooks/use-is-renderer-ready' +import { useSendDarkModeStatusToRenderer } from './hooks/use-send-dark-mode-status-to-renderer' +import { useSendMarkdownToRenderer } from './hooks/use-send-markdown-to-renderer' +import { useSendScrollState } from './hooks/use-send-scroll-state' export interface RenderIframeProps extends RendererProps { rendererType: RendererType @@ -33,16 +42,12 @@ export const RenderIframe: React.FC = ({ rendererType, forcedDarkMode }) => { - const savedDarkMode = useIsDarkModeActivated() - const darkMode = forcedDarkMode ?? savedDarkMode - const [lightboxDetails, setLightboxDetails] = useState(undefined) - const frameReference = useRef(null) - const frontmatterInfo = useApplicationState((state) => state.noteDetails.frontmatterRendererInfo) const rendererOrigin = useApplicationState((state) => state.config.iframeCommunication.rendererOrigin) const renderPageUrl = `${rendererOrigin}render` const resetRendererReady = useCallback(() => setRendererStatus(false), []) - const iframeCommunicator = useIFrameEditorToRendererCommunicator() + const iframeCommunicator = useEditorToRendererCommunicator() + const rendererReady = useIsRendererReady() const onIframeLoad = useOnIframeLoad( frameReference, iframeCommunicator, @@ -52,8 +57,6 @@ export const RenderIframe: React.FC = ({ ) const [frameHeight, setFrameHeight] = useState(0) - const rendererReady = useApplicationState((state) => state.rendererStatus.rendererReady) - useEffect( () => () => { iframeCommunicator.unregisterEventListener() @@ -62,76 +65,59 @@ export const RenderIframe: React.FC = ({ [iframeCommunicator] ) - useEffect(() => { - iframeCommunicator.onFirstHeadingChange(onFirstHeadingChange) - return () => iframeCommunicator.onFirstHeadingChange(undefined) - }, [iframeCommunicator, onFirstHeadingChange]) + useEditorReceiveHandler( + CommunicationMessageType.ON_FIRST_HEADING_CHANGE, + useCallback( + (values: OnFirstHeadingChangeMessage) => onFirstHeadingChange?.(values.firstHeading), + [onFirstHeadingChange] + ) + ) - useEffect(() => { - iframeCommunicator.onSetScrollState(onScroll) - return () => iframeCommunicator.onSetScrollState(undefined) - }, [iframeCommunicator, onScroll]) + useEditorReceiveHandler( + CommunicationMessageType.SET_SCROLL_STATE, + useCallback((values: SetScrollStateMessage) => onScroll?.(values.scrollState), [onScroll]) + ) - useEffect(() => { - iframeCommunicator.onSetScrollSourceToRenderer(onMakeScrollSource) - return () => iframeCommunicator.onSetScrollSourceToRenderer(undefined) - }, [iframeCommunicator, onMakeScrollSource]) + useEditorReceiveHandler( + CommunicationMessageType.SET_SCROLL_SOURCE_TO_RENDERER, + useCallback(() => onMakeScrollSource?.(), [onMakeScrollSource]) + ) - useEffect(() => { - iframeCommunicator.onTaskCheckboxChange(onTaskCheckedChange) - return () => iframeCommunicator.onTaskCheckboxChange(undefined) - }, [iframeCommunicator, onTaskCheckedChange]) + useEditorReceiveHandler( + CommunicationMessageType.ON_TASK_CHECKBOX_CHANGE, + useCallback( + (values: OnTaskCheckboxChangeMessage) => onTaskCheckedChange?.(values.lineInMarkdown, values.checked), + [onTaskCheckedChange] + ) + ) - useEffect(() => { - iframeCommunicator.onImageClicked(setLightboxDetails) - return () => iframeCommunicator.onImageClicked(undefined) - }, [iframeCommunicator]) + useEditorReceiveHandler( + CommunicationMessageType.ON_HEIGHT_CHANGE, + useCallback((values: OnHeightChangeMessage) => setFrameHeight?.(values.height), [setFrameHeight]) + ) - useEffect(() => { - iframeCommunicator.onHeightChange(setFrameHeight) - return () => iframeCommunicator.onHeightChange(undefined) - }, [iframeCommunicator]) - - useEffect(() => { - iframeCommunicator.onRendererReady(() => { - iframeCommunicator.sendSetBaseConfiguration({ - baseUrl: window.location.toString(), - rendererType + useEditorReceiveHandler( + CommunicationMessageType.RENDERER_READY, + useCallback(() => { + iframeCommunicator.enableCommunication() + iframeCommunicator.sendMessageToOtherSide({ + type: CommunicationMessageType.SET_BASE_CONFIGURATION, + baseConfiguration: { + baseUrl: window.location.toString(), + rendererType + } }) setRendererStatus(true) - }) - return () => iframeCommunicator.onRendererReady(undefined) - }, [iframeCommunicator, rendererType]) + }, [iframeCommunicator, rendererType]) + ) - useEffect(() => { - if (rendererReady) { - iframeCommunicator.sendSetDarkmode(darkMode) - } - }, [darkMode, iframeCommunicator, rendererReady]) - - const oldScrollState = useRef(undefined) - useEffect(() => { - if (rendererReady && !equal(scrollState, oldScrollState.current)) { - oldScrollState.current = scrollState - iframeCommunicator.sendScrollState(scrollState) - } - }, [iframeCommunicator, rendererReady, scrollState]) - - useEffect(() => { - if (rendererReady) { - iframeCommunicator.sendSetMarkdownContent(markdownContent) - } - }, [iframeCommunicator, markdownContent, rendererReady]) - - useEffect(() => { - if (rendererReady && frontmatterInfo !== undefined) { - iframeCommunicator.sendSetFrontmatterInfo(frontmatterInfo) - } - }, [iframeCommunicator, rendererReady, frontmatterInfo]) + useSendScrollState(scrollState) + useSendDarkModeStatusToRenderer(forcedDarkMode) + useSendMarkdownToRenderer(markdownContent) return ( - +