fix: Move content into to frontend directory

Doing this BEFORE the merge prevents a lot of merge conflicts.

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2022-11-11 11:16:18 +01:00
parent 4e18ce38f3
commit 762a0a850e
No known key found for this signature in database
GPG key ID: B97799103358209B
1051 changed files with 0 additions and 35 deletions

View file

@ -0,0 +1,26 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useEffect, useRef } from 'react'
import type { RendererType } from '../../../render-page/window-post-message-communicator/rendering-message'
/**
* Execute the given reload callback if the given render type changes.
*
* @param rendererType The render type to watch
* @param effectCallback The callback that should be executed if the render type changes.
*/
export const useEffectOnRenderTypeChange = (rendererType: RendererType, effectCallback: () => void): void => {
const lastRendererType = useRef<RendererType>(rendererType)
useEffect(() => {
if (lastRendererType.current === rendererType) {
return
}
effectCallback()
lastRendererType.current = rendererType
}, [effectCallback, rendererType])
}

View file

@ -0,0 +1,59 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { RefObject } from 'react'
import { useCallback, useEffect, useMemo, useRef } from 'react'
import { Logger } from '../../../../utils/logger'
import { ORIGIN, useBaseUrl } from '../../../../hooks/common/use-base-url'
import { useEditorToRendererCommunicator } from '../../render-context/editor-to-renderer-communicator-context-provider'
const log = new Logger('IframeLoader')
/**
* Generates a callback for an iframe load handler, that enforces a given URL if frame navigates away.
*
* @param iFrameReference A reference to the iframe react dom element.
* @param onNavigateAway An optional callback that is executed when the iframe leaves the enforced URL.
*/
export const useForceRenderPageUrlOnIframeLoadCallback = (
iFrameReference: RefObject<HTMLIFrameElement>,
onNavigateAway: () => void
): (() => void) => {
const iframeCommunicator = useEditorToRendererCommunicator()
const rendererBaseUrl = useBaseUrl(ORIGIN.RENDERER)
const forcedUrl = useMemo(() => {
const renderUrl = new URL(rendererBaseUrl)
renderUrl.pathname += 'render'
renderUrl.searchParams.set('uuid', iframeCommunicator.getUuid())
return renderUrl.toString()
}, [iframeCommunicator, rendererBaseUrl])
const redirectionInProgress = useRef<boolean>(false)
const loadCallback = useCallback(() => {
const frame = iFrameReference.current
if (!frame) {
log.debug('No frame in reference')
return
}
if (redirectionInProgress.current) {
redirectionInProgress.current = false
log.debug('Redirect complete')
} else {
log.warn(`Navigated away from unknown URL. Forcing back to ${forcedUrl}`)
onNavigateAway?.()
redirectionInProgress.current = true
frame.src = forcedUrl
}
}, [iFrameReference, onNavigateAway, forcedUrl])
useEffect(() => {
loadCallback()
}, [loadCallback])
return loadCallback
}

View file

@ -0,0 +1,31 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
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'
import { useApplicationState } from '../../../../hooks/common/use-application-state'
/**
* Sends the current dark mode setting to the renderer.
*
* @param forcedDarkMode Overwrites the value from the global application states if set.
* @param rendererReady Defines if the target renderer is ready
*/
export const useSendDarkModeStatusToRenderer = (forcedDarkMode: boolean | undefined, rendererReady: boolean): void => {
const darkModePreference = useApplicationState((state) => state.darkMode.darkModePreference)
useSendToRenderer(
useMemo(
() => ({
type: CommunicationMessageType.SET_DARKMODE,
preference: darkModePreference
}),
[darkModePreference]
),
rendererReady
)
}

View file

@ -0,0 +1,28 @@
/*
* SPDX-FileCopyrightText: 2022 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 markdownContentLines The markdown content to send.
* @param rendererReady Defines if the target renderer is ready
*/
export const useSendMarkdownToRenderer = (markdownContentLines: string[], rendererReady: boolean): void => {
return useSendToRenderer(
useMemo(
() => ({
type: CommunicationMessageType.SET_MARKDOWN_CONTENT,
content: markdownContentLines
}),
[markdownContentLines]
),
rendererReady
)
}

View file

@ -0,0 +1,30 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useEffect, useRef } from 'react'
import type { ScrollState } from '../../synced-scroll/scroll-props'
import { CommunicationMessageType } from '../../../render-page/window-post-message-communicator/rendering-message'
import equal from 'fast-deep-equal'
import { useEditorToRendererCommunicator } from '../../render-context/editor-to-renderer-communicator-context-provider'
import { useApplicationState } from '../../../../hooks/common/use-application-state'
/**
* 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)
const rendererReady = useApplicationState((state) => state.rendererStatus.rendererReady)
useEffect(() => {
if (rendererReady && scrollState && !equal(scrollState, oldScrollState.current)) {
oldScrollState.current = scrollState
iframeCommunicator.sendMessageToOtherSide({ type: CommunicationMessageType.SET_SCROLL_STATE, scrollState })
}
}, [iframeCommunicator, rendererReady, scrollState])
}

View file

@ -0,0 +1,189 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React, { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { isTestMode } from '../../../utils/test-modes'
import type { RendererProps } from '../../render-page/markdown-document'
import type {
ExtensionEvent,
OnFirstHeadingChangeMessage,
OnHeightChangeMessage,
RendererType,
SetScrollStateMessage
} from '../../render-page/window-post-message-communicator/rendering-message'
import { CommunicationMessageType } from '../../render-page/window-post-message-communicator/rendering-message'
import { useEditorToRendererCommunicator } from '../render-context/editor-to-renderer-communicator-context-provider'
import { useForceRenderPageUrlOnIframeLoadCallback } from './hooks/use-force-render-page-url-on-iframe-load-callback'
import { useEditorReceiveHandler } from '../../render-page/window-post-message-communicator/hooks/use-editor-receive-handler'
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'
import { Logger } from '../../../utils/logger'
import { useEffectOnRenderTypeChange } from './hooks/use-effect-on-render-type-change'
import { cypressAttribute, cypressId } from '../../../utils/cypress-attribute'
import { ShowIf } from '../../common/show-if/show-if'
import { WaitSpinner } from '../../common/wait-spinner/wait-spinner'
import { useExtensionEventEmitter } from '../../markdown-renderer/hooks/use-extension-event-emitter'
export interface RenderIframeProps extends RendererProps {
rendererType: RendererType
forcedDarkMode?: boolean
frameClasses?: string
onRendererStatusChange?: undefined | ((rendererReady: boolean) => void)
adaptFrameHeightToContent?: boolean
}
const log = new Logger('RenderIframe')
/**
* Renders the iframe for the HTML-rendering of the markdown content.
* The iframe is enhanced by the {@link useEditorToRendererCommunicator iframe communicator} which is used for
* passing data from the parent frame into the iframe as well as receiving status messages and data from the iframe.
*
* @param markdownContentLines Array of lines of the markdown content
* @param onTaskCheckedChange Callback that is fired when a task-list item in the iframe is checked
* @param scrollState The current {@link ScrollState}
* @param onFirstHeadingChange Callback that is fired when the first heading of the note changes
* @param onScroll Callback that is fired when the user scrolls in the iframe
* @param onMakeScrollSource Callback that is fired when the renderer requests to be set as the current scroll source
* @param frameClasses CSS classes that should be applied to the iframe
* @param rendererType The {@link RendererType type} of the renderer to use.
* @param forcedDarkMode If set, the dark mode will be set to the given value. Otherwise, the dark mode won't be changed.
* @param adaptFrameHeightToContent If set, the iframe height will be adjusted to the content height
* @param onRendererStatusChange Callback that is fired when the renderer in the iframe is ready
*/
export const RenderIframe: React.FC<RenderIframeProps> = ({
markdownContentLines,
scrollState,
onFirstHeadingChange,
onScroll,
onMakeScrollSource,
frameClasses,
rendererType,
forcedDarkMode,
adaptFrameHeightToContent,
onRendererStatusChange
}) => {
const [rendererReady, setRendererReady] = useState<boolean>(false)
const frameReference = useRef<HTMLIFrameElement>(null)
const iframeCommunicator = useEditorToRendererCommunicator()
const resetRendererReady = useCallback(() => {
log.debug('Reset render status')
setRendererReady(false)
}, [])
const onIframeLoad = useForceRenderPageUrlOnIframeLoadCallback(frameReference, resetRendererReady)
const [frameHeight, setFrameHeight] = useState<number>(0)
useEffect(() => {
onRendererStatusChange?.(rendererReady)
}, [onRendererStatusChange, rendererReady])
useEffect(() => () => setRendererReady(false), [iframeCommunicator])
useEffect(() => {
if (!rendererReady) {
iframeCommunicator.unsetMessageTarget()
}
}, [iframeCommunicator, rendererReady])
useEffect(() => {
onRendererStatusChange?.(rendererReady)
}, [onRendererStatusChange, rendererReady])
useEditorReceiveHandler(
CommunicationMessageType.ON_FIRST_HEADING_CHANGE,
useCallback(
(values: OnFirstHeadingChangeMessage) => onFirstHeadingChange?.(values.firstHeading),
[onFirstHeadingChange]
)
)
useEditorReceiveHandler(
CommunicationMessageType.ENABLE_RENDERER_SCROLL_SOURCE,
useCallback(() => onMakeScrollSource?.(), [onMakeScrollSource])
)
const eventEmitter = useExtensionEventEmitter()
useEditorReceiveHandler(
CommunicationMessageType.EXTENSION_EVENT,
useMemo(() => {
return eventEmitter === undefined
? undefined
: (values: ExtensionEvent) => eventEmitter.emit(values.eventName, values.payload)
}, [eventEmitter])
)
useEditorReceiveHandler(
CommunicationMessageType.ON_HEIGHT_CHANGE,
useCallback(
(values: OnHeightChangeMessage) => {
if (adaptFrameHeightToContent) {
setFrameHeight?.(values.height)
}
},
[adaptFrameHeightToContent]
)
)
useEditorReceiveHandler(
CommunicationMessageType.RENDERER_READY,
useCallback(() => {
const frame = frameReference.current
if (!frame) {
log.error('Load triggered without frame in ref')
return
}
const otherWindow = frame.contentWindow
if (!otherWindow) {
log.error('Load triggered without content window')
return
}
iframeCommunicator.setMessageTarget(otherWindow)
iframeCommunicator.enableCommunication()
iframeCommunicator.sendMessageToOtherSide({
type: CommunicationMessageType.SET_BASE_CONFIGURATION,
baseConfiguration: {
baseUrl: window.location.toString(),
rendererType
}
})
setRendererReady(true)
}, [iframeCommunicator, rendererType])
)
useEffectOnRenderTypeChange(rendererType, onIframeLoad)
useSendDarkModeStatusToRenderer(forcedDarkMode, rendererReady)
useSendMarkdownToRenderer(markdownContentLines, rendererReady)
useSendScrollState(scrollState)
useEditorReceiveHandler(
CommunicationMessageType.SET_SCROLL_STATE,
useCallback((values: SetScrollStateMessage) => onScroll?.(values.scrollState), [onScroll])
)
return (
<Fragment>
<ShowIf condition={!rendererReady}>
<WaitSpinner />
</ShowIf>
<iframe
style={{ height: `${frameHeight}px` }}
{...cypressId('documentIframe')}
onLoad={onIframeLoad}
title='render'
{...(isTestMode ? {} : { sandbox: 'allow-downloads allow-same-origin allow-scripts allow-popups' })}
allowFullScreen={true}
ref={frameReference}
referrerPolicy={'no-referrer'}
className={`border-0 ${frameClasses ?? ''}`}
allow={'clipboard-write'}
{...cypressAttribute('renderer-ready', rendererReady ? 'true' : 'false')}
{...cypressAttribute('renderer-type', rendererType)}
/>
</Fragment>
)
}