diff --git a/frontend/src/components/common/renderer-iframe/hooks/use-force-render-page-url-on-iframe-load-callback.ts b/frontend/src/components/common/renderer-iframe/hooks/use-force-render-page-url-on-iframe-load-callback.ts index 87d349b9a..b87b99d53 100644 --- a/frontend/src/components/common/renderer-iframe/hooks/use-force-render-page-url-on-iframe-load-callback.ts +++ b/frontend/src/components/common/renderer-iframe/hooks/use-force-render-page-url-on-iframe-load-callback.ts @@ -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(false) diff --git a/frontend/src/components/common/renderer-iframe/hooks/use-send-fragment-to-renderer.ts b/frontend/src/components/common/renderer-iframe/hooks/use-send-fragment-to-renderer.ts index 444d9f4af..d9a698710 100644 --- a/frontend/src/components/common/renderer-iframe/hooks/use-send-fragment-to-renderer.ts +++ b/frontend/src/components/common/renderer-iframe/hooks/use-send-fragment-to-renderer.ts @@ -16,7 +16,7 @@ export const useSendFragmentToRenderer = (rendererReady: boolean): void => { const iframeCommunicator = useEditorToRendererCommunicator() useEffect(() => { - if (!rendererReady || !iframeCommunicator) { + if (!rendererReady) { return } diff --git a/frontend/src/components/common/renderer-iframe/renderer-iframe.tsx b/frontend/src/components/common/renderer-iframe/renderer-iframe.tsx index 054f68b6f..f70e1ef1f 100644 --- a/frontend/src/components/common/renderer-iframe/renderer-iframe.tsx +++ b/frontend/src/components/common/renderer-iframe/renderer-iframe.tsx @@ -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 = ({ const [rendererReady, setRendererReady] = useState(false) const frameReference = useRef(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 = ({ useEffect(() => { if (!rendererReady) { - iframeCommunicator?.unsetMessageTarget() + iframeCommunicator.unsetMessageTarget() } }, [iframeCommunicator, rendererReady]) @@ -111,22 +106,19 @@ export const RendererIframe: React.FC = ({ 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 = ({ 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 = ({ 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), diff --git a/frontend/src/components/editor-page/render-context/editor-to-renderer-communicator-context-provider.tsx b/frontend/src/components/editor-page/render-context/editor-to-renderer-communicator-context-provider.tsx index 39ca241ba..479e85714 100644 --- a/frontend/src/components/editor-page/render-context/editor-to-renderer-communicator-context-provider.tsx +++ b/frontend/src/components/editor-page/render-context/editor-to-renderer-communicator-context-provider.tsx @@ -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(undefined) /** @@ -21,11 +19,10 @@ const EditorToRendererCommunicatorContext = createContext { +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 } diff --git a/frontend/src/components/editor-page/render-context/renderer-to-editor-communicator-context-provider.tsx b/frontend/src/components/editor-page/render-context/renderer-to-editor-communicator-context-provider.tsx index c56f2d76f..484a9a6ec 100644 --- a/frontend/src/components/editor-page/render-context/renderer-to-editor-communicator-context-provider.tsx +++ b/frontend/src/components/editor-page/render-context/renderer-to-editor-communicator-context-provider.tsx @@ -20,7 +20,7 @@ const RendererToEditorCommunicatorContext = createContext 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?') diff --git a/frontend/src/components/editor-page/sidebar/specific-sidebar-entries/note-info-sidebar-menu/note-info-line/note-info-line-word-count.tsx b/frontend/src/components/editor-page/sidebar/specific-sidebar-entries/note-info-sidebar-menu/note-info-line/note-info-line-word-count.tsx index c2bac2af8..08cc29f67 100644 --- a/frontend/src/components/editor-page/sidebar/specific-sidebar-entries/note-info-sidebar-menu/note-info-line/note-info-line-word-count.tsx +++ b/frontend/src/components/editor-page/sidebar/specific-sidebar-entries/note-info-sidebar-menu/note-info-line/note-info-line-word-count.tsx @@ -35,7 +35,7 @@ export const NoteInfoLineWordCount: React.FC = ({ 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]) diff --git a/frontend/src/components/editor-page/utils/print-iframe.ts b/frontend/src/components/editor-page/utils/print-iframe.ts index 1bbffd5fa..5d1f27d70 100644 --- a/frontend/src/components/editor-page/utils/print-iframe.ts +++ b/frontend/src/components/editor-page/utils/print-iframe.ts @@ -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 }) diff --git a/frontend/src/components/markdown-renderer/extensions/link-replacer/jump-anchor.tsx b/frontend/src/components/markdown-renderer/extensions/link-replacer/jump-anchor.tsx index a070311c7..7d393f89f 100644 --- a/frontend/src/components/markdown-renderer/extensions/link-replacer/jump-anchor.tsx +++ b/frontend/src/components/markdown-renderer/extensions/link-replacer/jump-anchor.tsx @@ -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 { jumpTargetId: string @@ -18,8 +20,13 @@ export interface JumpAnchorProps extends AllHTMLAttributes { * @param props Additional props directly given to the link */ export const JumpAnchor: React.FC = ({ jumpTargetId, children, ...props }) => { + const iframeCommunicator = useRendererToEditorCommunicator() const jumpToTargetId = useCallback( (event: React.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 = ({ jumpTargetId, children, scrollElement.scrollTo({ behavior: 'smooth', top: intoViewElement.offsetTop }) event.preventDefault() }, - [jumpTargetId] + [iframeCommunicator, jumpTargetId] ) return ( diff --git a/frontend/src/components/render-page/render-page-content.tsx b/frontend/src/components/render-page/render-page-content.tsx index 69a69a154..7259eeb04 100644 --- a/frontend/src/components/render-page/render-page-content.tsx +++ b/frontend/src/components/render-page/render-page-content.tsx @@ -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' diff --git a/frontend/src/components/render-page/window-post-message-communicator/hooks/use-editor-receive-handler.ts b/frontend/src/components/render-page/window-post-message-communicator/hooks/use-editor-receive-handler.ts index 0a5837332..f77d5fe73 100644 --- a/frontend/src/components/render-page/window-post-message-communicator/hooks/use-editor-receive-handler.ts +++ b/frontend/src/components/render-page/window-post-message-communicator/hooks/use-editor-receive-handler.ts @@ -8,22 +8,28 @@ import type { CommunicationMessages, RendererToEditorMessageType } from '../rend import type { Handler } from '../window-post-message-communicator' import { useEffect } from 'react' +type EditorMessageHandler = 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 = ( - messageType: R, - handler: Handler | null +export const useEditorReceiveHandler = ( + messageType: MESSAGE_TYPE, + handler: EditorMessageHandler, ): 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]) } diff --git a/frontend/src/components/render-page/window-post-message-communicator/hooks/use-renderer-receive-handler.ts b/frontend/src/components/render-page/window-post-message-communicator/hooks/use-renderer-receive-handler.ts index 709a2622d..35ebf5546 100644 --- a/frontend/src/components/render-page/window-post-message-communicator/hooks/use-renderer-receive-handler.ts +++ b/frontend/src/components/render-page/window-post-message-communicator/hooks/use-renderer-receive-handler.ts @@ -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 = Handler< +type RendererMessageHandler = Handler< CommunicationMessages, MESSAGE_TYPE > @@ -21,11 +21,14 @@ export type CommunicationMessageHandler( messageType: MESSAGE_TYPE, - handler: CommunicationMessageHandler + handler: RendererMessageHandler, ): 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]) } diff --git a/frontend/src/components/render-page/window-post-message-communicator/hooks/use-send-to-renderer.ts b/frontend/src/components/render-page/window-post-message-communicator/hooks/use-send-to-renderer.ts index cb8785e91..1460cbee9 100644 --- a/frontend/src/components/render-page/window-post-message-communicator/hooks/use-send-to-renderer.ts +++ b/frontend/src/components/render-page/window-post-message-communicator/hooks/use-send-to-renderer.ts @@ -22,7 +22,7 @@ export const useSendToRenderer = ( useEffect(() => { if (message && rendererReady) { - iframeCommunicator?.sendMessageToOtherSide(message) + iframeCommunicator.sendMessageToOtherSide(message) } }, [iframeCommunicator, message, rendererReady]) } diff --git a/frontend/src/components/render-page/window-post-message-communicator/rendering-message.ts b/frontend/src/components/render-page/window-post-message-communicator/rendering-message.ts index 3f5945c2d..e5269c421 100644 --- a/frontend/src/components/render-page/window-post-message-communicator/rendering-message.ts +++ b/frontend/src/components/render-page/window-post-message-communicator/rendering-message.ts @@ -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 { @@ -99,6 +100,11 @@ export interface ScrollToElementMessage { elementId: string } +export interface SetUrlHashMessage { + type: CommunicationMessageType.SET_URL_HASH + hash: string +} + export type CommunicationMessages = | NoPayloadMessage | NoPayloadMessage @@ -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',