feat(renderer): Use callback instead of redux for renderer ready

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2022-10-06 22:48:36 +02:00
parent 285daeef8b
commit 2817740c94
12 changed files with 82 additions and 118 deletions

View file

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
* *
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
@ -11,6 +11,7 @@ import { DocumentInfobar } from './document-infobar'
import { RenderIframe } from '../editor-page/renderer-pane/render-iframe' import { RenderIframe } from '../editor-page/renderer-pane/render-iframe'
import { RendererType } from '../render-page/window-post-message-communicator/rendering-message' import { RendererType } from '../render-page/window-post-message-communicator/rendering-message'
import { useTrimmedNoteMarkdownContentWithoutFrontmatter } from '../../hooks/common/use-trimmed-note-markdown-content-without-frontmatter' import { useTrimmedNoteMarkdownContentWithoutFrontmatter } from '../../hooks/common/use-trimmed-note-markdown-content-without-frontmatter'
import { setRendererStatus } from '../../redux/renderer-status/methods'
/** /**
* Renders the read-only version of a note with a header bar that contains information about the note. * Renders the read-only version of a note with a header bar that contains information about the note.
@ -29,6 +30,7 @@ export const DocumentReadOnlyPageContent: React.FC = () => {
markdownContentLines={markdownContentLines} markdownContentLines={markdownContentLines}
onFirstHeadingChange={updateNoteTitleByFirstHeading} onFirstHeadingChange={updateNoteTitleByFirstHeading}
rendererType={RendererType.DOCUMENT} rendererType={RendererType.DOCUMENT}
onRendererStatusChange={setRendererStatus}
/> />
</Fragment> </Fragment>
) )

View file

@ -5,7 +5,7 @@
*/ */
import type { PropsWithChildren } from 'react' import type { PropsWithChildren } from 'react'
import React, { useCallback, useState } from 'react' import React, { useCallback, useEffect, 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 { NoteInfoLine } from './note-info-line' import { NoteInfoLine } from './note-info-line'
@ -14,8 +14,8 @@ import { useEditorToRendererCommunicator } from '../../render-context/editor-to-
import type { OnWordCountCalculatedMessage } from '../../../render-page/window-post-message-communicator/rendering-message' import type { OnWordCountCalculatedMessage } from '../../../render-page/window-post-message-communicator/rendering-message'
import { CommunicationMessageType } from '../../../render-page/window-post-message-communicator/rendering-message' import { CommunicationMessageType } from '../../../render-page/window-post-message-communicator/rendering-message'
import { useEditorReceiveHandler } from '../../../render-page/window-post-message-communicator/hooks/use-editor-receive-handler' 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'
import { cypressId } from '../../../../utils/cypress-attribute' import { cypressId } from '../../../../utils/cypress-attribute'
import { useApplicationState } from '../../../../hooks/common/use-application-state'
/** /**
* 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
@ -31,11 +31,12 @@ export const NoteInfoLineWordCount: React.FC<PropsWithChildren<unknown>> = () =>
useCallback((values: OnWordCountCalculatedMessage) => setWordCount(values.words), [setWordCount]) useCallback((values: OnWordCountCalculatedMessage) => setWordCount(values.words), [setWordCount])
) )
useEffectOnRendererReady( const rendererReady = useApplicationState((state) => state.rendererStatus.rendererReady)
useCallback(() => { useEffect(() => {
if (rendererReady) {
editorToRendererCommunicator.sendMessageToOtherSide({ type: CommunicationMessageType.GET_WORD_COUNT }) editorToRendererCommunicator.sendMessageToOtherSide({ type: CommunicationMessageType.GET_WORD_COUNT })
}, [editorToRendererCommunicator]) }
) }, [editorToRendererCommunicator, rendererReady])
return ( return (
<NoteInfoLine icon={'align-left'} size={'2x'}> <NoteInfoLine icon={'align-left'} size={'2x'}>

View file

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
* *
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
@ -14,6 +14,7 @@ import { RendererType } from '../../render-page/window-post-message-communicator
import { useSetCheckboxInEditor } from './hooks/use-set-checkbox-in-editor' import { useSetCheckboxInEditor } from './hooks/use-set-checkbox-in-editor'
import { useOnScrollWithLineOffset } from './hooks/use-on-scroll-with-line-offset' import { useOnScrollWithLineOffset } from './hooks/use-on-scroll-with-line-offset'
import { useScrollStateWithoutLineOffset } from './hooks/use-scroll-state-without-line-offset' import { useScrollStateWithoutLineOffset } from './hooks/use-scroll-state-without-line-offset'
import { setRendererStatus } from '../../../redux/renderer-status/methods'
export type EditorDocumentRendererProps = Omit< export type EditorDocumentRendererProps = Omit<
RenderIframeProps, RenderIframeProps,
@ -42,6 +43,7 @@ export const EditorDocumentRenderer: React.FC<EditorDocumentRendererProps> = ({
onTaskCheckedChange={setCheckboxInEditor} onTaskCheckedChange={setCheckboxInEditor}
rendererType={noteType === NoteType.SLIDE ? RendererType.SLIDESHOW : RendererType.DOCUMENT} rendererType={noteType === NoteType.SLIDE ? RendererType.SLIDESHOW : RendererType.DOCUMENT}
markdownContentLines={trimmedContentLines} markdownContentLines={trimmedContentLines}
onRendererStatusChange={setRendererStatus}
/> />
) )
} }

View file

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
* *
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
@ -13,8 +13,9 @@ import { useSendToRenderer } from '../../../render-page/window-post-message-comm
* Sends the current dark mode setting to the renderer. * Sends the current dark mode setting to the renderer.
* *
* @param forcedDarkMode Overwrites the value from the global application states if set. * @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): void => { export const useSendDarkModeStatusToRenderer = (forcedDarkMode: boolean | undefined, rendererReady: boolean): void => {
const savedDarkMode = useIsDarkModeActivated() const savedDarkMode = useIsDarkModeActivated()
useSendToRenderer( useSendToRenderer(
@ -24,6 +25,7 @@ export const useSendDarkModeStatusToRenderer = (forcedDarkMode?: boolean): void
activated: forcedDarkMode ?? savedDarkMode activated: forcedDarkMode ?? savedDarkMode
}), }),
[forcedDarkMode, savedDarkMode] [forcedDarkMode, savedDarkMode]
) ),
rendererReady
) )
} }

View file

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
* *
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
@ -12,8 +12,9 @@ import { CommunicationMessageType } from '../../../render-page/window-post-messa
* Sends the given markdown content to the renderer. * Sends the given markdown content to the renderer.
* *
* @param markdownContentLines The markdown content to send. * @param markdownContentLines The markdown content to send.
* @param rendererReady Defines if the target renderer is ready
*/ */
export const useSendMarkdownToRenderer = (markdownContentLines: string[]): void => { export const useSendMarkdownToRenderer = (markdownContentLines: string[], rendererReady: boolean): void => {
return useSendToRenderer( return useSendToRenderer(
useMemo( useMemo(
() => ({ () => ({
@ -21,6 +22,7 @@ export const useSendMarkdownToRenderer = (markdownContentLines: string[]): void
content: markdownContentLines content: markdownContentLines
}), }),
[markdownContentLines] [markdownContentLines]
) ),
rendererReady
) )
} }

View file

@ -4,12 +4,12 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { useCallback, useRef } from 'react' import { useEffect, useRef } from 'react'
import type { ScrollState } from '../../synced-scroll/scroll-props' import type { ScrollState } from '../../synced-scroll/scroll-props'
import { CommunicationMessageType } from '../../../render-page/window-post-message-communicator/rendering-message' 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 equal from 'fast-deep-equal'
import { useEditorToRendererCommunicator } from '../../render-context/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'
/** /**
* Sends the given {@link ScrollState scroll state} to the renderer if the content changed. * Sends the given {@link ScrollState scroll state} to the renderer if the content changed.
@ -19,13 +19,12 @@ import { useEditorToRendererCommunicator } from '../../render-context/editor-to-
export const useSendScrollState = (scrollState: ScrollState | undefined): void => { export const useSendScrollState = (scrollState: ScrollState | undefined): void => {
const iframeCommunicator = useEditorToRendererCommunicator() const iframeCommunicator = useEditorToRendererCommunicator()
const oldScrollState = useRef<ScrollState | undefined>(undefined) const oldScrollState = useRef<ScrollState | undefined>(undefined)
const rendererReady = useApplicationState((state) => state.rendererStatus.rendererReady)
useEffectOnRendererReady( useEffect(() => {
useCallback(() => { if (rendererReady && scrollState && !equal(scrollState, oldScrollState.current)) {
if (scrollState && !equal(scrollState, oldScrollState.current)) { oldScrollState.current = scrollState
oldScrollState.current = scrollState iframeCommunicator.sendMessageToOtherSide({ type: CommunicationMessageType.SET_SCROLL_STATE, scrollState })
iframeCommunicator.sendMessageToOtherSide({ type: CommunicationMessageType.SET_SCROLL_STATE, scrollState }) }
} }, [iframeCommunicator, rendererReady, scrollState])
}, [iframeCommunicator, scrollState])
)
} }

View file

@ -17,9 +17,7 @@ import { CommunicationMessageType } from '../../render-page/window-post-message-
import { useEditorToRendererCommunicator } from '../render-context/editor-to-renderer-communicator-context-provider' 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 { useForceRenderPageUrlOnIframeLoadCallback } from './hooks/use-force-render-page-url-on-iframe-load-callback'
import { CommunicatorImageLightbox } from './communicator-image-lightbox' import { CommunicatorImageLightbox } from './communicator-image-lightbox'
import { setRendererStatus } from '../../../redux/renderer-status/methods'
import { useEditorReceiveHandler } from '../../render-page/window-post-message-communicator/hooks/use-editor-receive-handler' 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 { useSendDarkModeStatusToRenderer } from './hooks/use-send-dark-mode-status-to-renderer'
import { useSendMarkdownToRenderer } from './hooks/use-send-markdown-to-renderer' import { useSendMarkdownToRenderer } from './hooks/use-send-markdown-to-renderer'
import { useSendScrollState } from './hooks/use-send-scroll-state' import { useSendScrollState } from './hooks/use-send-scroll-state'
@ -33,6 +31,7 @@ export interface RenderIframeProps extends RendererProps {
rendererType: RendererType rendererType: RendererType
forcedDarkMode?: boolean forcedDarkMode?: boolean
frameClasses?: string frameClasses?: string
onRendererStatusChange?: undefined | ((rendererReady: boolean) => void)
} }
const log = new Logger('RenderIframe') const log = new Logger('RenderIframe')
@ -51,6 +50,7 @@ const log = new Logger('RenderIframe')
* @param frameClasses CSS classes that should be applied to the iframe * @param frameClasses CSS classes that should be applied to the iframe
* @param rendererType The {@link RendererType type} of the renderer to use. * @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 forcedDarkMode If set, the dark mode will be set to the given value. Otherwise, the dark mode won't be changed.
* @param onRendererStatusChange Callback that is fired when the renderer in the iframe is ready
*/ */
export const RenderIframe: React.FC<RenderIframeProps> = ({ export const RenderIframe: React.FC<RenderIframeProps> = ({
markdownContentLines, markdownContentLines,
@ -61,20 +61,25 @@ export const RenderIframe: React.FC<RenderIframeProps> = ({
onMakeScrollSource, onMakeScrollSource,
frameClasses, frameClasses,
rendererType, rendererType,
forcedDarkMode forcedDarkMode,
onRendererStatusChange
}) => { }) => {
const [rendererReady, setRendererReady] = useState<boolean>(false)
const frameReference = useRef<HTMLIFrameElement>(null) const frameReference = useRef<HTMLIFrameElement>(null)
const rendererBaseUrl = useBaseUrl(ORIGIN.RENDERER) const rendererBaseUrl = useBaseUrl(ORIGIN.RENDERER)
const iframeCommunicator = useEditorToRendererCommunicator() const iframeCommunicator = useEditorToRendererCommunicator()
const resetRendererReady = useCallback(() => { const resetRendererReady = useCallback(() => {
log.debug('Reset render status') log.debug('Reset render status')
setRendererStatus(false) setRendererReady(false)
}, []) }, [])
const rendererReady = useIsRendererReady()
const onIframeLoad = useForceRenderPageUrlOnIframeLoadCallback(frameReference, resetRendererReady) const onIframeLoad = useForceRenderPageUrlOnIframeLoadCallback(frameReference, resetRendererReady)
const [frameHeight, setFrameHeight] = useState<number>(0) const [frameHeight, setFrameHeight] = useState<number>(0)
useEffect(() => () => setRendererStatus(false), [iframeCommunicator]) useEffect(() => {
onRendererStatusChange?.(rendererReady)
}, [onRendererStatusChange, rendererReady])
useEffect(() => () => setRendererReady(false), [iframeCommunicator])
useEffect(() => { useEffect(() => {
if (!rendererReady) { if (!rendererReady) {
@ -108,7 +113,9 @@ export const RenderIframe: React.FC<RenderIframeProps> = ({
useEditorReceiveHandler( useEditorReceiveHandler(
CommunicationMessageType.ON_HEIGHT_CHANGE, CommunicationMessageType.ON_HEIGHT_CHANGE,
useCallback((values: OnHeightChangeMessage) => setFrameHeight?.(values.height), [setFrameHeight]) useCallback((values: OnHeightChangeMessage) => {
setFrameHeight?.(values.height)
}, [])
) )
useEditorReceiveHandler( useEditorReceiveHandler(
@ -135,13 +142,13 @@ export const RenderIframe: React.FC<RenderIframeProps> = ({
rendererType rendererType
} }
}) })
setRendererStatus(true) setRendererReady(true)
}, [iframeCommunicator, rendererBaseUrl, rendererType]) }, [iframeCommunicator, rendererBaseUrl, rendererType])
) )
useEffectOnRenderTypeChange(rendererType, onIframeLoad) useEffectOnRenderTypeChange(rendererType, onIframeLoad)
useSendDarkModeStatusToRenderer(forcedDarkMode) useSendDarkModeStatusToRenderer(forcedDarkMode, rendererReady)
useSendMarkdownToRenderer(markdownContentLines) useSendMarkdownToRenderer(markdownContentLines, rendererReady)
useSendScrollState(scrollState) useSendScrollState(scrollState)

View file

@ -1,22 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { fetchFrontPageContent } from '../requests'
export const useIntroPageContent = (): string[] | undefined => {
const { t } = useTranslation()
const [content, setContent] = useState<string[] | undefined>(undefined)
useEffect(() => {
fetchFrontPageContent()
.then((content) => setContent(content.split('\n')))
.catch(() => setContent(undefined))
}, [t])
return content
}

View file

@ -4,43 +4,34 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import React, { Fragment, useMemo } from 'react' import React, { useEffect, useState } from 'react'
import { useIntroPageContent } from './hooks/use-intro-page-content'
import { useApplicationState } from '../../hooks/common/use-application-state'
import { WaitSpinner } from '../common/wait-spinner/wait-spinner' import { WaitSpinner } from '../common/wait-spinner/wait-spinner'
import { RenderIframe } from '../editor-page/renderer-pane/render-iframe' import { RenderIframe } from '../editor-page/renderer-pane/render-iframe'
import { RendererType } from '../render-page/window-post-message-communicator/rendering-message' import { RendererType } from '../render-page/window-post-message-communicator/rendering-message'
import { useTranslation } from 'react-i18next'
import { fetchFrontPageContent } from './requests'
/** /**
* Fetches the content for the customizable part of the intro page and renders it. * Fetches the content for the customizable part of the intro page and renders it.
*/ */
export const IntroCustomContent: React.FC = () => { export const IntroCustomContent: React.FC = () => {
const introPageContent = useIntroPageContent() const { t } = useTranslation()
const rendererReady = useApplicationState((state) => state.rendererStatus.rendererReady) const [content, setContent] = useState<string[] | undefined>(undefined)
const spinner = useMemo(() => { useEffect(() => {
if (!rendererReady && introPageContent !== undefined) { fetchFrontPageContent()
return <WaitSpinner /> .then((content) => setContent(content.split('\n')))
} .catch(() => setContent(undefined))
}, [introPageContent, rendererReady]) }, [t])
const introContent = useMemo(() => { return content === undefined ? (
if (introPageContent !== undefined) { <WaitSpinner />
return ( ) : (
<RenderIframe <RenderIframe
frameClasses={'w-100 overflow-y-hidden'} frameClasses={'w-100 overflow-y-hidden'}
markdownContentLines={introPageContent} markdownContentLines={content}
rendererType={RendererType.INTRO} rendererType={RendererType.INTRO}
forcedDarkMode={true} forcedDarkMode={true}
/> />
)
}
}, [introPageContent])
return (
<Fragment>
{spinner}
{introContent}
</Fragment>
) )
} }

View file

@ -1,23 +0,0 @@
/*
* 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])
}

View file

@ -4,27 +4,26 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { useCallback } from 'react' import { useEffect } from 'react'
import type { CommunicationMessages, EditorToRendererMessageType } from '../rendering-message' import type { CommunicationMessages, EditorToRendererMessageType } from '../rendering-message'
import { useEditorToRendererCommunicator } from '../../../editor-page/render-context/editor-to-renderer-communicator-context-provider' import { useEditorToRendererCommunicator } from '../../../editor-page/render-context/editor-to-renderer-communicator-context-provider'
import type { MessagePayload } from '../window-post-message-communicator' import type { MessagePayload } from '../window-post-message-communicator'
import { useEffectOnRendererReady } from './use-effect-on-renderer-ready'
/** /**
* Sends the given message to the renderer. * Sends the given message to the renderer.
* *
* @param message The message to send * @param message The message to send
* @param rendererReady Defines if the target renderer is ready
*/ */
export const useSendToRenderer = ( export const useSendToRenderer = (
message: undefined | Extract<CommunicationMessages, MessagePayload<EditorToRendererMessageType>> message: undefined | Extract<CommunicationMessages, MessagePayload<EditorToRendererMessageType>>,
rendererReady: boolean
): void => { ): void => {
const iframeCommunicator = useEditorToRendererCommunicator() const iframeCommunicator = useEditorToRendererCommunicator()
useEffectOnRendererReady( useEffect(() => {
useCallback(() => { if (message && rendererReady) {
if (message) { iframeCommunicator.sendMessageToOtherSide(message)
iframeCommunicator.sendMessageToOtherSide(message) }
} }, [iframeCommunicator, message, rendererReady])
}, [iframeCommunicator, message])
)
} }

View file

@ -15,6 +15,7 @@ import { useTranslation } from 'react-i18next'
import { useTrimmedNoteMarkdownContentWithoutFrontmatter } from '../../hooks/common/use-trimmed-note-markdown-content-without-frontmatter' import { useTrimmedNoteMarkdownContentWithoutFrontmatter } from '../../hooks/common/use-trimmed-note-markdown-content-without-frontmatter'
import { useSendToRenderer } from '../render-page/window-post-message-communicator/hooks/use-send-to-renderer' import { useSendToRenderer } from '../render-page/window-post-message-communicator/hooks/use-send-to-renderer'
import { useApplicationState } from '../../hooks/common/use-application-state' import { useApplicationState } from '../../hooks/common/use-application-state'
import { setRendererStatus } from '../../redux/renderer-status/methods'
/** /**
* Renders the current markdown content as a slideshow. * Renders the current markdown content as a slideshow.
@ -24,6 +25,7 @@ export const SlideShowPageContent: React.FC = () => {
useTranslation() useTranslation()
const slideOptions = useApplicationState((state) => state.noteDetails.frontmatter.slideOptions) const slideOptions = useApplicationState((state) => state.noteDetails.frontmatter.slideOptions)
const rendererReady = useApplicationState((state) => state.rendererStatus.rendererReady)
useSendToRenderer( useSendToRenderer(
useMemo( useMemo(
() => ({ () => ({
@ -31,7 +33,8 @@ export const SlideShowPageContent: React.FC = () => {
slideOptions slideOptions
}), }),
[slideOptions] [slideOptions]
) ),
rendererReady
) )
return ( return (
@ -41,6 +44,7 @@ export const SlideShowPageContent: React.FC = () => {
markdownContentLines={markdownContentLines} markdownContentLines={markdownContentLines}
rendererType={RendererType.SLIDESHOW} rendererType={RendererType.SLIDESHOW}
onFirstHeadingChange={updateNoteTitleByFirstHeading} onFirstHeadingChange={updateNoteTitleByFirstHeading}
onRendererStatusChange={setRendererStatus}
/> />
</div> </div>
) )