mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-27 03:58:02 -05:00
Restructure Communicator (#1510)
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
e6830598d5
commit
f1e91b4574
31 changed files with 680 additions and 569 deletions
|
@ -19,10 +19,11 @@ import { RenderIframe } from '../editor-page/renderer-pane/render-iframe'
|
||||||
import { DocumentInfobar } from './document-infobar'
|
import { DocumentInfobar } from './document-infobar'
|
||||||
import { ErrorWhileLoadingNoteAlert } from './ErrorWhileLoadingNoteAlert'
|
import { ErrorWhileLoadingNoteAlert } from './ErrorWhileLoadingNoteAlert'
|
||||||
import { LoadingNoteAlert } from './LoadingNoteAlert'
|
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 { 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 { 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 = () => {
|
export const DocumentReadOnlyPage: React.FC = () => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
|
@ -35,9 +36,10 @@ export const DocumentReadOnlyPage: React.FC = () => {
|
||||||
const [error, loading] = useLoadNoteFromServer()
|
const [error, loading] = useLoadNoteFromServer()
|
||||||
const markdownContent = useNoteMarkdownContentWithoutFrontmatter()
|
const markdownContent = useNoteMarkdownContentWithoutFrontmatter()
|
||||||
const noteDetails = useApplicationState((state) => state.noteDetails)
|
const noteDetails = useApplicationState((state) => state.noteDetails)
|
||||||
|
useSendFrontmatterInfoFromReduxToRenderer()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IframeEditorToRendererCommunicatorContextProvider>
|
<EditorToRendererCommunicatorContextProvider>
|
||||||
<div className={'d-flex flex-column mvh-100 bg-light'}>
|
<div className={'d-flex flex-column mvh-100 bg-light'}>
|
||||||
<MotdBanner />
|
<MotdBanner />
|
||||||
<AppBar mode={AppBarMode.BASIC} />
|
<AppBar mode={AppBarMode.BASIC} />
|
||||||
|
@ -63,7 +65,7 @@ export const DocumentReadOnlyPage: React.FC = () => {
|
||||||
/>
|
/>
|
||||||
</ShowIf>
|
</ShowIf>
|
||||||
</div>
|
</div>
|
||||||
</IframeEditorToRendererCommunicatorContextProvider>
|
</EditorToRendererCommunicatorContextProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,13 +4,18 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* 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 { Trans, useTranslation } from 'react-i18next'
|
||||||
import { ShowIf } from '../../../common/show-if/show-if'
|
import { ShowIf } from '../../../common/show-if/show-if'
|
||||||
import { DocumentInfoLine } from './document-info-line'
|
import { DocumentInfoLine } from './document-info-line'
|
||||||
import { UnitalicBoldText } from './unitalic-bold-text'
|
import { UnitalicBoldText } from './unitalic-bold-text'
|
||||||
import { useIFrameEditorToRendererCommunicator } from '../../render-context/iframe-editor-to-renderer-communicator-context-provider'
|
import { useEditorToRendererCommunicator } from '../../render-context/editor-to-renderer-communicator-context-provider'
|
||||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
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
|
* 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 = () => {
|
export const DocumentInfoLineWordCount: React.FC = () => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
const iframeEditorToRendererCommunicator = useIFrameEditorToRendererCommunicator()
|
const editorToRendererCommunicator = useEditorToRendererCommunicator()
|
||||||
const [wordCount, setWordCount] = useState<number | null>(null)
|
const [wordCount, setWordCount] = useState<number | null>(null)
|
||||||
const rendererReady = useApplicationState((state) => state.rendererStatus.rendererReady)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEditorReceiveHandler(
|
||||||
iframeEditorToRendererCommunicator.onWordCountCalculated((words) => {
|
CommunicationMessageType.ON_WORD_COUNT_CALCULATED,
|
||||||
setWordCount(words)
|
useCallback((values: OnWordCountCalculatedMessage) => setWordCount(values.words), [setWordCount])
|
||||||
})
|
)
|
||||||
return () => {
|
|
||||||
iframeEditorToRendererCommunicator.onWordCountCalculated(undefined)
|
|
||||||
}
|
|
||||||
}, [iframeEditorToRendererCommunicator, setWordCount])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffectOnRendererReady(
|
||||||
if (rendererReady) {
|
useCallback(() => {
|
||||||
iframeEditorToRendererCommunicator.sendGetWordCount()
|
editorToRendererCommunicator.sendMessageToOtherSide({ type: CommunicationMessageType.GET_WORD_COUNT })
|
||||||
}
|
}, [editorToRendererCommunicator])
|
||||||
}, [iframeEditorToRendererCommunicator, rendererReady])
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DocumentInfoLine icon={'align-left'} size={'2x'}>
|
<DocumentInfoLine icon={'align-left'} size={'2x'}>
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { RenderIframe, RenderIframeProps } from '../renderer-pane/render-iframe'
|
import { RenderIframe, RenderIframeProps } from '../renderer-pane/render-iframe'
|
||||||
import { useNoteMarkdownContentWithoutFrontmatter } from '../../../hooks/common/use-note-markdown-content-without-frontmatter'
|
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<RenderIframeProps, 'markdownContent'>
|
export type EditorDocumentRendererProps = Omit<RenderIframeProps, 'markdownContent'>
|
||||||
|
|
||||||
|
@ -17,5 +18,8 @@ export type EditorDocumentRendererProps = Omit<RenderIframeProps, 'markdownConte
|
||||||
*/
|
*/
|
||||||
export const EditorDocumentRenderer: React.FC<EditorDocumentRendererProps> = (props) => {
|
export const EditorDocumentRenderer: React.FC<EditorDocumentRendererProps> = (props) => {
|
||||||
const markdownContent = useNoteMarkdownContentWithoutFrontmatter()
|
const markdownContent = useNoteMarkdownContentWithoutFrontmatter()
|
||||||
|
|
||||||
|
useSendFrontmatterInfoFromReduxToRenderer()
|
||||||
|
|
||||||
return <RenderIframe frameClasses={'h-100 w-100'} markdownContent={markdownContent} {...props} />
|
return <RenderIframe frameClasses={'h-100 w-100'} markdownContent={markdownContent} {...props} />
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,14 +21,14 @@ import { useViewModeShortcuts } from './hooks/useViewModeShortcuts'
|
||||||
import { Sidebar } from './sidebar/sidebar'
|
import { Sidebar } from './sidebar/sidebar'
|
||||||
import { Splitter } from './splitter/splitter'
|
import { Splitter } from './splitter/splitter'
|
||||||
import { DualScrollState, ScrollState } from './synced-scroll/scroll-props'
|
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 { useEditorModeFromUrl } from './hooks/useEditorModeFromUrl'
|
||||||
import { UiNotifications } from '../notifications/ui-notifications'
|
import { UiNotifications } from '../notifications/ui-notifications'
|
||||||
import { useNotificationTest } from './use-notification-test'
|
import { useNotificationTest } from './use-notification-test'
|
||||||
import { IframeEditorToRendererCommunicatorContextProvider } from './render-context/iframe-editor-to-renderer-communicator-context-provider'
|
|
||||||
import { useUpdateLocalHistoryEntry } from './hooks/useUpdateLocalHistoryEntry'
|
import { useUpdateLocalHistoryEntry } from './hooks/useUpdateLocalHistoryEntry'
|
||||||
import { useApplicationState } from '../../hooks/common/use-application-state'
|
import { useApplicationState } from '../../hooks/common/use-application-state'
|
||||||
import { EditorDocumentRenderer } from './editor-document-renderer/editor-document-renderer'
|
import { EditorDocumentRenderer } from './editor-document-renderer/editor-document-renderer'
|
||||||
|
import { EditorToRendererCommunicatorContextProvider } from './render-context/editor-to-renderer-communicator-context-provider'
|
||||||
|
|
||||||
export interface EditorPagePathParams {
|
export interface EditorPagePathParams {
|
||||||
id: string
|
id: string
|
||||||
|
@ -53,7 +53,11 @@ export const EditorPage: React.FC = () => {
|
||||||
const onMarkdownRendererScroll = useCallback(
|
const onMarkdownRendererScroll = useCallback(
|
||||||
(newScrollState: ScrollState) => {
|
(newScrollState: ScrollState) => {
|
||||||
if (scrollSource.current === ScrollSource.RENDERER && editorSyncScroll) {
|
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]
|
[editorSyncScroll]
|
||||||
|
@ -62,7 +66,11 @@ export const EditorPage: React.FC = () => {
|
||||||
const onEditorScroll = useCallback(
|
const onEditorScroll = useCallback(
|
||||||
(newScrollState: ScrollState) => {
|
(newScrollState: ScrollState) => {
|
||||||
if (scrollSource.current === ScrollSource.EDITOR && editorSyncScroll) {
|
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]
|
[editorSyncScroll]
|
||||||
|
@ -79,10 +87,12 @@ export const EditorPage: React.FC = () => {
|
||||||
|
|
||||||
const setRendererToScrollSource = useCallback(() => {
|
const setRendererToScrollSource = useCallback(() => {
|
||||||
scrollSource.current = ScrollSource.RENDERER
|
scrollSource.current = ScrollSource.RENDERER
|
||||||
|
console.debug('[EditorPage] Make renderer scroll source')
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const setEditorToScrollSource = useCallback(() => {
|
const setEditorToScrollSource = useCallback(() => {
|
||||||
scrollSource.current = ScrollSource.EDITOR
|
scrollSource.current = ScrollSource.EDITOR
|
||||||
|
console.debug('[EditorPage] Make editor scroll source')
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useNotificationTest()
|
useNotificationTest()
|
||||||
|
@ -114,7 +124,7 @@ export const EditorPage: React.FC = () => {
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IframeEditorToRendererCommunicatorContextProvider>
|
<EditorToRendererCommunicatorContextProvider>
|
||||||
<UiNotifications />
|
<UiNotifications />
|
||||||
<MotdBanner />
|
<MotdBanner />
|
||||||
<div className={'d-flex flex-column vh-100'}>
|
<div className={'d-flex flex-column vh-100'}>
|
||||||
|
@ -136,7 +146,7 @@ export const EditorPage: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
</ShowIf>
|
</ShowIf>
|
||||||
</div>
|
</div>
|
||||||
</IframeEditorToRendererCommunicatorContextProvider>
|
</EditorToRendererCommunicatorContextProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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<EditorToRendererCommunicator | undefined>(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<EditorToRendererCommunicator>(() => new EditorToRendererCommunicator(), [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EditorToRendererCommunicatorContext.Provider value={communicator}>
|
||||||
|
{children}
|
||||||
|
</EditorToRendererCommunicatorContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
|
@ -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<IframeEditorToRendererCommunicator | undefined>(
|
|
||||||
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<IframeEditorToRendererCommunicator>(
|
|
||||||
() => new IframeEditorToRendererCommunicator(),
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<IFrameEditorToRendererCommunicatorContext.Provider value={currentIFrameCommunicator}>
|
|
||||||
{children}
|
|
||||||
</IFrameEditorToRendererCommunicatorContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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<IframeRendererToEditorCommunicator | undefined>(
|
|
||||||
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<IframeRendererToEditorCommunicator>(() => {
|
|
||||||
const newCommunicator = new IframeRendererToEditorCommunicator()
|
|
||||||
newCommunicator.setMessageTarget(window.parent, editorOrigin)
|
|
||||||
return newCommunicator
|
|
||||||
}, [editorOrigin])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const currentIFrame = currentIFrameCommunicator
|
|
||||||
currentIFrame?.sendRendererReady()
|
|
||||||
return () => currentIFrame?.unregisterEventListener()
|
|
||||||
}, [currentIFrameCommunicator])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<IFrameRendererToEditorCommunicatorContext.Provider value={currentIFrameCommunicator}>
|
|
||||||
{children}
|
|
||||||
</IFrameRendererToEditorCommunicatorContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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<RendererToEditorCommunicator | undefined>(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<RendererToEditorCommunicator>(() => {
|
||||||
|
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 (
|
||||||
|
<RendererToEditorCommunicatorContext.Provider value={communicator}>
|
||||||
|
{children}
|
||||||
|
</RendererToEditorCommunicatorContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
|
@ -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<ImageDetails | undefined>(undefined)
|
||||||
|
const [show, setShow] = useState<boolean>(false)
|
||||||
|
|
||||||
|
useEditorReceiveHandler(
|
||||||
|
CommunicationMessageType.IMAGE_CLICKED,
|
||||||
|
useCallback(
|
||||||
|
(values: ImageClickedMessage) => {
|
||||||
|
setLightboxDetails?.(values.details)
|
||||||
|
setShow(true)
|
||||||
|
},
|
||||||
|
[setLightboxDetails]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
const hideLightbox = useCallback(() => {
|
||||||
|
setShow(false)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ImageLightboxModal
|
||||||
|
show={show}
|
||||||
|
onHide={hideLightbox}
|
||||||
|
src={lightboxDetails?.src}
|
||||||
|
alt={lightboxDetails?.alt}
|
||||||
|
title={lightboxDetails?.title}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
|
@ -5,11 +5,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { RefObject, useCallback, useRef } from 'react'
|
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 = (
|
export const useOnIframeLoad = (
|
||||||
frameReference: RefObject<HTMLIFrameElement>,
|
frameReference: RefObject<HTMLIFrameElement>,
|
||||||
iframeCommunicator: IframeEditorToRendererCommunicator,
|
iframeCommunicator: EditorToRendererCommunicator,
|
||||||
rendererOrigin: string,
|
rendererOrigin: string,
|
||||||
renderPageUrl: string,
|
renderPageUrl: string,
|
||||||
onNavigateAway: () => void
|
onNavigateAway: () => void
|
||||||
|
|
|
@ -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]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
|
@ -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]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
|
@ -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]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
|
@ -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<ScrollState | undefined>(undefined)
|
||||||
|
|
||||||
|
useEffectOnRendererReady(
|
||||||
|
useCallback(() => {
|
||||||
|
if (scrollState && !equal(scrollState, oldScrollState.current)) {
|
||||||
|
oldScrollState.current = scrollState
|
||||||
|
iframeCommunicator.sendMessageToOtherSide({ type: CommunicationMessageType.SET_SCROLL_STATE, scrollState })
|
||||||
|
}
|
||||||
|
}, [iframeCommunicator, scrollState])
|
||||||
|
)
|
||||||
|
}
|
|
@ -3,18 +3,27 @@
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import equal from 'fast-deep-equal'
|
|
||||||
import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react'
|
import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
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 { isTestMode } from '../../../utils/test-modes'
|
||||||
import { RendererProps } from '../../render-page/markdown-document'
|
import { RendererProps } from '../../render-page/markdown-document'
|
||||||
import { ImageDetails, RendererType } from '../../render-page/rendering-message'
|
import {
|
||||||
import { useIFrameEditorToRendererCommunicator } from '../render-context/iframe-editor-to-renderer-communicator-context-provider'
|
CommunicationMessageType,
|
||||||
import { ScrollState } from '../synced-scroll/scroll-props'
|
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 { 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 { 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 {
|
export interface RenderIframeProps extends RendererProps {
|
||||||
rendererType: RendererType
|
rendererType: RendererType
|
||||||
|
@ -33,16 +42,12 @@ export const RenderIframe: React.FC<RenderIframeProps> = ({
|
||||||
rendererType,
|
rendererType,
|
||||||
forcedDarkMode
|
forcedDarkMode
|
||||||
}) => {
|
}) => {
|
||||||
const savedDarkMode = useIsDarkModeActivated()
|
|
||||||
const darkMode = forcedDarkMode ?? savedDarkMode
|
|
||||||
const [lightboxDetails, setLightboxDetails] = useState<ImageDetails | undefined>(undefined)
|
|
||||||
|
|
||||||
const frameReference = useRef<HTMLIFrameElement>(null)
|
const frameReference = useRef<HTMLIFrameElement>(null)
|
||||||
const frontmatterInfo = useApplicationState((state) => state.noteDetails.frontmatterRendererInfo)
|
|
||||||
const rendererOrigin = useApplicationState((state) => state.config.iframeCommunication.rendererOrigin)
|
const rendererOrigin = useApplicationState((state) => state.config.iframeCommunication.rendererOrigin)
|
||||||
const renderPageUrl = `${rendererOrigin}render`
|
const renderPageUrl = `${rendererOrigin}render`
|
||||||
const resetRendererReady = useCallback(() => setRendererStatus(false), [])
|
const resetRendererReady = useCallback(() => setRendererStatus(false), [])
|
||||||
const iframeCommunicator = useIFrameEditorToRendererCommunicator()
|
const iframeCommunicator = useEditorToRendererCommunicator()
|
||||||
|
const rendererReady = useIsRendererReady()
|
||||||
const onIframeLoad = useOnIframeLoad(
|
const onIframeLoad = useOnIframeLoad(
|
||||||
frameReference,
|
frameReference,
|
||||||
iframeCommunicator,
|
iframeCommunicator,
|
||||||
|
@ -52,8 +57,6 @@ export const RenderIframe: React.FC<RenderIframeProps> = ({
|
||||||
)
|
)
|
||||||
const [frameHeight, setFrameHeight] = useState<number>(0)
|
const [frameHeight, setFrameHeight] = useState<number>(0)
|
||||||
|
|
||||||
const rendererReady = useApplicationState((state) => state.rendererStatus.rendererReady)
|
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() => () => {
|
() => () => {
|
||||||
iframeCommunicator.unregisterEventListener()
|
iframeCommunicator.unregisterEventListener()
|
||||||
|
@ -62,76 +65,59 @@ export const RenderIframe: React.FC<RenderIframeProps> = ({
|
||||||
[iframeCommunicator]
|
[iframeCommunicator]
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEditorReceiveHandler(
|
||||||
iframeCommunicator.onFirstHeadingChange(onFirstHeadingChange)
|
CommunicationMessageType.ON_FIRST_HEADING_CHANGE,
|
||||||
return () => iframeCommunicator.onFirstHeadingChange(undefined)
|
useCallback(
|
||||||
}, [iframeCommunicator, onFirstHeadingChange])
|
(values: OnFirstHeadingChangeMessage) => onFirstHeadingChange?.(values.firstHeading),
|
||||||
|
[onFirstHeadingChange]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEditorReceiveHandler(
|
||||||
iframeCommunicator.onSetScrollState(onScroll)
|
CommunicationMessageType.SET_SCROLL_STATE,
|
||||||
return () => iframeCommunicator.onSetScrollState(undefined)
|
useCallback((values: SetScrollStateMessage) => onScroll?.(values.scrollState), [onScroll])
|
||||||
}, [iframeCommunicator, onScroll])
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEditorReceiveHandler(
|
||||||
iframeCommunicator.onSetScrollSourceToRenderer(onMakeScrollSource)
|
CommunicationMessageType.SET_SCROLL_SOURCE_TO_RENDERER,
|
||||||
return () => iframeCommunicator.onSetScrollSourceToRenderer(undefined)
|
useCallback(() => onMakeScrollSource?.(), [onMakeScrollSource])
|
||||||
}, [iframeCommunicator, onMakeScrollSource])
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEditorReceiveHandler(
|
||||||
iframeCommunicator.onTaskCheckboxChange(onTaskCheckedChange)
|
CommunicationMessageType.ON_TASK_CHECKBOX_CHANGE,
|
||||||
return () => iframeCommunicator.onTaskCheckboxChange(undefined)
|
useCallback(
|
||||||
}, [iframeCommunicator, onTaskCheckedChange])
|
(values: OnTaskCheckboxChangeMessage) => onTaskCheckedChange?.(values.lineInMarkdown, values.checked),
|
||||||
|
[onTaskCheckedChange]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEditorReceiveHandler(
|
||||||
iframeCommunicator.onImageClicked(setLightboxDetails)
|
CommunicationMessageType.ON_HEIGHT_CHANGE,
|
||||||
return () => iframeCommunicator.onImageClicked(undefined)
|
useCallback((values: OnHeightChangeMessage) => setFrameHeight?.(values.height), [setFrameHeight])
|
||||||
}, [iframeCommunicator])
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEditorReceiveHandler(
|
||||||
iframeCommunicator.onHeightChange(setFrameHeight)
|
CommunicationMessageType.RENDERER_READY,
|
||||||
return () => iframeCommunicator.onHeightChange(undefined)
|
useCallback(() => {
|
||||||
}, [iframeCommunicator])
|
iframeCommunicator.enableCommunication()
|
||||||
|
iframeCommunicator.sendMessageToOtherSide({
|
||||||
useEffect(() => {
|
type: CommunicationMessageType.SET_BASE_CONFIGURATION,
|
||||||
iframeCommunicator.onRendererReady(() => {
|
baseConfiguration: {
|
||||||
iframeCommunicator.sendSetBaseConfiguration({
|
|
||||||
baseUrl: window.location.toString(),
|
baseUrl: window.location.toString(),
|
||||||
rendererType
|
rendererType
|
||||||
|
}
|
||||||
})
|
})
|
||||||
setRendererStatus(true)
|
setRendererStatus(true)
|
||||||
})
|
|
||||||
return () => iframeCommunicator.onRendererReady(undefined)
|
|
||||||
}, [iframeCommunicator, rendererType])
|
}, [iframeCommunicator, rendererType])
|
||||||
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useSendScrollState(scrollState)
|
||||||
if (rendererReady) {
|
useSendDarkModeStatusToRenderer(forcedDarkMode)
|
||||||
iframeCommunicator.sendSetDarkmode(darkMode)
|
useSendMarkdownToRenderer(markdownContent)
|
||||||
}
|
|
||||||
}, [darkMode, iframeCommunicator, rendererReady])
|
|
||||||
|
|
||||||
const oldScrollState = useRef<ScrollState | undefined>(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])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<ShowOnPropChangeImageLightbox details={lightboxDetails} />
|
<CommunicatorImageLightbox />
|
||||||
<iframe
|
<iframe
|
||||||
style={{ height: `${frameHeight}px` }}
|
style={{ height: `${frameHeight}px` }}
|
||||||
data-cy={'documentIframe'}
|
data-cy={'documentIframe'}
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React, { useCallback, useEffect, useState } from 'react'
|
|
||||||
import { ImageLightboxModal } from '../../markdown-renderer/replace-components/image/image-lightbox-modal'
|
|
||||||
import { ImageDetails } from '../../render-page/rendering-message'
|
|
||||||
|
|
||||||
export interface ShowOnPropChangeImageLightboxProps {
|
|
||||||
details?: ImageDetails
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ShowOnPropChangeImageLightbox: React.FC<ShowOnPropChangeImageLightboxProps> = ({ details }) => {
|
|
||||||
const [show, setShow] = useState<boolean>(false)
|
|
||||||
|
|
||||||
const hideLightbox = useCallback(() => {
|
|
||||||
setShow(false)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (details) {
|
|
||||||
setShow(true)
|
|
||||||
}
|
|
||||||
}, [details])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ImageLightboxModal
|
|
||||||
show={show}
|
|
||||||
onHide={hideLightbox}
|
|
||||||
src={details?.src}
|
|
||||||
alt={details?.alt}
|
|
||||||
title={details?.title}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -17,17 +17,17 @@ import { CoverButtons } from './cover-buttons/cover-buttons'
|
||||||
import { FeatureLinks } from './feature-links'
|
import { FeatureLinks } from './feature-links'
|
||||||
import { useIntroPageContent } from './hooks/use-intro-page-content'
|
import { useIntroPageContent } from './hooks/use-intro-page-content'
|
||||||
import { ShowIf } from '../common/show-if/show-if'
|
import { ShowIf } from '../common/show-if/show-if'
|
||||||
import { RendererType } from '../render-page/rendering-message'
|
import { RendererType } from '../render-page/window-post-message-communicator/rendering-message'
|
||||||
import { WaitSpinner } from '../common/wait-spinner/wait-spinner'
|
import { WaitSpinner } from '../common/wait-spinner/wait-spinner'
|
||||||
import { IframeEditorToRendererCommunicatorContextProvider } from '../editor-page/render-context/iframe-editor-to-renderer-communicator-context-provider'
|
|
||||||
import { useApplicationState } from '../../hooks/common/use-application-state'
|
import { useApplicationState } from '../../hooks/common/use-application-state'
|
||||||
|
import { EditorToRendererCommunicatorContextProvider } from '../editor-page/render-context/editor-to-renderer-communicator-context-provider'
|
||||||
|
|
||||||
export const IntroPage: React.FC = () => {
|
export const IntroPage: React.FC = () => {
|
||||||
const introPageContent = useIntroPageContent()
|
const introPageContent = useIntroPageContent()
|
||||||
const rendererReady = useApplicationState((state) => state.rendererStatus.rendererReady)
|
const rendererReady = useApplicationState((state) => state.rendererStatus.rendererReady)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IframeEditorToRendererCommunicatorContextProvider>
|
<EditorToRendererCommunicatorContextProvider>
|
||||||
<div className={'flex-fill mt-3'}>
|
<div className={'flex-fill mt-3'}>
|
||||||
<h1 dir='auto' className={'align-items-center d-flex justify-content-center flex-column'}>
|
<h1 dir='auto' className={'align-items-center d-flex justify-content-center flex-column'}>
|
||||||
<HedgeDocLogoWithText logoType={HedgeDocLogoType.COLOR_VERTICAL} size={HedgeDocLogoSize.BIG} />
|
<HedgeDocLogoWithText logoType={HedgeDocLogoType.COLOR_VERTICAL} size={HedgeDocLogoSize.BIG} />
|
||||||
|
@ -53,6 +53,6 @@ export const IntroPage: React.FC = () => {
|
||||||
<hr className={'mb-5'} />
|
<hr className={'mb-5'} />
|
||||||
</div>
|
</div>
|
||||||
<FeatureLinks />
|
<FeatureLinks />
|
||||||
</IframeEditorToRendererCommunicatorContextProvider>
|
</EditorToRendererCommunicatorContextProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,19 +6,23 @@
|
||||||
|
|
||||||
import React, { useCallback } from 'react'
|
import React, { useCallback } from 'react'
|
||||||
import { ImageClickHandler } from '../../markdown-renderer/replace-components/image/image-replacer'
|
import { ImageClickHandler } from '../../markdown-renderer/replace-components/image/image-replacer'
|
||||||
import { IframeRendererToEditorCommunicator } from '../iframe-renderer-to-editor-communicator'
|
import { RendererToEditorCommunicator } from '../window-post-message-communicator/renderer-to-editor-communicator'
|
||||||
|
import { CommunicationMessageType } from '../window-post-message-communicator/rendering-message'
|
||||||
|
|
||||||
export const useImageClickHandler = (iframeCommunicator: IframeRendererToEditorCommunicator): ImageClickHandler => {
|
export const useImageClickHandler = (iframeCommunicator: RendererToEditorCommunicator): ImageClickHandler => {
|
||||||
return useCallback(
|
return useCallback(
|
||||||
(event: React.MouseEvent<HTMLImageElement, MouseEvent>) => {
|
(event: React.MouseEvent<HTMLImageElement, MouseEvent>) => {
|
||||||
const image = event.target as HTMLImageElement
|
const image = event.target as HTMLImageElement
|
||||||
if (image.src === '') {
|
if (image.src === '') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
iframeCommunicator.sendClickedImageUrl({
|
iframeCommunicator.sendMessageToOtherSide({
|
||||||
|
type: CommunicationMessageType.IMAGE_CLICKED,
|
||||||
|
details: {
|
||||||
src: image.src,
|
src: image.src,
|
||||||
alt: image.alt,
|
alt: image.alt,
|
||||||
title: image.title
|
title: image.title
|
||||||
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
[iframeCommunicator]
|
[iframeCommunicator]
|
||||||
|
|
|
@ -1,136 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
import { ScrollState } from '../editor-page/synced-scroll/scroll-props'
|
|
||||||
import { IframeCommunicator } from './iframe-communicator'
|
|
||||||
import {
|
|
||||||
BaseConfiguration,
|
|
||||||
EditorToRendererIframeMessage,
|
|
||||||
ImageDetails,
|
|
||||||
RendererToEditorIframeMessage,
|
|
||||||
RenderIframeMessageType
|
|
||||||
} from './rendering-message'
|
|
||||||
import { RendererFrontmatterInfo } from '../common/note-frontmatter/types'
|
|
||||||
|
|
||||||
export class IframeEditorToRendererCommunicator extends IframeCommunicator<
|
|
||||||
EditorToRendererIframeMessage,
|
|
||||||
RendererToEditorIframeMessage
|
|
||||||
> {
|
|
||||||
private onSetScrollSourceToRendererHandler?: () => void
|
|
||||||
private onTaskCheckboxChangeHandler?: (lineInMarkdown: number, checked: boolean) => void
|
|
||||||
private onFirstHeadingChangeHandler?: (heading?: string) => void
|
|
||||||
private onSetScrollStateHandler?: (scrollState: ScrollState) => void
|
|
||||||
private onRendererReadyHandler?: () => void
|
|
||||||
private onImageClickedHandler?: (details: ImageDetails) => void
|
|
||||||
private onHeightChangeHandler?: (height: number) => void
|
|
||||||
private onWordCountCalculatedHandler?: (words: number) => void
|
|
||||||
|
|
||||||
public onHeightChange(handler?: (height: number) => void): void {
|
|
||||||
this.onHeightChangeHandler = handler
|
|
||||||
}
|
|
||||||
|
|
||||||
public onImageClicked(handler?: (details: ImageDetails) => void): void {
|
|
||||||
this.onImageClickedHandler = handler
|
|
||||||
}
|
|
||||||
|
|
||||||
public onRendererReady(handler?: () => void): void {
|
|
||||||
this.onRendererReadyHandler = handler
|
|
||||||
}
|
|
||||||
|
|
||||||
public onSetScrollSourceToRenderer(handler?: () => void): void {
|
|
||||||
this.onSetScrollSourceToRendererHandler = handler
|
|
||||||
}
|
|
||||||
|
|
||||||
public onTaskCheckboxChange(handler?: (lineInMarkdown: number, checked: boolean) => void): void {
|
|
||||||
this.onTaskCheckboxChangeHandler = handler
|
|
||||||
}
|
|
||||||
|
|
||||||
public onFirstHeadingChange(handler?: (heading?: string) => void): void {
|
|
||||||
this.onFirstHeadingChangeHandler = handler
|
|
||||||
}
|
|
||||||
|
|
||||||
public onSetScrollState(handler?: (scrollState: ScrollState) => void): void {
|
|
||||||
this.onSetScrollStateHandler = handler
|
|
||||||
}
|
|
||||||
|
|
||||||
public onWordCountCalculated(handler?: (words: number) => void): void {
|
|
||||||
this.onWordCountCalculatedHandler = handler
|
|
||||||
}
|
|
||||||
|
|
||||||
public sendSetBaseConfiguration(baseConfiguration: BaseConfiguration): void {
|
|
||||||
this.sendMessageToOtherSide({
|
|
||||||
type: RenderIframeMessageType.SET_BASE_CONFIGURATION,
|
|
||||||
baseConfiguration
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public sendSetMarkdownContent(markdownContent: string): void {
|
|
||||||
this.sendMessageToOtherSide({
|
|
||||||
type: RenderIframeMessageType.SET_MARKDOWN_CONTENT,
|
|
||||||
content: markdownContent
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public sendSetDarkmode(darkModeActivated: boolean): void {
|
|
||||||
this.sendMessageToOtherSide({
|
|
||||||
type: RenderIframeMessageType.SET_DARKMODE,
|
|
||||||
activated: darkModeActivated
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public sendScrollState(scrollState?: ScrollState): void {
|
|
||||||
if (!scrollState) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.sendMessageToOtherSide({
|
|
||||||
type: RenderIframeMessageType.SET_SCROLL_STATE,
|
|
||||||
scrollState
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public sendGetWordCount(): void {
|
|
||||||
this.sendMessageToOtherSide({
|
|
||||||
type: RenderIframeMessageType.GET_WORD_COUNT
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public sendSetFrontmatterInfo(frontmatterInfo: RendererFrontmatterInfo): void {
|
|
||||||
this.sendMessageToOtherSide({
|
|
||||||
type: RenderIframeMessageType.SET_FRONTMATTER_INFO,
|
|
||||||
frontmatterInfo: frontmatterInfo
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
protected handleEvent(event: MessageEvent<RendererToEditorIframeMessage>): boolean | undefined {
|
|
||||||
const renderMessage = event.data
|
|
||||||
switch (renderMessage.type) {
|
|
||||||
case RenderIframeMessageType.RENDERER_READY:
|
|
||||||
this.enableCommunication()
|
|
||||||
this.onRendererReadyHandler?.()
|
|
||||||
return false
|
|
||||||
case RenderIframeMessageType.SET_SCROLL_SOURCE_TO_RENDERER:
|
|
||||||
this.onSetScrollSourceToRendererHandler?.()
|
|
||||||
return false
|
|
||||||
case RenderIframeMessageType.SET_SCROLL_STATE:
|
|
||||||
this.onSetScrollStateHandler?.(renderMessage.scrollState)
|
|
||||||
return false
|
|
||||||
case RenderIframeMessageType.ON_FIRST_HEADING_CHANGE:
|
|
||||||
this.onFirstHeadingChangeHandler?.(renderMessage.firstHeading)
|
|
||||||
return false
|
|
||||||
case RenderIframeMessageType.ON_TASK_CHECKBOX_CHANGE:
|
|
||||||
this.onTaskCheckboxChangeHandler?.(renderMessage.lineInMarkdown, renderMessage.checked)
|
|
||||||
return false
|
|
||||||
case RenderIframeMessageType.IMAGE_CLICKED:
|
|
||||||
this.onImageClickedHandler?.(renderMessage.details)
|
|
||||||
return false
|
|
||||||
case RenderIframeMessageType.ON_HEIGHT_CHANGE:
|
|
||||||
this.onHeightChangeHandler?.(renderMessage.height)
|
|
||||||
return false
|
|
||||||
case RenderIframeMessageType.ON_WORD_COUNT_CALCULATED:
|
|
||||||
this.onWordCountCalculatedHandler?.(renderMessage.words)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,16 +4,21 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useCallback, useEffect, useState } from 'react'
|
import React, { useCallback, useState } from 'react'
|
||||||
import { ScrollState } from '../editor-page/synced-scroll/scroll-props'
|
import { ScrollState } from '../editor-page/synced-scroll/scroll-props'
|
||||||
import { BaseConfiguration, RendererType } from './rendering-message'
|
import {
|
||||||
|
BaseConfiguration,
|
||||||
|
CommunicationMessageType,
|
||||||
|
RendererType
|
||||||
|
} from './window-post-message-communicator/rendering-message'
|
||||||
import { setDarkMode } from '../../redux/dark-mode/methods'
|
import { setDarkMode } from '../../redux/dark-mode/methods'
|
||||||
import { ImageClickHandler } from '../markdown-renderer/replace-components/image/image-replacer'
|
import { ImageClickHandler } from '../markdown-renderer/replace-components/image/image-replacer'
|
||||||
import { useImageClickHandler } from './hooks/use-image-click-handler'
|
import { useImageClickHandler } from './hooks/use-image-click-handler'
|
||||||
import { MarkdownDocument } from './markdown-document'
|
import { MarkdownDocument } from './markdown-document'
|
||||||
import { useIFrameRendererToEditorCommunicator } from '../editor-page/render-context/iframe-renderer-to-editor-communicator-context-provider'
|
|
||||||
import { countWords } from './word-counter'
|
import { countWords } from './word-counter'
|
||||||
import { RendererFrontmatterInfo } from '../common/note-frontmatter/types'
|
import { RendererFrontmatterInfo } from '../common/note-frontmatter/types'
|
||||||
|
import { useRendererToEditorCommunicator } from '../editor-page/render-context/renderer-to-editor-communicator-context-provider'
|
||||||
|
import { useRendererReceiveHandler } from './window-post-message-communicator/hooks/use-renderer-receive-handler'
|
||||||
|
|
||||||
export const IframeMarkdownRenderer: React.FC = () => {
|
export const IframeMarkdownRenderer: React.FC = () => {
|
||||||
const [markdownContent, setMarkdownContent] = useState('')
|
const [markdownContent, setMarkdownContent] = useState('')
|
||||||
|
@ -25,60 +30,76 @@ export const IframeMarkdownRenderer: React.FC = () => {
|
||||||
deprecatedSyntax: false
|
deprecatedSyntax: false
|
||||||
})
|
})
|
||||||
|
|
||||||
const iframeCommunicator = useIFrameRendererToEditorCommunicator()
|
const communicator = useRendererToEditorCommunicator()
|
||||||
|
|
||||||
const countWordsInRenderedDocument = useCallback(() => {
|
const countWordsInRenderedDocument = useCallback(() => {
|
||||||
const documentContainer = document.querySelector('.markdown-body')
|
const documentContainer = document.querySelector('.markdown-body')
|
||||||
if (!documentContainer) {
|
communicator.sendMessageToOtherSide({
|
||||||
iframeCommunicator.sendWordCountCalculated(0)
|
type: CommunicationMessageType.ON_WORD_COUNT_CALCULATED,
|
||||||
return
|
words: documentContainer ? countWords(documentContainer) : 0
|
||||||
}
|
})
|
||||||
const wordCount = countWords(documentContainer)
|
}, [communicator])
|
||||||
iframeCommunicator.sendWordCountCalculated(wordCount)
|
|
||||||
}, [iframeCommunicator])
|
|
||||||
|
|
||||||
useEffect(() => iframeCommunicator.onSetBaseConfiguration(setBaseConfiguration), [iframeCommunicator])
|
useRendererReceiveHandler(CommunicationMessageType.SET_BASE_CONFIGURATION, (values) =>
|
||||||
useEffect(() => iframeCommunicator.onSetMarkdownContent(setMarkdownContent), [iframeCommunicator])
|
setBaseConfiguration(values.baseConfiguration)
|
||||||
useEffect(() => iframeCommunicator.onSetDarkMode(setDarkMode), [iframeCommunicator])
|
|
||||||
useEffect(() => iframeCommunicator.onSetScrollState(setScrollState), [iframeCommunicator, scrollState])
|
|
||||||
useEffect(() => iframeCommunicator.onSetFrontmatterInfo(setFrontmatterInfo), [iframeCommunicator, setFrontmatterInfo])
|
|
||||||
useEffect(
|
|
||||||
() => iframeCommunicator.onGetWordCount(countWordsInRenderedDocument),
|
|
||||||
[iframeCommunicator, countWordsInRenderedDocument]
|
|
||||||
)
|
)
|
||||||
|
useRendererReceiveHandler(CommunicationMessageType.SET_MARKDOWN_CONTENT, (values) =>
|
||||||
|
setMarkdownContent(values.content)
|
||||||
|
)
|
||||||
|
useRendererReceiveHandler(CommunicationMessageType.SET_DARKMODE, (values) => setDarkMode(values.activated))
|
||||||
|
useRendererReceiveHandler(CommunicationMessageType.SET_SCROLL_STATE, (values) => setScrollState(values.scrollState))
|
||||||
|
useRendererReceiveHandler(CommunicationMessageType.SET_FRONTMATTER_INFO, (values) =>
|
||||||
|
setFrontmatterInfo(values.frontmatterInfo)
|
||||||
|
)
|
||||||
|
useRendererReceiveHandler(CommunicationMessageType.GET_WORD_COUNT, () => countWordsInRenderedDocument())
|
||||||
|
|
||||||
const onTaskCheckedChange = useCallback(
|
const onTaskCheckedChange = useCallback(
|
||||||
(lineInMarkdown: number, checked: boolean) => {
|
(lineInMarkdown: number, checked: boolean) => {
|
||||||
iframeCommunicator.sendTaskCheckBoxChange(lineInMarkdown, checked)
|
communicator.sendMessageToOtherSide({
|
||||||
|
type: CommunicationMessageType.ON_TASK_CHECKBOX_CHANGE,
|
||||||
|
checked,
|
||||||
|
lineInMarkdown
|
||||||
|
})
|
||||||
},
|
},
|
||||||
[iframeCommunicator]
|
[communicator]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onFirstHeadingChange = useCallback(
|
const onFirstHeadingChange = useCallback(
|
||||||
(firstHeading?: string) => {
|
(firstHeading?: string) => {
|
||||||
iframeCommunicator.sendFirstHeadingChanged(firstHeading)
|
communicator.sendMessageToOtherSide({
|
||||||
|
type: CommunicationMessageType.ON_FIRST_HEADING_CHANGE,
|
||||||
|
firstHeading
|
||||||
|
})
|
||||||
},
|
},
|
||||||
[iframeCommunicator]
|
[communicator]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onMakeScrollSource = useCallback(() => {
|
const onMakeScrollSource = useCallback(() => {
|
||||||
iframeCommunicator.sendSetScrollSourceToRenderer()
|
communicator.sendMessageToOtherSide({
|
||||||
}, [iframeCommunicator])
|
type: CommunicationMessageType.SET_SCROLL_SOURCE_TO_RENDERER
|
||||||
|
})
|
||||||
|
}, [communicator])
|
||||||
|
|
||||||
const onScroll = useCallback(
|
const onScroll = useCallback(
|
||||||
(scrollState: ScrollState) => {
|
(scrollState: ScrollState) => {
|
||||||
iframeCommunicator.sendSetScrollState(scrollState)
|
communicator.sendMessageToOtherSide({
|
||||||
|
type: CommunicationMessageType.SET_SCROLL_STATE,
|
||||||
|
scrollState
|
||||||
|
})
|
||||||
},
|
},
|
||||||
[iframeCommunicator]
|
[communicator]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onImageClick: ImageClickHandler = useImageClickHandler(iframeCommunicator)
|
const onImageClick: ImageClickHandler = useImageClickHandler(communicator)
|
||||||
|
|
||||||
const onHeightChange = useCallback(
|
const onHeightChange = useCallback(
|
||||||
(height: number) => {
|
(height: number) => {
|
||||||
iframeCommunicator.sendHeightChange(height)
|
communicator.sendMessageToOtherSide({
|
||||||
|
type: CommunicationMessageType.ON_HEIGHT_CHANGE,
|
||||||
|
height
|
||||||
|
})
|
||||||
},
|
},
|
||||||
[iframeCommunicator]
|
[communicator]
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!baseConfiguration) {
|
if (!baseConfiguration) {
|
||||||
|
|
|
@ -1,132 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { ScrollState } from '../editor-page/synced-scroll/scroll-props'
|
|
||||||
import { IframeCommunicator } from './iframe-communicator'
|
|
||||||
import {
|
|
||||||
BaseConfiguration,
|
|
||||||
EditorToRendererIframeMessage,
|
|
||||||
ImageDetails,
|
|
||||||
RendererToEditorIframeMessage,
|
|
||||||
RenderIframeMessageType
|
|
||||||
} from './rendering-message'
|
|
||||||
import { RendererFrontmatterInfo } from '../common/note-frontmatter/types'
|
|
||||||
|
|
||||||
export class IframeRendererToEditorCommunicator extends IframeCommunicator<
|
|
||||||
RendererToEditorIframeMessage,
|
|
||||||
EditorToRendererIframeMessage
|
|
||||||
> {
|
|
||||||
private onSetMarkdownContentHandler?: (markdownContent: string) => void
|
|
||||||
private onSetDarkModeHandler?: (darkModeActivated: boolean) => void
|
|
||||||
private onSetScrollStateHandler?: (scrollState: ScrollState) => void
|
|
||||||
private onSetBaseConfigurationHandler?: (baseConfiguration: BaseConfiguration) => void
|
|
||||||
private onGetWordCountHandler?: () => void
|
|
||||||
private onSetFrontmatterInfoHandler?: (frontmatterInfo: RendererFrontmatterInfo) => void
|
|
||||||
|
|
||||||
public onSetBaseConfiguration(handler?: (baseConfiguration: BaseConfiguration) => void): void {
|
|
||||||
this.onSetBaseConfigurationHandler = handler
|
|
||||||
}
|
|
||||||
|
|
||||||
public onSetMarkdownContent(handler?: (markdownContent: string) => void): void {
|
|
||||||
this.onSetMarkdownContentHandler = handler
|
|
||||||
}
|
|
||||||
|
|
||||||
public onSetDarkMode(handler?: (darkModeActivated: boolean) => void): void {
|
|
||||||
this.onSetDarkModeHandler = handler
|
|
||||||
}
|
|
||||||
|
|
||||||
public onSetScrollState(handler?: (scrollState: ScrollState) => void): void {
|
|
||||||
this.onSetScrollStateHandler = handler
|
|
||||||
}
|
|
||||||
|
|
||||||
public onGetWordCount(handler?: () => void): void {
|
|
||||||
this.onGetWordCountHandler = handler
|
|
||||||
}
|
|
||||||
|
|
||||||
public onSetFrontmatterInfo(handler?: (frontmatterInfo: RendererFrontmatterInfo) => void): void {
|
|
||||||
this.onSetFrontmatterInfoHandler = handler
|
|
||||||
}
|
|
||||||
|
|
||||||
public sendRendererReady(): void {
|
|
||||||
this.enableCommunication()
|
|
||||||
this.sendMessageToOtherSide({
|
|
||||||
type: RenderIframeMessageType.RENDERER_READY
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public sendTaskCheckBoxChange(lineInMarkdown: number, checked: boolean): void {
|
|
||||||
this.sendMessageToOtherSide({
|
|
||||||
type: RenderIframeMessageType.ON_TASK_CHECKBOX_CHANGE,
|
|
||||||
checked,
|
|
||||||
lineInMarkdown
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public sendFirstHeadingChanged(firstHeading: string | undefined): void {
|
|
||||||
this.sendMessageToOtherSide({
|
|
||||||
type: RenderIframeMessageType.ON_FIRST_HEADING_CHANGE,
|
|
||||||
firstHeading
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public sendSetScrollSourceToRenderer(): void {
|
|
||||||
this.sendMessageToOtherSide({
|
|
||||||
type: RenderIframeMessageType.SET_SCROLL_SOURCE_TO_RENDERER
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public sendSetScrollState(scrollState: ScrollState): void {
|
|
||||||
this.sendMessageToOtherSide({
|
|
||||||
type: RenderIframeMessageType.SET_SCROLL_STATE,
|
|
||||||
scrollState
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public sendClickedImageUrl(details: ImageDetails): void {
|
|
||||||
this.sendMessageToOtherSide({
|
|
||||||
type: RenderIframeMessageType.IMAGE_CLICKED,
|
|
||||||
details: details
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public sendHeightChange(height: number): void {
|
|
||||||
this.sendMessageToOtherSide({
|
|
||||||
type: RenderIframeMessageType.ON_HEIGHT_CHANGE,
|
|
||||||
height
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public sendWordCountCalculated(words: number): void {
|
|
||||||
this.sendMessageToOtherSide({
|
|
||||||
type: RenderIframeMessageType.ON_WORD_COUNT_CALCULATED,
|
|
||||||
words
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
protected handleEvent(event: MessageEvent<EditorToRendererIframeMessage>): boolean | undefined {
|
|
||||||
const renderMessage = event.data
|
|
||||||
switch (renderMessage.type) {
|
|
||||||
case RenderIframeMessageType.SET_MARKDOWN_CONTENT:
|
|
||||||
this.onSetMarkdownContentHandler?.(renderMessage.content)
|
|
||||||
return false
|
|
||||||
case RenderIframeMessageType.SET_DARKMODE:
|
|
||||||
this.onSetDarkModeHandler?.(renderMessage.activated)
|
|
||||||
return false
|
|
||||||
case RenderIframeMessageType.SET_SCROLL_STATE:
|
|
||||||
this.onSetScrollStateHandler?.(renderMessage.scrollState)
|
|
||||||
return false
|
|
||||||
case RenderIframeMessageType.SET_BASE_CONFIGURATION:
|
|
||||||
this.onSetBaseConfigurationHandler?.(renderMessage.baseConfiguration)
|
|
||||||
return false
|
|
||||||
case RenderIframeMessageType.GET_WORD_COUNT:
|
|
||||||
this.onGetWordCountHandler?.()
|
|
||||||
return false
|
|
||||||
case RenderIframeMessageType.SET_FRONTMATTER_INFO:
|
|
||||||
this.onSetFrontmatterInfoHandler?.(renderMessage.frontmatterInfo)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,15 +6,15 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useApplyDarkMode } from '../../hooks/common/use-apply-dark-mode'
|
import { useApplyDarkMode } from '../../hooks/common/use-apply-dark-mode'
|
||||||
import { IframeMarkdownRenderer } from './iframe-markdown-renderer'
|
import { IframeMarkdownRenderer } from './iframe-markdown-renderer'
|
||||||
import { IframeRendererToEditorCommunicatorContextProvider } from '../editor-page/render-context/iframe-renderer-to-editor-communicator-context-provider'
|
import { RendererToEditorCommunicatorContextProvider } from '../editor-page/render-context/renderer-to-editor-communicator-context-provider'
|
||||||
|
|
||||||
export const RenderPage: React.FC = () => {
|
export const RenderPage: React.FC = () => {
|
||||||
useApplyDarkMode()
|
useApplyDarkMode()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IframeRendererToEditorCommunicatorContextProvider>
|
<RendererToEditorCommunicatorContextProvider>
|
||||||
<IframeMarkdownRenderer />
|
<IframeMarkdownRenderer />
|
||||||
</IframeRendererToEditorCommunicatorContextProvider>
|
</RendererToEditorCommunicatorContextProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { WindowPostMessageCommunicator } from './window-post-message-communicator'
|
||||||
|
import { CommunicationMessages, EditorToRendererMessageType, RendererToEditorMessageType } from './rendering-message'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The communicator that is used to send messages from the editor to the renderer.
|
||||||
|
*/
|
||||||
|
export class EditorToRendererCommunicator extends WindowPostMessageCommunicator<
|
||||||
|
RendererToEditorMessageType,
|
||||||
|
EditorToRendererMessageType,
|
||||||
|
CommunicationMessages
|
||||||
|
> {
|
||||||
|
protected generateLogIdentifier(): string {
|
||||||
|
return 'E=>R'
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import { CommunicationMessages, RendererToEditorMessageType } from '../rendering-message'
|
||||||
|
import { useEditorToRendererCommunicator } from '../../../editor-page/render-context/editor-to-renderer-communicator-context-provider'
|
||||||
|
import { Handler } from '../window-post-message-communicator'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the handler for the given message type in the current editor to renderer communicator.
|
||||||
|
*
|
||||||
|
* @param messageType The message type that should be used to listen to.
|
||||||
|
* @param handler The handler that should be called if a message with the given message type was received.
|
||||||
|
*/
|
||||||
|
export const useEditorReceiveHandler = <R extends RendererToEditorMessageType>(
|
||||||
|
messageType: R,
|
||||||
|
handler: Handler<CommunicationMessages, R>
|
||||||
|
): void => {
|
||||||
|
const editorToRendererCommunicator = useEditorToRendererCommunicator()
|
||||||
|
useEffect(() => {
|
||||||
|
editorToRendererCommunicator.setHandler(messageType, handler)
|
||||||
|
return () => {
|
||||||
|
editorToRendererCommunicator.setHandler(messageType, undefined)
|
||||||
|
}
|
||||||
|
}, [editorToRendererCommunicator, handler, messageType])
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the given callback if it changes or the renderer is ready for receiving messages.
|
||||||
|
*
|
||||||
|
* @param sendOnReadyCallback The callback that should get executed.
|
||||||
|
*/
|
||||||
|
export const useEffectOnRendererReady = (sendOnReadyCallback: () => void): void => {
|
||||||
|
const rendererReady = useApplicationState((state) => state.rendererStatus.rendererReady)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (rendererReady) {
|
||||||
|
sendOnReadyCallback()
|
||||||
|
}
|
||||||
|
}, [rendererReady, sendOnReadyCallback])
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current ready status of the renderer.
|
||||||
|
*/
|
||||||
|
export const useIsRendererReady = (): boolean => useApplicationState((state) => state.rendererStatus.rendererReady)
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import { CommunicationMessages, EditorToRendererMessageType } from '../rendering-message'
|
||||||
|
import { Handler } from '../window-post-message-communicator'
|
||||||
|
import { useRendererToEditorCommunicator } from '../../../editor-page/render-context/renderer-to-editor-communicator-context-provider'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the handler for the given message type in the current renderer to editor communicator.
|
||||||
|
*
|
||||||
|
* @param messageType The message type that should be used to listen to.
|
||||||
|
* @param handler The handler that should be called if a message with the given message type was received.
|
||||||
|
*/
|
||||||
|
export const useRendererReceiveHandler = <MESSAGE_TYPE extends EditorToRendererMessageType>(
|
||||||
|
messageType: MESSAGE_TYPE,
|
||||||
|
handler: Handler<CommunicationMessages, MESSAGE_TYPE>
|
||||||
|
): void => {
|
||||||
|
const editorToRendererCommunicator = useRendererToEditorCommunicator()
|
||||||
|
useEffect(() => {
|
||||||
|
editorToRendererCommunicator.setHandler(messageType, handler)
|
||||||
|
return () => {
|
||||||
|
editorToRendererCommunicator.setHandler(messageType, undefined)
|
||||||
|
}
|
||||||
|
}, [editorToRendererCommunicator, handler, messageType])
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
import { CommunicationMessages, EditorToRendererMessageType } from '../rendering-message'
|
||||||
|
import { useEditorToRendererCommunicator } from '../../../editor-page/render-context/editor-to-renderer-communicator-context-provider'
|
||||||
|
import { PostMessage } from '../window-post-message-communicator'
|
||||||
|
import { useEffectOnRendererReady } from './use-effect-on-renderer-ready'
|
||||||
|
|
||||||
|
export const useSendToRenderer = (
|
||||||
|
message: undefined | Extract<CommunicationMessages, PostMessage<EditorToRendererMessageType>>
|
||||||
|
): void => {
|
||||||
|
const iframeCommunicator = useEditorToRendererCommunicator()
|
||||||
|
|
||||||
|
useEffectOnRendererReady(
|
||||||
|
useCallback(() => {
|
||||||
|
if (message) {
|
||||||
|
iframeCommunicator.sendMessageToOtherSide(message)
|
||||||
|
}
|
||||||
|
}, [iframeCommunicator, message])
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { WindowPostMessageCommunicator } from './window-post-message-communicator'
|
||||||
|
import { CommunicationMessages, EditorToRendererMessageType, RendererToEditorMessageType } from './rendering-message'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The communicator that is used to send messages from the renderer to the editor.
|
||||||
|
*/
|
||||||
|
export class RendererToEditorCommunicator extends WindowPostMessageCommunicator<
|
||||||
|
EditorToRendererMessageType,
|
||||||
|
RendererToEditorMessageType,
|
||||||
|
CommunicationMessages
|
||||||
|
> {
|
||||||
|
protected generateLogIdentifier(): string {
|
||||||
|
return 'E<=R'
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,10 +3,10 @@
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { ScrollState } from '../editor-page/synced-scroll/scroll-props'
|
import { ScrollState } from '../../editor-page/synced-scroll/scroll-props'
|
||||||
import { RendererFrontmatterInfo } from '../common/note-frontmatter/types'
|
import { RendererFrontmatterInfo } from '../../common/note-frontmatter/types'
|
||||||
|
|
||||||
export enum RenderIframeMessageType {
|
export enum CommunicationMessageType {
|
||||||
SET_MARKDOWN_CONTENT = 'SET_MARKDOWN_CONTENT',
|
SET_MARKDOWN_CONTENT = 'SET_MARKDOWN_CONTENT',
|
||||||
RENDERER_READY = 'RENDERER_READY',
|
RENDERER_READY = 'RENDERER_READY',
|
||||||
SET_DARKMODE = 'SET_DARKMODE',
|
SET_DARKMODE = 'SET_DARKMODE',
|
||||||
|
@ -22,12 +22,12 @@ export enum RenderIframeMessageType {
|
||||||
SET_FRONTMATTER_INFO = 'SET_FRONTMATTER_INFO'
|
SET_FRONTMATTER_INFO = 'SET_FRONTMATTER_INFO'
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RendererToEditorSimpleMessage {
|
export interface NoPayloadMessage {
|
||||||
type: RenderIframeMessageType.RENDERER_READY | RenderIframeMessageType.SET_SCROLL_SOURCE_TO_RENDERER
|
type: CommunicationMessageType.RENDERER_READY | CommunicationMessageType.SET_SCROLL_SOURCE_TO_RENDERER
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SetDarkModeMessage {
|
export interface SetDarkModeMessage {
|
||||||
type: RenderIframeMessageType.SET_DARKMODE
|
type: CommunicationMessageType.SET_DARKMODE
|
||||||
activated: boolean
|
activated: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,72 +38,87 @@ export interface ImageDetails {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SetBaseUrlMessage {
|
export interface SetBaseUrlMessage {
|
||||||
type: RenderIframeMessageType.SET_BASE_CONFIGURATION
|
type: CommunicationMessageType.SET_BASE_CONFIGURATION
|
||||||
baseConfiguration: BaseConfiguration
|
baseConfiguration: BaseConfiguration
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetWordCountMessage {
|
export interface GetWordCountMessage {
|
||||||
type: RenderIframeMessageType.GET_WORD_COUNT
|
type: CommunicationMessageType.GET_WORD_COUNT
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ImageClickedMessage {
|
export interface ImageClickedMessage {
|
||||||
type: RenderIframeMessageType.IMAGE_CLICKED
|
type: CommunicationMessageType.IMAGE_CLICKED
|
||||||
details: ImageDetails
|
details: ImageDetails
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SetMarkdownContentMessage {
|
export interface SetMarkdownContentMessage {
|
||||||
type: RenderIframeMessageType.SET_MARKDOWN_CONTENT
|
type: CommunicationMessageType.SET_MARKDOWN_CONTENT
|
||||||
content: string
|
content: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SetScrollStateMessage {
|
export interface SetScrollStateMessage {
|
||||||
type: RenderIframeMessageType.SET_SCROLL_STATE
|
type: CommunicationMessageType.SET_SCROLL_STATE
|
||||||
scrollState: ScrollState
|
scrollState: ScrollState
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OnTaskCheckboxChangeMessage {
|
export interface OnTaskCheckboxChangeMessage {
|
||||||
type: RenderIframeMessageType.ON_TASK_CHECKBOX_CHANGE
|
type: CommunicationMessageType.ON_TASK_CHECKBOX_CHANGE
|
||||||
lineInMarkdown: number
|
lineInMarkdown: number
|
||||||
checked: boolean
|
checked: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OnFirstHeadingChangeMessage {
|
export interface OnFirstHeadingChangeMessage {
|
||||||
type: RenderIframeMessageType.ON_FIRST_HEADING_CHANGE
|
type: CommunicationMessageType.ON_FIRST_HEADING_CHANGE
|
||||||
firstHeading: string | undefined
|
firstHeading: string | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SetFrontmatterInfoMessage {
|
export interface SetFrontmatterInfoMessage {
|
||||||
type: RenderIframeMessageType.SET_FRONTMATTER_INFO
|
type: CommunicationMessageType.SET_FRONTMATTER_INFO
|
||||||
frontmatterInfo: RendererFrontmatterInfo
|
frontmatterInfo: RendererFrontmatterInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OnHeightChangeMessage {
|
export interface OnHeightChangeMessage {
|
||||||
type: RenderIframeMessageType.ON_HEIGHT_CHANGE
|
type: CommunicationMessageType.ON_HEIGHT_CHANGE
|
||||||
height: number
|
height: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OnWordCountCalculatedMessage {
|
export interface OnWordCountCalculatedMessage {
|
||||||
type: RenderIframeMessageType.ON_WORD_COUNT_CALCULATED
|
type: CommunicationMessageType.ON_WORD_COUNT_CALCULATED
|
||||||
words: number
|
words: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type EditorToRendererIframeMessage =
|
export type CommunicationMessages =
|
||||||
| SetMarkdownContentMessage
|
| NoPayloadMessage
|
||||||
| SetDarkModeMessage
|
| SetDarkModeMessage
|
||||||
| SetScrollStateMessage
|
|
||||||
| SetBaseUrlMessage
|
| SetBaseUrlMessage
|
||||||
| GetWordCountMessage
|
| GetWordCountMessage
|
||||||
| SetFrontmatterInfoMessage
|
|
||||||
|
|
||||||
export type RendererToEditorIframeMessage =
|
|
||||||
| RendererToEditorSimpleMessage
|
|
||||||
| OnFirstHeadingChangeMessage
|
|
||||||
| OnTaskCheckboxChangeMessage
|
|
||||||
| SetScrollStateMessage
|
|
||||||
| ImageClickedMessage
|
| ImageClickedMessage
|
||||||
|
| SetMarkdownContentMessage
|
||||||
|
| SetScrollStateMessage
|
||||||
|
| OnTaskCheckboxChangeMessage
|
||||||
|
| OnFirstHeadingChangeMessage
|
||||||
|
| SetFrontmatterInfoMessage
|
||||||
| OnHeightChangeMessage
|
| OnHeightChangeMessage
|
||||||
| OnWordCountCalculatedMessage
|
| OnWordCountCalculatedMessage
|
||||||
|
|
||||||
|
export type EditorToRendererMessageType =
|
||||||
|
| CommunicationMessageType.SET_MARKDOWN_CONTENT
|
||||||
|
| CommunicationMessageType.SET_DARKMODE
|
||||||
|
| CommunicationMessageType.SET_SCROLL_STATE
|
||||||
|
| CommunicationMessageType.SET_BASE_CONFIGURATION
|
||||||
|
| CommunicationMessageType.GET_WORD_COUNT
|
||||||
|
| CommunicationMessageType.SET_FRONTMATTER_INFO
|
||||||
|
|
||||||
|
export type RendererToEditorMessageType =
|
||||||
|
| CommunicationMessageType.RENDERER_READY
|
||||||
|
| CommunicationMessageType.SET_SCROLL_SOURCE_TO_RENDERER
|
||||||
|
| CommunicationMessageType.ON_FIRST_HEADING_CHANGE
|
||||||
|
| CommunicationMessageType.ON_TASK_CHECKBOX_CHANGE
|
||||||
|
| CommunicationMessageType.SET_SCROLL_STATE
|
||||||
|
| CommunicationMessageType.IMAGE_CLICKED
|
||||||
|
| CommunicationMessageType.ON_HEIGHT_CHANGE
|
||||||
|
| CommunicationMessageType.ON_WORD_COUNT_CALCULATED
|
||||||
|
|
||||||
export enum RendererType {
|
export enum RendererType {
|
||||||
DOCUMENT,
|
DOCUMENT,
|
||||||
INTRO,
|
INTRO,
|
|
@ -9,19 +9,39 @@
|
||||||
*/
|
*/
|
||||||
export class IframeCommunicatorSendingError extends Error {}
|
export class IframeCommunicatorSendingError extends Error {}
|
||||||
|
|
||||||
|
export type Handler<MESSAGES, MESSAGE_TYPE extends string> =
|
||||||
|
| ((values: Extract<MESSAGES, PostMessage<MESSAGE_TYPE>>) => void)
|
||||||
|
| undefined
|
||||||
|
|
||||||
|
export type HandlerMap<MESSAGES, MESSAGE_TYPE extends string> = Partial<{
|
||||||
|
[key in MESSAGE_TYPE]: Handler<MESSAGES, MESSAGE_TYPE>
|
||||||
|
}>
|
||||||
|
|
||||||
|
export interface PostMessage<MESSAGE_TYPE extends string> {
|
||||||
|
type: MESSAGE_TYPE
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for communication between renderer and editor.
|
* Base class for communication between renderer and editor.
|
||||||
*/
|
*/
|
||||||
export abstract class IframeCommunicator<SEND, RECEIVE> {
|
export abstract class WindowPostMessageCommunicator<
|
||||||
|
RECEIVE_TYPE extends string,
|
||||||
|
SEND_TYPE extends string,
|
||||||
|
MESSAGES extends PostMessage<RECEIVE_TYPE | SEND_TYPE>
|
||||||
|
> {
|
||||||
private messageTarget?: Window
|
private messageTarget?: Window
|
||||||
private targetOrigin?: string
|
private targetOrigin?: string
|
||||||
private communicationEnabled: boolean
|
private communicationEnabled: boolean
|
||||||
|
private handlers: HandlerMap<MESSAGES, RECEIVE_TYPE> = {}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
window.addEventListener('message', this.handleEvent.bind(this))
|
window.addEventListener('message', this.handleEvent.bind(this))
|
||||||
this.communicationEnabled = false
|
this.communicationEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the message event listener from the {@link window}
|
||||||
|
*/
|
||||||
public unregisterEventListener(): void {
|
public unregisterEventListener(): void {
|
||||||
window.removeEventListener('message', this.handleEvent.bind(this))
|
window.removeEventListener('message', this.handleEvent.bind(this))
|
||||||
}
|
}
|
||||||
|
@ -53,7 +73,7 @@ export abstract class IframeCommunicator<SEND, RECEIVE> {
|
||||||
* Enables the message communication.
|
* Enables the message communication.
|
||||||
* Should be called as soon as the other sides is ready to receive messages.
|
* Should be called as soon as the other sides is ready to receive messages.
|
||||||
*/
|
*/
|
||||||
protected enableCommunication(): void {
|
public enableCommunication(): void {
|
||||||
this.communicationEnabled = true
|
this.communicationEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,7 +82,7 @@ export abstract class IframeCommunicator<SEND, RECEIVE> {
|
||||||
*
|
*
|
||||||
* @param message The message to send.
|
* @param message The message to send.
|
||||||
*/
|
*/
|
||||||
protected sendMessageToOtherSide(message: SEND): void {
|
public sendMessageToOtherSide(message: Extract<MESSAGES, PostMessage<SEND_TYPE>>): void {
|
||||||
if (this.messageTarget === undefined || this.targetOrigin === undefined) {
|
if (this.messageTarget === undefined || this.targetOrigin === undefined) {
|
||||||
throw new IframeCommunicatorSendingError(`Other side is not set.\nMessage was: ${JSON.stringify(message)}`)
|
throw new IframeCommunicatorSendingError(`Other side is not set.\nMessage was: ${JSON.stringify(message)}`)
|
||||||
}
|
}
|
||||||
|
@ -71,8 +91,42 @@ export abstract class IframeCommunicator<SEND, RECEIVE> {
|
||||||
`Communication isn't enabled. Maybe the other side is not ready?\nMessage was: ${JSON.stringify(message)}`
|
`Communication isn't enabled. Maybe the other side is not ready?\nMessage was: ${JSON.stringify(message)}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
console.debug('[WPMC ' + this.generateLogIdentifier() + '] Sent event', message)
|
||||||
this.messageTarget.postMessage(message, this.targetOrigin)
|
this.messageTarget.postMessage(message, this.targetOrigin)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract handleEvent(event: MessageEvent<RECEIVE>): void
|
/**
|
||||||
|
* Sets the handler method that processes messages with the given message type.
|
||||||
|
* If there is already a handler for the given message type then the handler will be overwritten.
|
||||||
|
*
|
||||||
|
* @param messageType The message type for which the handler should be called
|
||||||
|
* @param handler The handler that processes messages with the given message type.
|
||||||
|
*/
|
||||||
|
public setHandler<R extends RECEIVE_TYPE>(messageType: R, handler: Handler<MESSAGES, R>): void {
|
||||||
|
this.handlers[messageType] = handler as Handler<MESSAGES, RECEIVE_TYPE>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a unique identifier that helps to separate log messages in the console from different communicators.
|
||||||
|
* @return the identifier
|
||||||
|
*/
|
||||||
|
protected abstract generateLogIdentifier(): string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives the message events and calls the handler that is mapped to the correct type.
|
||||||
|
*
|
||||||
|
* @param event The received event
|
||||||
|
* @return {@code true} if the event was processed.
|
||||||
|
*/
|
||||||
|
protected handleEvent(event: MessageEvent<PostMessage<RECEIVE_TYPE>>): boolean | undefined {
|
||||||
|
const data = event.data
|
||||||
|
|
||||||
|
const handler = this.handlers[data.type]
|
||||||
|
if (!handler) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
console.debug('[WPMC ' + this.generateLogIdentifier() + '] Received event ', data)
|
||||||
|
handler(data as Extract<MESSAGES, PostMessage<RECEIVE_TYPE>>)
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue