refactor(frameCommunication): align Editor- and Renderer Handler

Signed-off-by: Philip Molares <philip.molares@udo.edu>
This commit is contained in:
Philip Molares
2026-07-31 23:17:15 +02:00
committed by Philip Molares
parent 5b7fa9aaba
commit 17c7ca48d7
13 changed files with 73 additions and 50 deletions
@@ -27,9 +27,7 @@ export const useForceRenderPageUrlOnIframeLoadCallback = (
const forcedUrl = useMemo(() => {
const renderUrl = new URL(rendererBaseUrl)
renderUrl.pathname += 'render'
if (iframeCommunicator !== undefined) {
renderUrl.searchParams.set('uuid', iframeCommunicator.getUuid())
}
renderUrl.searchParams.set('uuid', iframeCommunicator.getUuid())
return renderUrl.toString()
}, [iframeCommunicator, rendererBaseUrl])
const redirectionInProgress = useRef<boolean>(false)
@@ -16,7 +16,7 @@ export const useSendFragmentToRenderer = (rendererReady: boolean): void => {
const iframeCommunicator = useEditorToRendererCommunicator()
useEffect(() => {
if (!rendererReady || !iframeCommunicator) {
if (!rendererReady) {
return
}
@@ -12,12 +12,7 @@ import type { ScrollProps } from '../../editor-page/synced-scroll/scroll-props'
import { useExtensionEventEmitter } from '../../markdown-renderer/hooks/use-extension-event-emitter'
import type { CommonMarkdownRendererProps } from '../../render-page/renderers/common-markdown-renderer-props'
import { useEditorReceiveHandler } from '../../render-page/window-post-message-communicator/hooks/use-editor-receive-handler'
import type {
ExtensionEvent,
OnHeightChangeMessage,
RendererType,
SetScrollStateMessage
} from '../../render-page/window-post-message-communicator/rendering-message'
import type { RendererType } from '../../render-page/window-post-message-communicator/rendering-message'
import { CommunicationMessageType } from '../../render-page/window-post-message-communicator/rendering-message'
import { WaitSpinner } from '../wait-spinner/wait-spinner'
import { useEffectOnRenderTypeChange } from './hooks/use-effect-on-render-type-change'
@@ -67,7 +62,7 @@ export const RendererIframe: React.FC<RendererIframeProps> = ({
const [rendererReady, setRendererReady] = useState<boolean>(false)
const frameReference = useRef<HTMLIFrameElement>(null)
const iframeCommunicator = useEditorToRendererCommunicator()
const log = useMemo(() => new Logger(`RendererIframe[${iframeCommunicator?.getUuid()}]`), [iframeCommunicator])
const log = useMemo(() => new Logger(`RendererIframe[${iframeCommunicator.getUuid()}]`), [iframeCommunicator])
const resetRendererReady = useCallback(() => {
log.debug('Reset render status')
@@ -98,7 +93,7 @@ export const RendererIframe: React.FC<RendererIframeProps> = ({
useEffect(() => {
if (!rendererReady) {
iframeCommunicator?.unsetMessageTarget()
iframeCommunicator.unsetMessageTarget()
}
}, [iframeCommunicator, rendererReady])
@@ -111,22 +106,19 @@ export const RendererIframe: React.FC<RendererIframeProps> = ({
useEditorReceiveHandler(
CommunicationMessageType.EXTENSION_EVENT,
useMemo(() => {
return eventEmitter === undefined
? null
: (values: ExtensionEvent) => eventEmitter.emit(values.eventName, values.payload)
return eventEmitter === undefined ? null : ({ eventName, payload }) => eventEmitter.emit(eventName, payload)
}, [eventEmitter])
)
useEditorReceiveHandler(
CommunicationMessageType.ON_HEIGHT_CHANGE,
useCallback(
(values: OnHeightChangeMessage) => {
useMemo(() => {
return ({ height }) => {
if (adaptFrameHeightToContent) {
setFrameHeight?.(values.height)
setFrameHeight?.(height)
}
},
[adaptFrameHeightToContent]
)
}
}, [adaptFrameHeightToContent])
)
useEditorReceiveHandler(
@@ -142,9 +134,9 @@ export const RendererIframe: React.FC<RendererIframeProps> = ({
log.error('Load triggered without content window')
return
}
iframeCommunicator?.setMessageTarget(otherWindow)
iframeCommunicator?.enableCommunication()
iframeCommunicator?.sendMessageToOtherSide({
iframeCommunicator.setMessageTarget(otherWindow)
iframeCommunicator.enableCommunication()
iframeCommunicator.sendMessageToOtherSide({
type: CommunicationMessageType.SET_BASE_CONFIGURATION,
baseConfiguration: {
baseUrl: window.location.toString(),
@@ -163,12 +155,20 @@ export const RendererIframe: React.FC<RendererIframeProps> = ({
useSendScrollState(scrollState ?? null, rendererReady)
useEditorReceiveHandler(
CommunicationMessageType.SET_SCROLL_STATE,
useCallback((values: SetScrollStateMessage) => onScroll?.(values.scrollState), [onScroll])
useMemo(() => {
return ({ scrollState }) => onScroll?.(scrollState)
}, [onScroll])
)
useEditorReceiveHandler(
CommunicationMessageType.ENABLE_RENDERER_SCROLL_SOURCE,
useCallback(() => onMakeScrollSource?.(), [onMakeScrollSource])
)
useEditorReceiveHandler(
CommunicationMessageType.SET_URL_HASH,
useMemo(() => {
return ({ hash }) => (window.location.hash = hash)
}, [])
)
const frameClassNames = useMemo(
() => concatCssClasses({ 'd-none': !rendererReady && showWaitSpinner }, 'border-0', styles.frame, frameClasses),
@@ -10,9 +10,7 @@ import { EditorToRendererCommunicator } from '../../render-page/window-post-mess
import type { PropsWithChildren } from 'react'
import React, { createContext, useContext, useEffect, useMemo } from 'react'
import { v4 as uuid } from 'uuid'
import { Logger } from '../../../utils/logger'
const logger = new Logger('EditorToRendererCommunicator')
const EditorToRendererCommunicatorContext = createContext<EditorToRendererCommunicator | undefined>(undefined)
/**
@@ -21,11 +19,10 @@ const EditorToRendererCommunicatorContext = createContext<EditorToRendererCommun
* @return the received communicator
* @throws {Error} if no communicator was received
*/
export const useEditorToRendererCommunicator = (): EditorToRendererCommunicator | undefined => {
export const useEditorToRendererCommunicator = (): EditorToRendererCommunicator => {
const communicatorFromContext = useContext(EditorToRendererCommunicatorContext)
if (!communicatorFromContext) {
logger.error('No editor-to-renderer-iframe-communicator received. Did you forget to use the provider component?')
return undefined
throw new Error('No editor-to-renderer-iframe-communicator received. Did you forget to use the provider component?')
}
return communicatorFromContext
}
@@ -20,7 +20,7 @@ const RendererToEditorCommunicatorContext = createContext<RendererToEditorCommun
* @return the received communicator
* @throws {Error} if no communicator was received
*/
export const useRendererToEditorCommunicator: () => RendererToEditorCommunicator = () => {
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?')
@@ -35,7 +35,7 @@ export const NoteInfoLineWordCount: React.FC<NoteInfoLineWordCountProps> = ({ vi
const rendererReady = useApplicationState((state) => state.rendererStatus.rendererReady)
useEffect(() => {
if (rendererReady && visible) {
editorToRendererCommunicator?.sendMessageToOtherSide({ type: CommunicationMessageType.GET_WORD_COUNT })
editorToRendererCommunicator.sendMessageToOtherSide({ type: CommunicationMessageType.GET_WORD_COUNT })
}
}, [editorToRendererCommunicator, rendererReady, visible])
@@ -28,13 +28,13 @@ export const usePrintIframe = (): (() => void) => {
if (!iframe || !iframe.contentWindow) {
return
}
iframeCommunicator?.sendMessageToOtherSide({
iframeCommunicator.sendMessageToOtherSide({
type: CommunicationMessageType.SET_PRINT_MODE,
printMode: true
})
setTimeout(() => {
iframe.contentWindow?.print()
iframeCommunicator?.sendMessageToOtherSide({
iframeCommunicator.sendMessageToOtherSide({
type: CommunicationMessageType.SET_PRINT_MODE,
printMode: false
})
@@ -5,6 +5,8 @@
*/
import type { AllHTMLAttributes } from 'react'
import React, { useCallback } from 'react'
import { useRendererToEditorCommunicator } from '../../../editor-page/render-context/renderer-to-editor-communicator-context-provider'
import { CommunicationMessageType } from '../../../render-page/window-post-message-communicator/rendering-message'
export interface JumpAnchorProps extends AllHTMLAttributes<HTMLAnchorElement> {
jumpTargetId: string
@@ -18,8 +20,13 @@ export interface JumpAnchorProps extends AllHTMLAttributes<HTMLAnchorElement> {
* @param props Additional props directly given to the link
*/
export const JumpAnchor: React.FC<JumpAnchorProps> = ({ jumpTargetId, children, ...props }) => {
const iframeCommunicator = useRendererToEditorCommunicator()
const jumpToTargetId = useCallback(
(event: React.MouseEvent<HTMLElement, MouseEvent>): void => {
iframeCommunicator.sendMessageToOtherSide({
type: CommunicationMessageType.SET_URL_HASH,
hash: jumpTargetId
})
const intoViewElement = document.getElementById(jumpTargetId)
const scrollElement = document.querySelector('[data-scroll-element]')
if (!intoViewElement || !scrollElement) {
@@ -30,7 +37,7 @@ export const JumpAnchor: React.FC<JumpAnchorProps> = ({ jumpTargetId, children,
scrollElement.scrollTo({ behavior: 'smooth', top: intoViewElement.offsetTop })
event.preventDefault()
},
[jumpTargetId]
[iframeCommunicator, jumpTargetId]
)
return (
@@ -11,8 +11,12 @@ import { DocumentMarkdownRenderer } from './renderers/document/document-markdown
import { SimpleMarkdownRenderer } from './renderers/simple/simple-markdown-renderer'
import { SlideshowMarkdownRenderer } from './renderers/slideshow/slideshow-markdown-renderer'
import { useRendererReceiveHandler } from './window-post-message-communicator/hooks/use-renderer-receive-handler'
import type { BaseConfiguration } from './window-post-message-communicator/rendering-message'
import { CommunicationMessageType, RendererType } from './window-post-message-communicator/rendering-message'
import type {
BaseConfiguration} from './window-post-message-communicator/rendering-message';
import {
CommunicationMessageType,
RendererType
} from './window-post-message-communicator/rendering-message'
import { countWords } from './word-counter'
import type { RevealOptions } from 'reveal.js'
import { EventEmitter2 } from 'eventemitter2'
@@ -8,22 +8,28 @@ import type { CommunicationMessages, RendererToEditorMessageType } from '../rend
import type { Handler } from '../window-post-message-communicator'
import { useEffect } from 'react'
type EditorMessageHandler<MESSAGE_TYPE extends RendererToEditorMessageType> = Handler<
CommunicationMessages,
MESSAGE_TYPE
> | null
/**
* 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> | null
export const useEditorReceiveHandler = <MESSAGE_TYPE extends RendererToEditorMessageType>(
messageType: MESSAGE_TYPE,
handler: EditorMessageHandler<MESSAGE_TYPE>,
): void => {
const editorToRendererCommunicator = useEditorToRendererCommunicator()
useEffect(() => {
if (!handler) {
return
}
editorToRendererCommunicator?.on(messageType, handler)
return () => editorToRendererCommunicator?.off(messageType, handler)
editorToRendererCommunicator.on(messageType, handler)
return () => editorToRendererCommunicator.off(messageType, handler)
}, [editorToRendererCommunicator, handler, messageType])
}
@@ -8,7 +8,7 @@ import type { CommunicationMessages, EditorToRendererMessageType } from '../rend
import type { Handler } from '../window-post-message-communicator'
import { useEffect } from 'react'
export type CommunicationMessageHandler<MESSAGE_TYPE extends EditorToRendererMessageType> = Handler<
type RendererMessageHandler<MESSAGE_TYPE extends EditorToRendererMessageType> = Handler<
CommunicationMessages,
MESSAGE_TYPE
>
@@ -21,11 +21,14 @@ export type CommunicationMessageHandler<MESSAGE_TYPE extends EditorToRendererMes
*/
export const useRendererReceiveHandler = <MESSAGE_TYPE extends EditorToRendererMessageType>(
messageType: MESSAGE_TYPE,
handler: CommunicationMessageHandler<MESSAGE_TYPE>
handler: RendererMessageHandler<MESSAGE_TYPE>,
): void => {
const editorToRendererCommunicator = useRendererToEditorCommunicator()
const rendererToEditorCommunicator = useRendererToEditorCommunicator()
useEffect(() => {
editorToRendererCommunicator.on(messageType, handler)
return () => editorToRendererCommunicator.off(messageType, handler)
}, [editorToRendererCommunicator, handler, messageType])
if (!handler) {
return
}
rendererToEditorCommunicator.on(messageType, handler)
return () => rendererToEditorCommunicator.off(messageType, handler)
}, [rendererToEditorCommunicator, handler, messageType])
}
@@ -22,7 +22,7 @@ export const useSendToRenderer = (
useEffect(() => {
if (message && rendererReady) {
iframeCommunicator?.sendMessageToOtherSide(message)
iframeCommunicator.sendMessageToOtherSide(message)
}
}, [iframeCommunicator, message, rendererReady])
}
@@ -22,7 +22,8 @@ export enum CommunicationMessageType {
IMAGE_UPLOAD = 'IMAGE_UPLOAD',
EXTENSION_EVENT = 'EXTENSION_EVENT',
SET_PRINT_MODE = 'SET_PRINT_MODE',
SCROLL_TO_ELEMENT = 'SCROLL_TO_ELEMENT'
SCROLL_TO_ELEMENT = 'SCROLL_TO_ELEMENT',
SET_URL_HASH = 'SET_URL_HASH'
}
export interface NoPayloadMessage<TYPE extends CommunicationMessageType> {
@@ -99,6 +100,11 @@ export interface ScrollToElementMessage {
elementId: string
}
export interface SetUrlHashMessage {
type: CommunicationMessageType.SET_URL_HASH
hash: string
}
export type CommunicationMessages =
| NoPayloadMessage<CommunicationMessageType.RENDERER_READY>
| NoPayloadMessage<CommunicationMessageType.ENABLE_RENDERER_SCROLL_SOURCE>
@@ -115,6 +121,7 @@ export type CommunicationMessages =
| ExtensionEvent
| SetPrintModeConfigurationMessage
| ScrollToElementMessage
| SetUrlHashMessage
export type EditorToRendererMessageType =
| CommunicationMessageType.SET_MARKDOWN_CONTENT
@@ -135,6 +142,7 @@ export type RendererToEditorMessageType =
| CommunicationMessageType.ON_WORD_COUNT_CALCULATED
| CommunicationMessageType.IMAGE_UPLOAD
| CommunicationMessageType.EXTENSION_EVENT
| CommunicationMessageType.SET_URL_HASH
export enum RendererType {
DOCUMENT = 'document',