mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-25 12:34:45 -04: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
|
@ -6,19 +6,23 @@
|
|||
|
||||
import React, { useCallback } from 'react'
|
||||
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(
|
||||
(event: React.MouseEvent<HTMLImageElement, MouseEvent>) => {
|
||||
const image = event.target as HTMLImageElement
|
||||
if (image.src === '') {
|
||||
return
|
||||
}
|
||||
iframeCommunicator.sendClickedImageUrl({
|
||||
src: image.src,
|
||||
alt: image.alt,
|
||||
title: image.title
|
||||
iframeCommunicator.sendMessageToOtherSide({
|
||||
type: CommunicationMessageType.IMAGE_CLICKED,
|
||||
details: {
|
||||
src: image.src,
|
||||
alt: image.alt,
|
||||
title: image.title
|
||||
}
|
||||
})
|
||||
},
|
||||
[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
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
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 { 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'
|
||||
import { countWords } from './word-counter'
|
||||
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 = () => {
|
||||
const [markdownContent, setMarkdownContent] = useState('')
|
||||
|
@ -25,60 +30,76 @@ export const IframeMarkdownRenderer: React.FC = () => {
|
|||
deprecatedSyntax: false
|
||||
})
|
||||
|
||||
const iframeCommunicator = useIFrameRendererToEditorCommunicator()
|
||||
const communicator = useRendererToEditorCommunicator()
|
||||
|
||||
const countWordsInRenderedDocument = useCallback(() => {
|
||||
const documentContainer = document.querySelector('.markdown-body')
|
||||
if (!documentContainer) {
|
||||
iframeCommunicator.sendWordCountCalculated(0)
|
||||
return
|
||||
}
|
||||
const wordCount = countWords(documentContainer)
|
||||
iframeCommunicator.sendWordCountCalculated(wordCount)
|
||||
}, [iframeCommunicator])
|
||||
communicator.sendMessageToOtherSide({
|
||||
type: CommunicationMessageType.ON_WORD_COUNT_CALCULATED,
|
||||
words: documentContainer ? countWords(documentContainer) : 0
|
||||
})
|
||||
}, [communicator])
|
||||
|
||||
useEffect(() => iframeCommunicator.onSetBaseConfiguration(setBaseConfiguration), [iframeCommunicator])
|
||||
useEffect(() => iframeCommunicator.onSetMarkdownContent(setMarkdownContent), [iframeCommunicator])
|
||||
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_BASE_CONFIGURATION, (values) =>
|
||||
setBaseConfiguration(values.baseConfiguration)
|
||||
)
|
||||
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(
|
||||
(lineInMarkdown: number, checked: boolean) => {
|
||||
iframeCommunicator.sendTaskCheckBoxChange(lineInMarkdown, checked)
|
||||
communicator.sendMessageToOtherSide({
|
||||
type: CommunicationMessageType.ON_TASK_CHECKBOX_CHANGE,
|
||||
checked,
|
||||
lineInMarkdown
|
||||
})
|
||||
},
|
||||
[iframeCommunicator]
|
||||
[communicator]
|
||||
)
|
||||
|
||||
const onFirstHeadingChange = useCallback(
|
||||
(firstHeading?: string) => {
|
||||
iframeCommunicator.sendFirstHeadingChanged(firstHeading)
|
||||
communicator.sendMessageToOtherSide({
|
||||
type: CommunicationMessageType.ON_FIRST_HEADING_CHANGE,
|
||||
firstHeading
|
||||
})
|
||||
},
|
||||
[iframeCommunicator]
|
||||
[communicator]
|
||||
)
|
||||
|
||||
const onMakeScrollSource = useCallback(() => {
|
||||
iframeCommunicator.sendSetScrollSourceToRenderer()
|
||||
}, [iframeCommunicator])
|
||||
communicator.sendMessageToOtherSide({
|
||||
type: CommunicationMessageType.SET_SCROLL_SOURCE_TO_RENDERER
|
||||
})
|
||||
}, [communicator])
|
||||
|
||||
const onScroll = useCallback(
|
||||
(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(
|
||||
(height: number) => {
|
||||
iframeCommunicator.sendHeightChange(height)
|
||||
communicator.sendMessageToOtherSide({
|
||||
type: CommunicationMessageType.ON_HEIGHT_CHANGE,
|
||||
height
|
||||
})
|
||||
},
|
||||
[iframeCommunicator]
|
||||
[communicator]
|
||||
)
|
||||
|
||||
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 { useApplyDarkMode } from '../../hooks/common/use-apply-dark-mode'
|
||||
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 = () => {
|
||||
useApplyDarkMode()
|
||||
|
||||
return (
|
||||
<IframeRendererToEditorCommunicatorContextProvider>
|
||||
<RendererToEditorCommunicatorContextProvider>
|
||||
<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
|
||||
*/
|
||||
import { ScrollState } from '../editor-page/synced-scroll/scroll-props'
|
||||
import { RendererFrontmatterInfo } from '../common/note-frontmatter/types'
|
||||
import { ScrollState } from '../../editor-page/synced-scroll/scroll-props'
|
||||
import { RendererFrontmatterInfo } from '../../common/note-frontmatter/types'
|
||||
|
||||
export enum RenderIframeMessageType {
|
||||
export enum CommunicationMessageType {
|
||||
SET_MARKDOWN_CONTENT = 'SET_MARKDOWN_CONTENT',
|
||||
RENDERER_READY = 'RENDERER_READY',
|
||||
SET_DARKMODE = 'SET_DARKMODE',
|
||||
|
@ -22,12 +22,12 @@ export enum RenderIframeMessageType {
|
|||
SET_FRONTMATTER_INFO = 'SET_FRONTMATTER_INFO'
|
||||
}
|
||||
|
||||
export interface RendererToEditorSimpleMessage {
|
||||
type: RenderIframeMessageType.RENDERER_READY | RenderIframeMessageType.SET_SCROLL_SOURCE_TO_RENDERER
|
||||
export interface NoPayloadMessage {
|
||||
type: CommunicationMessageType.RENDERER_READY | CommunicationMessageType.SET_SCROLL_SOURCE_TO_RENDERER
|
||||
}
|
||||
|
||||
export interface SetDarkModeMessage {
|
||||
type: RenderIframeMessageType.SET_DARKMODE
|
||||
type: CommunicationMessageType.SET_DARKMODE
|
||||
activated: boolean
|
||||
}
|
||||
|
||||
|
@ -38,72 +38,87 @@ export interface ImageDetails {
|
|||
}
|
||||
|
||||
export interface SetBaseUrlMessage {
|
||||
type: RenderIframeMessageType.SET_BASE_CONFIGURATION
|
||||
type: CommunicationMessageType.SET_BASE_CONFIGURATION
|
||||
baseConfiguration: BaseConfiguration
|
||||
}
|
||||
|
||||
export interface GetWordCountMessage {
|
||||
type: RenderIframeMessageType.GET_WORD_COUNT
|
||||
type: CommunicationMessageType.GET_WORD_COUNT
|
||||
}
|
||||
|
||||
export interface ImageClickedMessage {
|
||||
type: RenderIframeMessageType.IMAGE_CLICKED
|
||||
type: CommunicationMessageType.IMAGE_CLICKED
|
||||
details: ImageDetails
|
||||
}
|
||||
|
||||
export interface SetMarkdownContentMessage {
|
||||
type: RenderIframeMessageType.SET_MARKDOWN_CONTENT
|
||||
type: CommunicationMessageType.SET_MARKDOWN_CONTENT
|
||||
content: string
|
||||
}
|
||||
|
||||
export interface SetScrollStateMessage {
|
||||
type: RenderIframeMessageType.SET_SCROLL_STATE
|
||||
type: CommunicationMessageType.SET_SCROLL_STATE
|
||||
scrollState: ScrollState
|
||||
}
|
||||
|
||||
export interface OnTaskCheckboxChangeMessage {
|
||||
type: RenderIframeMessageType.ON_TASK_CHECKBOX_CHANGE
|
||||
type: CommunicationMessageType.ON_TASK_CHECKBOX_CHANGE
|
||||
lineInMarkdown: number
|
||||
checked: boolean
|
||||
}
|
||||
|
||||
export interface OnFirstHeadingChangeMessage {
|
||||
type: RenderIframeMessageType.ON_FIRST_HEADING_CHANGE
|
||||
type: CommunicationMessageType.ON_FIRST_HEADING_CHANGE
|
||||
firstHeading: string | undefined
|
||||
}
|
||||
|
||||
export interface SetFrontmatterInfoMessage {
|
||||
type: RenderIframeMessageType.SET_FRONTMATTER_INFO
|
||||
type: CommunicationMessageType.SET_FRONTMATTER_INFO
|
||||
frontmatterInfo: RendererFrontmatterInfo
|
||||
}
|
||||
|
||||
export interface OnHeightChangeMessage {
|
||||
type: RenderIframeMessageType.ON_HEIGHT_CHANGE
|
||||
type: CommunicationMessageType.ON_HEIGHT_CHANGE
|
||||
height: number
|
||||
}
|
||||
|
||||
export interface OnWordCountCalculatedMessage {
|
||||
type: RenderIframeMessageType.ON_WORD_COUNT_CALCULATED
|
||||
type: CommunicationMessageType.ON_WORD_COUNT_CALCULATED
|
||||
words: number
|
||||
}
|
||||
|
||||
export type EditorToRendererIframeMessage =
|
||||
| SetMarkdownContentMessage
|
||||
export type CommunicationMessages =
|
||||
| NoPayloadMessage
|
||||
| SetDarkModeMessage
|
||||
| SetScrollStateMessage
|
||||
| SetBaseUrlMessage
|
||||
| GetWordCountMessage
|
||||
| SetFrontmatterInfoMessage
|
||||
|
||||
export type RendererToEditorIframeMessage =
|
||||
| RendererToEditorSimpleMessage
|
||||
| OnFirstHeadingChangeMessage
|
||||
| OnTaskCheckboxChangeMessage
|
||||
| SetScrollStateMessage
|
||||
| ImageClickedMessage
|
||||
| SetMarkdownContentMessage
|
||||
| SetScrollStateMessage
|
||||
| OnTaskCheckboxChangeMessage
|
||||
| OnFirstHeadingChangeMessage
|
||||
| SetFrontmatterInfoMessage
|
||||
| OnHeightChangeMessage
|
||||
| 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 {
|
||||
DOCUMENT,
|
||||
INTRO,
|
|
@ -9,19 +9,39 @@
|
|||
*/
|
||||
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.
|
||||
*/
|
||||
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 targetOrigin?: string
|
||||
private communicationEnabled: boolean
|
||||
private handlers: HandlerMap<MESSAGES, RECEIVE_TYPE> = {}
|
||||
|
||||
constructor() {
|
||||
window.addEventListener('message', this.handleEvent.bind(this))
|
||||
this.communicationEnabled = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the message event listener from the {@link window}
|
||||
*/
|
||||
public unregisterEventListener(): void {
|
||||
window.removeEventListener('message', this.handleEvent.bind(this))
|
||||
}
|
||||
|
@ -53,7 +73,7 @@ export abstract class IframeCommunicator<SEND, RECEIVE> {
|
|||
* Enables the message communication.
|
||||
* Should be called as soon as the other sides is ready to receive messages.
|
||||
*/
|
||||
protected enableCommunication(): void {
|
||||
public enableCommunication(): void {
|
||||
this.communicationEnabled = true
|
||||
}
|
||||
|
||||
|
@ -62,7 +82,7 @@ export abstract class IframeCommunicator<SEND, RECEIVE> {
|
|||
*
|
||||
* @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) {
|
||||
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)}`
|
||||
)
|
||||
}
|
||||
console.debug('[WPMC ' + this.generateLogIdentifier() + '] Sent event', message)
|
||||
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…
Add table
Add a link
Reference in a new issue