refactor: reorganize props and locations of markdown renderers

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2023-04-11 17:12:30 +02:00
parent 7abbe79ec9
commit 6b3743e6a3
13 changed files with 214 additions and 243 deletions

View file

@ -1,104 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useApplicationState } from '../../hooks/common/use-application-state'
import type { ScrollProps } from '../editor-page/synced-scroll/scroll-props'
import { DocumentMarkdownRenderer } from '../markdown-renderer/document-markdown-renderer'
import { DocumentTocSidebar } from './document-toc-sidebar'
import { useDocumentSyncScrolling } from './hooks/sync-scroll/use-document-sync-scrolling'
import styles from './markdown-document.module.scss'
import useResizeObserver from '@react-hook/resize-observer'
import type { MutableRefObject } from 'react'
import React, { useEffect, useMemo, useRef, useState } from 'react'
export interface RendererProps extends ScrollProps {
documentRenderPaneRef?: MutableRefObject<HTMLDivElement | null>
markdownContentLines: string[]
onHeightChange?: (height: number) => void
}
export interface MarkdownDocumentProps extends RendererProps {
additionalOuterContainerClasses?: string
additionalRendererClasses?: string
disableToc?: boolean
baseUrl: string
newLinesAreBreaks?: boolean
}
/**
* Renders a Markdown document and handles scrolling, yaml metadata and a floating table of contents.
*
* @param additionalOuterContainerClasses Additional classes given to the outer container directly
* @param additionalRendererClasses Additional classes given {@link DocumentMarkdownRenderer} directly
* @param onMakeScrollSource The callback to call if a change of the scroll source is requested-
* @param baseUrl The base url for the renderer
* @param markdownContentLines The current content of the markdown document.
* @param onScroll The callback to call if the renderer is scrolling.
* @param scrollState The current {@link ScrollState}
* @param onHeightChange The callback to call if the height of the document changes
* @param disableToc If the table of contents should be disabled.
* @param newLinesAreBreaks Defines if the provided markdown content should treat new lines as breaks
*/
export const MarkdownDocument: React.FC<MarkdownDocumentProps> = ({
additionalOuterContainerClasses,
additionalRendererClasses,
onMakeScrollSource,
baseUrl,
markdownContentLines,
onScroll,
scrollState,
onHeightChange,
disableToc,
newLinesAreBreaks
}) => {
const rendererRef = useRef<HTMLDivElement | null>(null)
const [rendererSize, setRendererSize] = useState<DOMRectReadOnly>()
useResizeObserver(rendererRef.current, (entry) => {
setRendererSize(entry.contentRect)
})
useEffect(() => onHeightChange?.((rendererSize?.height ?? 0) + 1), [rendererSize, onHeightChange])
const internalDocumentRenderPaneRef = useRef<HTMLDivElement>(null)
const [internalDocumentRenderPaneSize, setInternalDocumentRenderPaneSize] = useState<DOMRectReadOnly>()
useResizeObserver(internalDocumentRenderPaneRef.current, (entry) =>
setInternalDocumentRenderPaneSize(entry.contentRect)
)
const contentLineCount = useMemo(() => markdownContentLines.length, [markdownContentLines])
const [recalculateLineMarkers, onUserScroll] = useDocumentSyncScrolling(
internalDocumentRenderPaneRef,
rendererRef,
contentLineCount,
scrollState,
onScroll
)
return (
<div
className={`${styles['markdown-document']} ${additionalOuterContainerClasses ?? ''}`}
ref={internalDocumentRenderPaneRef}
onScroll={onUserScroll}
data-scroll-element={true}
onMouseEnter={onMakeScrollSource}
onTouchStart={onMakeScrollSource}>
<div className={styles['markdown-document-side']} />
<div className={styles['markdown-document-content']}>
<DocumentMarkdownRenderer
outerContainerRef={rendererRef}
className={`mb-3 ${additionalRendererClasses ?? ''}`}
markdownContentLines={markdownContentLines}
onLineMarkerPositionChanged={recalculateLineMarkers}
baseUrl={baseUrl}
newlinesAreBreaks={newLinesAreBreaks}
/>
</div>
<DocumentTocSidebar
width={internalDocumentRenderPaneSize?.width ?? 0}
baseUrl={baseUrl}
disableToc={disableToc ?? false}
/>
</div>
)
}

View file

@ -7,13 +7,14 @@ import { setDarkModePreference } from '../../redux/dark-mode/methods'
import { useRendererToEditorCommunicator } from '../editor-page/render-context/renderer-to-editor-communicator-context-provider'
import type { ScrollState } from '../editor-page/synced-scroll/scroll-props'
import { eventEmitterContext } from '../markdown-renderer/hooks/use-extension-event-emitter'
import { SlideshowMarkdownRenderer } from '../markdown-renderer/slideshow-markdown-renderer'
import { MarkdownDocument } from './markdown-document'
import { DocumentMarkdownRenderer } from './renderers/document/document-markdown-renderer'
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 { countWords } from './word-counter'
import type { SlideOptions } from '@hedgedoc/commons/src/title-extraction/types/slide-show-options'
import type { SlideOptions } from '@hedgedoc/commons'
import { EventEmitter2 } from 'eventemitter2'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
@ -24,12 +25,10 @@ export const RenderPageContent: React.FC = () => {
const [markdownContentLines, setMarkdownContentLines] = useState<string[]>([])
const [scrollState, setScrollState] = useState<ScrollState>({ firstLineInView: 1, scrolledPercentage: 0 })
const [baseConfiguration, setBaseConfiguration] = useState<BaseConfiguration | undefined>(undefined)
const [slideOptions, setSlideOptions] = useState<SlideOptions>()
const [newLinesAreBreaks, setNewLinesAreBreaks] = useState<boolean>(true)
const communicator = useRendererToEditorCommunicator()
const sendScrolling = useRef<boolean>(false)
const [newLinesAreBreaks, setNewLinesAreBreaks] = useState<boolean>(true)
const [slideOptions, setSlideOptions] = useState<SlideOptions>()
useRendererReceiveHandler(
CommunicationMessageType.SET_SLIDE_OPTIONS,
@ -119,8 +118,7 @@ export const RenderPageContent: React.FC = () => {
switch (baseConfiguration.rendererType) {
case RendererType.DOCUMENT:
return (
<MarkdownDocument
additionalOuterContainerClasses={'vh-100 bg-light'}
<DocumentMarkdownRenderer
markdownContentLines={markdownContentLines}
onMakeScrollSource={onMakeScrollSource}
scrollState={scrollState}
@ -135,20 +133,17 @@ export const RenderPageContent: React.FC = () => {
<SlideshowMarkdownRenderer
markdownContentLines={markdownContentLines}
baseUrl={baseConfiguration.baseUrl}
scrollState={scrollState}
newLinesAreBreaks={newLinesAreBreaks}
slideOptions={slideOptions}
newlinesAreBreaks={newLinesAreBreaks}
/>
)
case RendererType.SIMPLE:
return (
<MarkdownDocument
additionalOuterContainerClasses={'vh-100 bg-light overflow-y-hidden'}
<SimpleMarkdownRenderer
markdownContentLines={markdownContentLines}
baseUrl={baseConfiguration.baseUrl}
disableToc={true}
onHeightChange={onHeightChange}
newLinesAreBreaks={newLinesAreBreaks}
onHeightChange={onHeightChange}
/>
)
default:

View file

@ -0,0 +1,15 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
export interface CommonMarkdownRendererProps {
baseUrl: string
newLinesAreBreaks?: boolean
markdownContentLines: string[]
}
export interface HeightChangeRendererProps {
onHeightChange?: (height: number) => void
}

View file

@ -0,0 +1,108 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { cypressId } from '../../../../utils/cypress-attribute'
import type { ScrollProps } from '../../../editor-page/synced-scroll/scroll-props'
import { HeadlineAnchorsMarkdownExtension } from '../../../markdown-renderer/extensions/headline-anchors-markdown-extension'
import type { LineMarkers } from '../../../markdown-renderer/extensions/linemarker/add-line-marker-markdown-it-plugin'
import { LinemarkerMarkdownExtension } from '../../../markdown-renderer/extensions/linemarker/linemarker-markdown-extension'
import { useCalculateLineMarkerPosition } from '../../../markdown-renderer/hooks/use-calculate-line-marker-positions'
import { useMarkdownExtensions } from '../../../markdown-renderer/hooks/use-markdown-extensions'
import { MarkdownToReact } from '../../../markdown-renderer/markdown-to-react/markdown-to-react'
import { useDocumentSyncScrolling } from '../../hooks/sync-scroll/use-document-sync-scrolling'
import type { CommonMarkdownRendererProps, HeightChangeRendererProps } from '../common-markdown-renderer-props'
import { DocumentTocSidebar } from './document-toc-sidebar'
import styles from './markdown-document.module.scss'
import useResizeObserver from '@react-hook/resize-observer'
import React, { useEffect, useMemo, useRef, useState } from 'react'
export type DocumentMarkdownRendererProps = CommonMarkdownRendererProps & ScrollProps & HeightChangeRendererProps
/**
* Renders a Markdown document and handles scrolling, yaml metadata and a floating table of contents.
*
* @param onMakeScrollSource The callback to call if a change of the scroll source is requested-
* @param baseUrl The base url for the renderer
* @param markdownContentLines The current content of the Markdown document.
* @param onScroll The callback to call if the renderer is scrolling.
* @param scrollState The current {@link ScrollState}
* @param onHeightChange The callback to call if the height of the document changes
* @param newlinesAreBreaks Defines if the provided markdown content should treat new lines as breaks
*/
export const DocumentMarkdownRenderer: React.FC<DocumentMarkdownRendererProps> = ({
onMakeScrollSource,
baseUrl,
markdownContentLines,
onScroll,
scrollState,
onHeightChange,
newLinesAreBreaks
}) => {
const rendererRef = useRef<HTMLDivElement | null>(null)
const [rendererSize, setRendererSize] = useState<DOMRectReadOnly>()
useResizeObserver(rendererRef.current, (entry) => {
setRendererSize(entry.contentRect)
})
useEffect(() => onHeightChange?.((rendererSize?.height ?? 0) + 1), [rendererSize, onHeightChange])
const internalDocumentRenderPaneRef = useRef<HTMLDivElement>(null)
const [internalDocumentRenderPaneSize, setInternalDocumentRenderPaneSize] = useState<DOMRectReadOnly>()
useResizeObserver(internalDocumentRenderPaneRef.current, (entry) =>
setInternalDocumentRenderPaneSize(entry.contentRect)
)
const contentLineCount = useMemo(() => markdownContentLines.length, [markdownContentLines])
const [recalculateLineMarkers, onUserScroll] = useDocumentSyncScrolling(
internalDocumentRenderPaneRef,
rendererRef,
contentLineCount,
scrollState,
onScroll
)
const markdownBodyRef = useRef<HTMLDivElement>(null)
const currentLineMarkers = useRef<LineMarkers[]>()
const extensions = useMarkdownExtensions(
baseUrl,
useMemo(
() => [
new HeadlineAnchorsMarkdownExtension(),
new LinemarkerMarkdownExtension((values) => (currentLineMarkers.current = values))
],
[]
)
)
useCalculateLineMarkerPosition(markdownBodyRef, currentLineMarkers.current, recalculateLineMarkers)
return (
<div
className={`${styles['markdown-document']} vh-100 bg-light`}
ref={internalDocumentRenderPaneRef}
onScroll={onUserScroll}
data-scroll-element={true}
onMouseEnter={onMakeScrollSource}
onTouchStart={onMakeScrollSource}>
<div className={styles['markdown-document-side']} />
<div className={styles['markdown-document-content']}>
<div ref={rendererRef} className={`position-relative`}>
<div
{...cypressId('markdown-body')}
ref={markdownBodyRef}
data-word-count-target={true}
className={`mb-3 markdown-body w-100 d-flex flex-column align-items-center`}>
<MarkdownToReact
markdownContentLines={markdownContentLines}
markdownRenderExtensions={extensions}
newlinesAreBreaks={newLinesAreBreaks}
allowHtml={true}
/>
</div>
</div>
</div>
<DocumentTocSidebar width={internalDocumentRenderPaneSize?.width ?? 0} baseUrl={baseUrl} />
</div>
)
}

View file

@ -3,9 +3,8 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { ShowIf } from '../common/show-if/show-if'
import { TableOfContentsMarkdownExtension } from '../markdown-renderer/extensions/table-of-contents/table-of-contents-markdown-extension'
import { useExtensionEventEmitterHandler } from '../markdown-renderer/hooks/use-extension-event-emitter'
import { TableOfContentsMarkdownExtension } from '../../../markdown-renderer/extensions/table-of-contents/table-of-contents-markdown-extension'
import { useExtensionEventEmitterHandler } from '../../../markdown-renderer/hooks/use-extension-event-emitter'
import styles from './markdown-document.module.scss'
import { WidthBasedTableOfContents } from './width-based-table-of-contents'
import type { TocAst } from '@hedgedoc/markdown-it-plugins'
@ -13,18 +12,15 @@ import React, { useState } from 'react'
export interface DocumentTocSidebarProps {
width: number
disableToc: boolean
baseUrl: string
}
export const DocumentTocSidebar: React.FC<DocumentTocSidebarProps> = ({ disableToc, width, baseUrl }) => {
export const DocumentTocSidebar: React.FC<DocumentTocSidebarProps> = ({ width, baseUrl }) => {
const [tocAst, setTocAst] = useState<TocAst>()
useExtensionEventEmitterHandler(TableOfContentsMarkdownExtension.EVENT_NAME, setTocAst)
return (
<div className={`${styles['markdown-document-side']} pt-4`}>
<ShowIf condition={!!tocAst && !disableToc}>
<WidthBasedTableOfContents tocAst={tocAst as TocAst} baseUrl={baseUrl} width={width} />
</ShowIf>
<WidthBasedTableOfContents tocAst={tocAst as TocAst} baseUrl={baseUrl} width={width} />
</div>
)
}

View file

@ -3,8 +3,8 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { TableOfContents } from '../editor-page/table-of-contents/table-of-contents'
import { TableOfContentsHoveringButton } from './markdown-toc-button/table-of-contents-hovering-button'
import { TableOfContents } from '../../../editor-page/table-of-contents/table-of-contents'
import { TableOfContentsHoveringButton } from '../../markdown-toc-button/table-of-contents-hovering-button'
import type { TocAst } from '@hedgedoc/markdown-it-plugins'
import React from 'react'

View file

@ -0,0 +1,54 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { cypressId } from '../../../../utils/cypress-attribute'
import { useMarkdownExtensions } from '../../../markdown-renderer/hooks/use-markdown-extensions'
import { MarkdownToReact } from '../../../markdown-renderer/markdown-to-react/markdown-to-react'
import type { CommonMarkdownRendererProps, HeightChangeRendererProps } from '../common-markdown-renderer-props'
import useResizeObserver from '@react-hook/resize-observer'
import React, { useEffect, useRef, useState } from 'react'
export type SimpleMarkdownRendererProps = CommonMarkdownRendererProps & HeightChangeRendererProps
/**
* Renders just the given Markdown content without scrolling, slideshow, toc notifying and other additions.
*
* @param additionalOuterContainerClasses Additional classes given to the outer container directly
* @param baseUrl The base url for the renderer
* @param markdownContentLines The current content of the markdown document.
* @param onHeightChange The callback to call if the height of the document changes
*/
export const SimpleMarkdownRenderer: React.FC<SimpleMarkdownRendererProps> = ({
baseUrl,
markdownContentLines,
onHeightChange,
newLinesAreBreaks
}) => {
const rendererRef = useRef<HTMLDivElement | null>(null)
const [rendererSize, setRendererSize] = useState<DOMRectReadOnly>()
useResizeObserver(rendererRef.current, (entry) => {
setRendererSize(entry.contentRect)
})
useEffect(() => onHeightChange?.((rendererSize?.height ?? 0) + 1), [rendererSize, onHeightChange])
const extensions = useMarkdownExtensions(baseUrl, [])
return (
<div className={`vh-100 bg-transparent overflow-y-hidden`}>
<div ref={rendererRef} className={`position-relative`}>
<div
{...cypressId('markdown-body')}
data-word-count-target={true}
className={`markdown-body w-100 d-flex flex-column align-items-center`}>
<MarkdownToReact
markdownContentLines={markdownContentLines}
markdownRenderExtensions={extensions}
newlinesAreBreaks={newLinesAreBreaks}
allowHtml={true}
/>
</div>
</div>
</div>
)
}

View file

@ -0,0 +1,21 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React from 'react'
import { Trans, useTranslation } from 'react-i18next'
/**
* Shows a static text placeholder while reveal.js is loading.
*/
export const LoadingSlide: React.FC = () => {
useTranslation()
return (
<section>
<h1>
<Trans i18nKey={'common.loading'} />
</h1>
</section>
)
}

View file

@ -0,0 +1,64 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { RevealMarkdownExtension } from '../../../markdown-renderer/extensions/reveal/reveal-markdown-extension'
import { useMarkdownExtensions } from '../../../markdown-renderer/hooks/use-markdown-extensions'
import { REVEAL_STATUS, useReveal } from '../../../markdown-renderer/hooks/use-reveal'
import { MarkdownToReact } from '../../../markdown-renderer/markdown-to-react/markdown-to-react'
import type { CommonMarkdownRendererProps } from '../common-markdown-renderer-props'
import { LoadingSlide } from './loading-slide'
import type { SlideOptions } from '@hedgedoc/commons'
import React, { useMemo, useRef } from 'react'
export interface SlideshowMarkdownRendererProps extends CommonMarkdownRendererProps {
slideOptions?: SlideOptions
}
/**
* Renders the note as a reveal.js presentation.
*
* @param className Additional class names directly given to the div
* @param markdownContentLines The markdown lines
* @param baseUrl The base url of the renderer
* @param newLinesAreBreaks If newlines are rendered as breaks or not
*/
export const SlideshowMarkdownRenderer: React.FC<SlideshowMarkdownRendererProps> = ({
markdownContentLines,
baseUrl,
newLinesAreBreaks,
slideOptions
}) => {
const markdownBodyRef = useRef<HTMLDivElement>(null)
const extensions = useMarkdownExtensions(
baseUrl,
useMemo(() => [new RevealMarkdownExtension()], [])
)
const revealStatus = useReveal(markdownContentLines, slideOptions)
const slideShowDOM = useMemo(
() =>
revealStatus === REVEAL_STATUS.INITIALISED ? (
<MarkdownToReact
markdownContentLines={markdownContentLines}
markdownRenderExtensions={extensions}
allowHtml={true}
newlinesAreBreaks={newLinesAreBreaks}
/>
) : (
<LoadingSlide />
),
[extensions, markdownContentLines, newLinesAreBreaks, revealStatus]
)
return (
<div className={'reveal'}>
<div ref={markdownBodyRef} className={`slides`}>
{slideShowDOM}
</div>
</div>
)
}