diff --git a/src/components/editor-page/editor-page.tsx b/src/components/editor-page/editor-page.tsx index 50e784235..8755a6441 100644 --- a/src/components/editor-page/editor-page.tsx +++ b/src/components/editor-page/editor-page.tsx @@ -32,7 +32,7 @@ import { RendererType } from '../render-page/rendering-message' import { useEditorModeFromUrl } from './hooks/useEditorModeFromUrl' import { UiNotifications } from '../notifications/ui-notifications' import { useNotificationTest } from './use-notification-test' -import { IframeCommunicatorContextProvider } from './render-context/iframe-communicator-context-provider' +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' @@ -126,7 +126,7 @@ export const EditorPage: React.FC = () => { ) return ( - + @@ -148,7 +148,7 @@ export const EditorPage: React.FC = () => { - + ) } diff --git a/src/components/editor-page/editor-pane/editor-pane.tsx b/src/components/editor-page/editor-pane/editor-pane.tsx index 8f238a50b..8611c12a0 100644 --- a/src/components/editor-page/editor-pane/editor-pane.tsx +++ b/src/components/editor-page/editor-pane/editor-pane.tsx @@ -154,7 +154,7 @@ export const EditorPane: React.FC = ({ [onContentChange, maxLength, maxLengthWarningAlreadyShown] ) const onEditorDidMount = useCallback( - (mountedEditor) => { + (mountedEditor: Editor) => { setStatusBarInfo(createStatusInfo(mountedEditor, maxLength)) setEditor(mountedEditor) }, diff --git a/src/components/editor-page/render-context/iframe-communicator-context-provider.tsx b/src/components/editor-page/render-context/iframe-editor-to-renderer-communicator-context-provider.tsx similarity index 62% rename from src/components/editor-page/render-context/iframe-communicator-context-provider.tsx rename to src/components/editor-page/render-context/iframe-editor-to-renderer-communicator-context-provider.tsx index 209d8f9f4..a379a8d0e 100644 --- a/src/components/editor-page/render-context/iframe-communicator-context-provider.tsx +++ b/src/components/editor-page/render-context/iframe-editor-to-renderer-communicator-context-provider.tsx @@ -10,18 +10,10 @@ import { IframeEditorToRendererCommunicator } from '../../render-page/iframe-edi const IFrameEditorToRendererCommunicatorContext = React.createContext(undefined) -export const useIFrameCommunicator: () => IframeEditorToRendererCommunicator | undefined = () => +export const useIFrameEditorToRendererCommunicator: () => IframeEditorToRendererCommunicator | undefined = () => useContext(IFrameEditorToRendererCommunicatorContext) -export const useContextOrStandaloneIframeCommunicator: () => IframeEditorToRendererCommunicator = () => { - const contextCommunicator = useIFrameCommunicator() - return useMemo( - () => (contextCommunicator ? contextCommunicator : new IframeEditorToRendererCommunicator()), - [contextCommunicator] - ) -} - -export const IframeCommunicatorContextProvider: React.FC = ({ children }) => { +export const IframeEditorToRendererCommunicatorContextProvider: React.FC = ({ children }) => { const currentIFrameCommunicator = useMemo( () => new IframeEditorToRendererCommunicator(), [] 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 new file mode 100644 index 000000000..12528219c --- /dev/null +++ b/src/components/editor-page/render-context/iframe-renderer-to-editor-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, 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) + +export const useIFrameRendererToEditorCommunicator: () => IframeRendererToEditorCommunicator | undefined = () => + useContext(IFrameRendererToEditorCommunicatorContext) + +export const IframeRendererToEditorCommunicatorContextProvider: React.FC = ({ children }) => { + const editorOrigin = useSelector((state: ApplicationState) => state.config.iframeCommunication.editorOrigin) + const currentIFrameCommunicator = useMemo(() => { + const newCommunicator = new IframeRendererToEditorCommunicator() + newCommunicator.setOtherSide(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/renderer-pane/hooks/use-on-iframe-load.ts b/src/components/editor-page/renderer-pane/hooks/use-on-iframe-load.ts index 2af724262..0db6cc404 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 @@ -9,7 +9,7 @@ import { IframeEditorToRendererCommunicator } from '../../../render-page/iframe- export const useOnIframeLoad = ( frameReference: RefObject, - iframeCommunicator: IframeEditorToRendererCommunicator, + iframeCommunicator: IframeEditorToRendererCommunicator | undefined, rendererOrigin: string, renderPageUrl: string, onNavigateAway: () => void @@ -19,12 +19,12 @@ export const useOnIframeLoad = ( return useCallback(() => { const frame = frameReference.current if (!frame || !frame.contentWindow) { - iframeCommunicator.unsetOtherSide() + iframeCommunicator?.unsetOtherSide() return } if (sendToRenderPage.current) { - iframeCommunicator.setOtherSide(frame.contentWindow, rendererOrigin) + iframeCommunicator?.setOtherSide(frame.contentWindow, rendererOrigin) sendToRenderPage.current = false return } else { diff --git a/src/components/editor-page/renderer-pane/render-iframe.tsx b/src/components/editor-page/renderer-pane/render-iframe.tsx index ab7a56b9b..079339a08 100644 --- a/src/components/editor-page/renderer-pane/render-iframe.tsx +++ b/src/components/editor-page/renderer-pane/render-iframe.tsx @@ -10,7 +10,7 @@ import { useIsDarkModeActivated } from '../../../hooks/common/use-is-dark-mode-a import { isTestMode } from '../../../utils/test-modes' import { RendererProps } from '../../render-page/markdown-document' import { ImageDetails, RendererType } from '../../render-page/rendering-message' -import { useContextOrStandaloneIframeCommunicator } from '../render-context/iframe-communicator-context-provider' +import { useIFrameEditorToRendererCommunicator } from '../render-context/iframe-editor-to-renderer-communicator-context-provider' import { ScrollState } from '../synced-scroll/scroll-props' import { useOnIframeLoad } from './hooks/use-on-iframe-load' import { ShowOnPropChangeImageLightbox } from './show-on-prop-change-image-lightbox' @@ -44,7 +44,7 @@ export const RenderIframe: React.FC = ({ const rendererOrigin = useApplicationState((state) => state.config.iframeCommunication.rendererOrigin) const renderPageUrl = `${rendererOrigin}render` const resetRendererReady = useCallback(() => setRendererReady(false), []) - const iframeCommunicator = useContextOrStandaloneIframeCommunicator() + const iframeCommunicator = useIFrameEditorToRendererCommunicator() const onIframeLoad = useOnIframeLoad( frameReference, iframeCommunicator, @@ -58,41 +58,39 @@ export const RenderIframe: React.FC = ({ onRendererReadyChange?.(rendererReady) }, [onRendererReadyChange, rendererReady]) - useEffect(() => () => iframeCommunicator.unregisterEventListener(), [iframeCommunicator]) + useEffect(() => () => iframeCommunicator?.unregisterEventListener(), [iframeCommunicator]) useEffect( - () => iframeCommunicator.onFirstHeadingChange(onFirstHeadingChange), + () => iframeCommunicator?.onFirstHeadingChange(onFirstHeadingChange), [iframeCommunicator, onFirstHeadingChange] ) useEffect( - () => iframeCommunicator.onFrontmatterChange(onFrontmatterChange), + () => iframeCommunicator?.onFrontmatterChange(onFrontmatterChange), [iframeCommunicator, onFrontmatterChange] ) - useEffect(() => iframeCommunicator.onSetScrollState(onScroll), [iframeCommunicator, onScroll]) + useEffect(() => iframeCommunicator?.onSetScrollState(onScroll), [iframeCommunicator, onScroll]) useEffect( - () => iframeCommunicator.onSetScrollSourceToRenderer(onMakeScrollSource), + () => iframeCommunicator?.onSetScrollSourceToRenderer(onMakeScrollSource), [iframeCommunicator, onMakeScrollSource] ) useEffect( - () => iframeCommunicator.onTaskCheckboxChange(onTaskCheckedChange), + () => iframeCommunicator?.onTaskCheckboxChange(onTaskCheckedChange), [iframeCommunicator, onTaskCheckedChange] ) - useEffect(() => iframeCommunicator.onImageClicked(setLightboxDetails), [iframeCommunicator]) - useEffect( - () => - iframeCommunicator.onRendererReady(() => { - iframeCommunicator.sendSetBaseConfiguration({ - baseUrl: window.location.toString(), - rendererType - }) - setRendererReady(true) - }), - [darkMode, rendererType, iframeCommunicator, rendererReady, scrollState] - ) - useEffect(() => iframeCommunicator.onHeightChange(setFrameHeight), [iframeCommunicator]) + useEffect(() => iframeCommunicator?.onImageClicked(setLightboxDetails), [iframeCommunicator]) + useEffect(() => { + iframeCommunicator?.onRendererReady(() => { + iframeCommunicator?.sendSetBaseConfiguration({ + baseUrl: window.location.toString(), + rendererType + }) + setRendererReady(true) + }) + }, [darkMode, rendererType, iframeCommunicator, rendererReady, scrollState]) + useEffect(() => iframeCommunicator?.onHeightChange(setFrameHeight), [iframeCommunicator]) useEffect(() => { if (rendererReady) { - iframeCommunicator.sendSetDarkmode(darkMode) + iframeCommunicator?.sendSetDarkmode(darkMode) } }, [darkMode, iframeCommunicator, rendererReady]) @@ -100,13 +98,13 @@ export const RenderIframe: React.FC = ({ useEffect(() => { if (rendererReady && !equal(scrollState, oldScrollState.current)) { oldScrollState.current = scrollState - iframeCommunicator.sendScrollState(scrollState) + iframeCommunicator?.sendScrollState(scrollState) } }, [iframeCommunicator, rendererReady, scrollState]) useEffect(() => { if (rendererReady) { - iframeCommunicator.sendSetMarkdownContent(markdownContent) + iframeCommunicator?.sendSetMarkdownContent(markdownContent) } }, [iframeCommunicator, markdownContent, rendererReady]) diff --git a/src/components/intro-page/intro-page.tsx b/src/components/intro-page/intro-page.tsx index 874722bb2..b4f92dc0f 100644 --- a/src/components/intro-page/intro-page.tsx +++ b/src/components/intro-page/intro-page.tsx @@ -4,7 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import React, { Fragment, useState } from 'react' +import React, { useState } from 'react' import { Trans } from 'react-i18next' import { Branding } from '../common/branding/branding' import { @@ -19,13 +19,14 @@ import { useIntroPageContent } from './hooks/use-intro-page-content' import { ShowIf } from '../common/show-if/show-if' import { RendererType } from '../render-page/rendering-message' import { WaitSpinner } from '../common/wait-spinner/wait-spinner' +import { IframeEditorToRendererCommunicatorContextProvider } from '../editor-page/render-context/iframe-editor-to-renderer-communicator-context-provider' export const IntroPage: React.FC = () => { const introPageContent = useIntroPageContent() const [rendererReady, setRendererReady] = useState(true) return ( - + @@ -52,6 +53,6 @@ export const IntroPage: React.FC = () => { - + ) } diff --git a/src/components/render-page/hooks/use-image-click-handler.ts b/src/components/render-page/hooks/use-image-click-handler.ts index fdc072dac..7cbb0c243 100644 --- a/src/components/render-page/hooks/use-image-click-handler.ts +++ b/src/components/render-page/hooks/use-image-click-handler.ts @@ -8,14 +8,16 @@ import React, { useCallback } from 'react' import { ImageClickHandler } from '../../markdown-renderer/replace-components/image/image-replacer' import { IframeRendererToEditorCommunicator } from '../iframe-renderer-to-editor-communicator' -export const useImageClickHandler = (iframeCommunicator: IframeRendererToEditorCommunicator): ImageClickHandler => { +export const useImageClickHandler = ( + iframeCommunicator: IframeRendererToEditorCommunicator | undefined +): ImageClickHandler => { return useCallback( (event: React.MouseEvent) => { const image = event.target as HTMLImageElement if (image.src === '') { return } - iframeCommunicator.sendClickedImageUrl({ + iframeCommunicator?.sendClickedImageUrl({ src: image.src, alt: image.alt, title: image.title diff --git a/src/components/render-page/iframe-markdown-renderer.tsx b/src/components/render-page/iframe-markdown-renderer.tsx new file mode 100644 index 000000000..bab72e891 --- /dev/null +++ b/src/components/render-page/iframe-markdown-renderer.tsx @@ -0,0 +1,106 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import React, { useCallback, useEffect, useState } from 'react' +import { ScrollState } from '../editor-page/synced-scroll/scroll-props' +import { BaseConfiguration, RendererType } from './rendering-message' +import { setDarkMode } from '../../redux/dark-mode/methods' +import { NoteFrontmatter } from '../editor-page/note-frontmatter/note-frontmatter' +import { setNoteFrontmatter } from '../../redux/note-details/methods' +import { ImageClickHandler } from '../markdown-renderer/replace-components/image/image-replacer' +import { useImageClickHandler } from './hooks/use-image-click-handler' +import { MarkdownDocument } from './markdown-document' +import { useIFrameRendererToEditorCommunicator } from '../editor-page/render-context/iframe-renderer-to-editor-communicator-context-provider' + +export const IframeMarkdownRenderer: React.FC = () => { + const [markdownContent, setMarkdownContent] = useState('') + const [scrollState, setScrollState] = useState({ firstLineInView: 1, scrolledPercentage: 0 }) + const [baseConfiguration, setBaseConfiguration] = useState(undefined) + + const iframeCommunicator = useIFrameRendererToEditorCommunicator() + + useEffect(() => iframeCommunicator?.onSetBaseConfiguration(setBaseConfiguration), [iframeCommunicator]) + useEffect(() => iframeCommunicator?.onSetMarkdownContent(setMarkdownContent), [iframeCommunicator]) + useEffect(() => iframeCommunicator?.onSetDarkMode(setDarkMode), [iframeCommunicator]) + useEffect(() => iframeCommunicator?.onSetScrollState(setScrollState), [iframeCommunicator, scrollState]) + + const onTaskCheckedChange = useCallback( + (lineInMarkdown: number, checked: boolean) => { + iframeCommunicator?.sendTaskCheckBoxChange(lineInMarkdown, checked) + }, + [iframeCommunicator] + ) + + const onFirstHeadingChange = useCallback( + (firstHeading?: string) => { + iframeCommunicator?.sendFirstHeadingChanged(firstHeading) + }, + [iframeCommunicator] + ) + + const onMakeScrollSource = useCallback(() => { + iframeCommunicator?.sendSetScrollSourceToRenderer() + }, [iframeCommunicator]) + + const onFrontmatterChange = useCallback( + (frontmatter?: NoteFrontmatter) => { + setNoteFrontmatter(frontmatter) + iframeCommunicator?.sendSetFrontmatter(frontmatter) + }, + [iframeCommunicator] + ) + + const onScroll = useCallback( + (scrollState: ScrollState) => { + iframeCommunicator?.sendSetScrollState(scrollState) + }, + [iframeCommunicator] + ) + + const onImageClick: ImageClickHandler = useImageClickHandler(iframeCommunicator) + + const onHeightChange = useCallback( + (height: number) => { + iframeCommunicator?.sendHeightChange(height) + }, + [iframeCommunicator] + ) + + if (!baseConfiguration) { + return null + } + + switch (baseConfiguration.rendererType) { + case RendererType.DOCUMENT: + return ( + + ) + case RendererType.INTRO: + return ( + + ) + default: + return null + } +} diff --git a/src/components/render-page/render-page.tsx b/src/components/render-page/render-page.tsx index 1147d291c..8d7fd8c3c 100644 --- a/src/components/render-page/render-page.tsx +++ b/src/components/render-page/render-page.tsx @@ -3,120 +3,19 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import React, { useCallback, useEffect, useMemo, useState } from 'react' -import { useApplicationState } from '../../hooks/common/use-application-state' +import React from 'react' import { useApplyDarkMode } from '../../hooks/common/use-apply-dark-mode' -import { setDarkMode } from '../../redux/dark-mode/methods' -import { setNoteFrontmatter } from '../../redux/note-details/methods' -import { NoteFrontmatter } from '../editor-page/note-frontmatter/note-frontmatter' -import { ScrollState } from '../editor-page/synced-scroll/scroll-props' -import { ImageClickHandler } from '../markdown-renderer/replace-components/image/image-replacer' -import { useImageClickHandler } from './hooks/use-image-click-handler' -import { IframeRendererToEditorCommunicator } from './iframe-renderer-to-editor-communicator' -import { MarkdownDocument } from './markdown-document' -import { BaseConfiguration, RendererType } from './rendering-message' +import { IframeMarkdownRenderer } from './iframe-markdown-renderer' +import { IframeRendererToEditorCommunicatorContextProvider } from '../editor-page/render-context/iframe-renderer-to-editor-communicator-context-provider' export const RenderPage: React.FC = () => { useApplyDarkMode() - const [markdownContent, setMarkdownContent] = useState('') - const [scrollState, setScrollState] = useState({ firstLineInView: 1, scrolledPercentage: 0 }) - const [baseConfiguration, setBaseConfiguration] = useState(undefined) - - const editorOrigin = useApplicationState((state) => state.config.iframeCommunication.editorOrigin) - - const iframeCommunicator = useMemo(() => { - const newCommunicator = new IframeRendererToEditorCommunicator() - newCommunicator.setOtherSide(window.parent, editorOrigin) - return newCommunicator - }, [editorOrigin]) - - useEffect(() => { - iframeCommunicator.sendRendererReady() - return () => iframeCommunicator.unregisterEventListener() - }, [iframeCommunicator]) - - useEffect(() => iframeCommunicator.onSetBaseConfiguration(setBaseConfiguration), [iframeCommunicator]) - useEffect(() => iframeCommunicator.onSetMarkdownContent(setMarkdownContent), [iframeCommunicator]) - useEffect(() => iframeCommunicator.onSetDarkMode(setDarkMode), [iframeCommunicator]) - useEffect(() => iframeCommunicator.onSetScrollState(setScrollState), [iframeCommunicator, scrollState]) - - const onTaskCheckedChange = useCallback( - (lineInMarkdown: number, checked: boolean) => { - iframeCommunicator.sendTaskCheckBoxChange(lineInMarkdown, checked) - }, - [iframeCommunicator] + return ( + + + ) - - const onFirstHeadingChange = useCallback( - (firstHeading?: string) => { - iframeCommunicator.sendFirstHeadingChanged(firstHeading) - }, - [iframeCommunicator] - ) - - const onMakeScrollSource = useCallback(() => { - iframeCommunicator.sendSetScrollSourceToRenderer() - }, [iframeCommunicator]) - - const onFrontmatterChange = useCallback( - (frontmatter?: NoteFrontmatter) => { - setNoteFrontmatter(frontmatter) - iframeCommunicator.sendSetFrontmatter(frontmatter) - }, - [iframeCommunicator] - ) - - const onScroll = useCallback( - (scrollState: ScrollState) => { - iframeCommunicator.sendSetScrollState(scrollState) - }, - [iframeCommunicator] - ) - - const onImageClick: ImageClickHandler = useImageClickHandler(iframeCommunicator) - - const onHeightChange = useCallback( - (height: number) => { - iframeCommunicator.sendHeightChange(height) - }, - [iframeCommunicator] - ) - - if (!baseConfiguration) { - return null - } - - switch (baseConfiguration.rendererType) { - case RendererType.DOCUMENT: - return ( - - ) - case RendererType.INTRO: - return ( - - ) - default: - return null - } } export default RenderPage