mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-28 16:11:00 -05:00
refactor: reorganize props and locations of markdown renderers
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
a36d063da4
commit
6b308e58be
13 changed files with 214 additions and 243 deletions
|
@ -10,16 +10,17 @@ import { isTestMode } from '../../../utils/test-modes'
|
||||||
import { ShowIf } from '../../common/show-if/show-if'
|
import { ShowIf } from '../../common/show-if/show-if'
|
||||||
import { WaitSpinner } from '../../common/wait-spinner/wait-spinner'
|
import { WaitSpinner } from '../../common/wait-spinner/wait-spinner'
|
||||||
import { useExtensionEventEmitter } from '../../markdown-renderer/hooks/use-extension-event-emitter'
|
import { useExtensionEventEmitter } from '../../markdown-renderer/hooks/use-extension-event-emitter'
|
||||||
import type { RendererProps } from '../../render-page/markdown-document'
|
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 { useEditorReceiveHandler } from '../../render-page/window-post-message-communicator/hooks/use-editor-receive-handler'
|
||||||
import type {
|
import type {
|
||||||
ExtensionEvent,
|
ExtensionEvent,
|
||||||
OnHeightChangeMessage,
|
OnHeightChangeMessage,
|
||||||
RendererType,
|
|
||||||
SetScrollStateMessage
|
SetScrollStateMessage
|
||||||
} from '../../render-page/window-post-message-communicator/rendering-message'
|
} 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 { CommunicationMessageType } from '../../render-page/window-post-message-communicator/rendering-message'
|
||||||
import { useEditorToRendererCommunicator } from '../render-context/editor-to-renderer-communicator-context-provider'
|
import { useEditorToRendererCommunicator } from '../render-context/editor-to-renderer-communicator-context-provider'
|
||||||
|
import type { ScrollProps } from '../synced-scroll/scroll-props'
|
||||||
import { useEffectOnRenderTypeChange } from './hooks/use-effect-on-render-type-change'
|
import { useEffectOnRenderTypeChange } from './hooks/use-effect-on-render-type-change'
|
||||||
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 { useSendAdditionalConfigurationToRenderer } from './hooks/use-send-additional-configuration-to-renderer'
|
import { useSendAdditionalConfigurationToRenderer } from './hooks/use-send-additional-configuration-to-renderer'
|
||||||
|
@ -27,7 +28,7 @@ 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'
|
||||||
import React, { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
import React, { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
|
|
||||||
export interface RenderIframeProps extends RendererProps {
|
export interface RenderIframeProps extends Omit<CommonMarkdownRendererProps & ScrollProps, 'baseUrl'> {
|
||||||
rendererType: RendererType
|
rendererType: RendererType
|
||||||
forcedDarkMode?: DarkModePreference
|
forcedDarkMode?: DarkModePreference
|
||||||
frameClasses?: string
|
frameClasses?: string
|
||||||
|
@ -90,11 +91,6 @@ export const RenderIframe: React.FC<RenderIframeProps> = ({
|
||||||
onRendererStatusChange?.(rendererReady)
|
onRendererStatusChange?.(rendererReady)
|
||||||
}, [onRendererStatusChange, rendererReady])
|
}, [onRendererStatusChange, rendererReady])
|
||||||
|
|
||||||
useEditorReceiveHandler(
|
|
||||||
CommunicationMessageType.ENABLE_RENDERER_SCROLL_SOURCE,
|
|
||||||
useCallback(() => onMakeScrollSource?.(), [onMakeScrollSource])
|
|
||||||
)
|
|
||||||
|
|
||||||
const eventEmitter = useExtensionEventEmitter()
|
const eventEmitter = useExtensionEventEmitter()
|
||||||
|
|
||||||
useEditorReceiveHandler(
|
useEditorReceiveHandler(
|
||||||
|
@ -147,12 +143,16 @@ export const RenderIframe: React.FC<RenderIframeProps> = ({
|
||||||
useEffectOnRenderTypeChange(rendererType, onIframeLoad)
|
useEffectOnRenderTypeChange(rendererType, onIframeLoad)
|
||||||
useSendAdditionalConfigurationToRenderer(rendererReady, forcedDarkMode)
|
useSendAdditionalConfigurationToRenderer(rendererReady, forcedDarkMode)
|
||||||
useSendMarkdownToRenderer(markdownContentLines, rendererReady)
|
useSendMarkdownToRenderer(markdownContentLines, rendererReady)
|
||||||
useSendScrollState(scrollState, rendererReady)
|
|
||||||
|
|
||||||
|
useSendScrollState(scrollState, rendererReady)
|
||||||
useEditorReceiveHandler(
|
useEditorReceiveHandler(
|
||||||
CommunicationMessageType.SET_SCROLL_STATE,
|
CommunicationMessageType.SET_SCROLL_STATE,
|
||||||
useCallback((values: SetScrollStateMessage) => onScroll?.(values.scrollState), [onScroll])
|
useCallback((values: SetScrollStateMessage) => onScroll?.(values.scrollState), [onScroll])
|
||||||
)
|
)
|
||||||
|
useEditorReceiveHandler(
|
||||||
|
CommunicationMessageType.ENABLE_RENDERER_SCROLL_SOURCE,
|
||||||
|
useCallback(() => onMakeScrollSource?.(), [onMakeScrollSource])
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
import type { Ref } from 'react'
|
|
||||||
|
|
||||||
export interface CommonMarkdownRendererProps {
|
|
||||||
baseUrl: string
|
|
||||||
outerContainerRef?: Ref<HTMLDivElement>
|
|
||||||
newlinesAreBreaks?: boolean
|
|
||||||
lineOffset?: number
|
|
||||||
className?: string
|
|
||||||
markdownContentLines: string[]
|
|
||||||
}
|
|
|
@ -1,75 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
import { cypressId } from '../../utils/cypress-attribute'
|
|
||||||
import type { CommonMarkdownRendererProps } from './common-markdown-renderer-props'
|
|
||||||
import { HeadlineAnchorsMarkdownExtension } from './extensions/headline-anchors-markdown-extension'
|
|
||||||
import type { LineMarkers } from './extensions/linemarker/add-line-marker-markdown-it-plugin'
|
|
||||||
import { LinemarkerMarkdownExtension } from './extensions/linemarker/linemarker-markdown-extension'
|
|
||||||
import type { LineMarkerPosition } from './extensions/linemarker/types'
|
|
||||||
import { useCalculateLineMarkerPosition } from './hooks/use-calculate-line-marker-positions'
|
|
||||||
import { useMarkdownExtensions } from './hooks/use-markdown-extensions'
|
|
||||||
import { MarkdownToReact } from './markdown-to-react/markdown-to-react'
|
|
||||||
import React, { useMemo, useRef } from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
|
|
||||||
export interface DocumentMarkdownRendererProps extends CommonMarkdownRendererProps {
|
|
||||||
onLineMarkerPositionChanged?: (lineMarkerPosition: LineMarkerPosition[]) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the note as normal document.
|
|
||||||
*
|
|
||||||
* @param className Additional class names directly given to the div
|
|
||||||
* @param markdownContentLines The markdown lines
|
|
||||||
* @param onLineMarkerPositionChanged The callback to call with changed {@link LineMarkers}
|
|
||||||
* @param baseUrl The base url of the renderer
|
|
||||||
* @param outerContainerRef A reference for the outer container
|
|
||||||
* @param newlinesAreBreaks If newlines are rendered as breaks or not
|
|
||||||
*/
|
|
||||||
export const DocumentMarkdownRenderer: React.FC<DocumentMarkdownRendererProps> = ({
|
|
||||||
className,
|
|
||||||
markdownContentLines,
|
|
||||||
onLineMarkerPositionChanged,
|
|
||||||
baseUrl,
|
|
||||||
outerContainerRef,
|
|
||||||
newlinesAreBreaks
|
|
||||||
}) => {
|
|
||||||
const markdownBodyRef = useRef<HTMLDivElement>(null)
|
|
||||||
const currentLineMarkers = useRef<LineMarkers[]>()
|
|
||||||
|
|
||||||
const extensions = useMarkdownExtensions(
|
|
||||||
baseUrl,
|
|
||||||
useMemo(
|
|
||||||
() => [
|
|
||||||
new HeadlineAnchorsMarkdownExtension(),
|
|
||||||
new LinemarkerMarkdownExtension((values) => (currentLineMarkers.current = values))
|
|
||||||
],
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
useTranslation()
|
|
||||||
useCalculateLineMarkerPosition(markdownBodyRef, currentLineMarkers.current, onLineMarkerPositionChanged)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div ref={outerContainerRef} className={`position-relative`}>
|
|
||||||
<div
|
|
||||||
{...cypressId('markdown-body')}
|
|
||||||
ref={markdownBodyRef}
|
|
||||||
data-word-count-target={true}
|
|
||||||
className={`${className ?? ''} markdown-body w-100 d-flex flex-column align-items-center`}>
|
|
||||||
<MarkdownToReact
|
|
||||||
markdownContentLines={markdownContentLines}
|
|
||||||
markdownRenderExtensions={extensions}
|
|
||||||
newlinesAreBreaks={newlinesAreBreaks}
|
|
||||||
allowHtml={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DocumentMarkdownRenderer
|
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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 { useRendererToEditorCommunicator } from '../editor-page/render-context/renderer-to-editor-communicator-context-provider'
|
||||||
import type { ScrollState } from '../editor-page/synced-scroll/scroll-props'
|
import type { ScrollState } from '../editor-page/synced-scroll/scroll-props'
|
||||||
import { eventEmitterContext } from '../markdown-renderer/hooks/use-extension-event-emitter'
|
import { eventEmitterContext } from '../markdown-renderer/hooks/use-extension-event-emitter'
|
||||||
import { SlideshowMarkdownRenderer } from '../markdown-renderer/slideshow-markdown-renderer'
|
import { DocumentMarkdownRenderer } from './renderers/document/document-markdown-renderer'
|
||||||
import { MarkdownDocument } from './markdown-document'
|
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 { useRendererReceiveHandler } from './window-post-message-communicator/hooks/use-renderer-receive-handler'
|
||||||
import type { BaseConfiguration } 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 { CommunicationMessageType, RendererType } from './window-post-message-communicator/rendering-message'
|
||||||
import { countWords } from './word-counter'
|
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 { EventEmitter2 } from 'eventemitter2'
|
||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
|
|
||||||
|
@ -24,12 +25,10 @@ export const RenderPageContent: React.FC = () => {
|
||||||
const [markdownContentLines, setMarkdownContentLines] = useState<string[]>([])
|
const [markdownContentLines, setMarkdownContentLines] = useState<string[]>([])
|
||||||
const [scrollState, setScrollState] = useState<ScrollState>({ firstLineInView: 1, scrolledPercentage: 0 })
|
const [scrollState, setScrollState] = useState<ScrollState>({ firstLineInView: 1, scrolledPercentage: 0 })
|
||||||
const [baseConfiguration, setBaseConfiguration] = useState<BaseConfiguration | undefined>(undefined)
|
const [baseConfiguration, setBaseConfiguration] = useState<BaseConfiguration | undefined>(undefined)
|
||||||
const [slideOptions, setSlideOptions] = useState<SlideOptions>()
|
|
||||||
const [newLinesAreBreaks, setNewLinesAreBreaks] = useState<boolean>(true)
|
|
||||||
|
|
||||||
const communicator = useRendererToEditorCommunicator()
|
const communicator = useRendererToEditorCommunicator()
|
||||||
|
|
||||||
const sendScrolling = useRef<boolean>(false)
|
const sendScrolling = useRef<boolean>(false)
|
||||||
|
const [newLinesAreBreaks, setNewLinesAreBreaks] = useState<boolean>(true)
|
||||||
|
const [slideOptions, setSlideOptions] = useState<SlideOptions>()
|
||||||
|
|
||||||
useRendererReceiveHandler(
|
useRendererReceiveHandler(
|
||||||
CommunicationMessageType.SET_SLIDE_OPTIONS,
|
CommunicationMessageType.SET_SLIDE_OPTIONS,
|
||||||
|
@ -119,8 +118,7 @@ export const RenderPageContent: React.FC = () => {
|
||||||
switch (baseConfiguration.rendererType) {
|
switch (baseConfiguration.rendererType) {
|
||||||
case RendererType.DOCUMENT:
|
case RendererType.DOCUMENT:
|
||||||
return (
|
return (
|
||||||
<MarkdownDocument
|
<DocumentMarkdownRenderer
|
||||||
additionalOuterContainerClasses={'vh-100 bg-light'}
|
|
||||||
markdownContentLines={markdownContentLines}
|
markdownContentLines={markdownContentLines}
|
||||||
onMakeScrollSource={onMakeScrollSource}
|
onMakeScrollSource={onMakeScrollSource}
|
||||||
scrollState={scrollState}
|
scrollState={scrollState}
|
||||||
|
@ -135,20 +133,17 @@ export const RenderPageContent: React.FC = () => {
|
||||||
<SlideshowMarkdownRenderer
|
<SlideshowMarkdownRenderer
|
||||||
markdownContentLines={markdownContentLines}
|
markdownContentLines={markdownContentLines}
|
||||||
baseUrl={baseConfiguration.baseUrl}
|
baseUrl={baseConfiguration.baseUrl}
|
||||||
scrollState={scrollState}
|
newLinesAreBreaks={newLinesAreBreaks}
|
||||||
slideOptions={slideOptions}
|
slideOptions={slideOptions}
|
||||||
newlinesAreBreaks={newLinesAreBreaks}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
case RendererType.SIMPLE:
|
case RendererType.SIMPLE:
|
||||||
return (
|
return (
|
||||||
<MarkdownDocument
|
<SimpleMarkdownRenderer
|
||||||
additionalOuterContainerClasses={'vh-100 bg-light overflow-y-hidden'}
|
|
||||||
markdownContentLines={markdownContentLines}
|
markdownContentLines={markdownContentLines}
|
||||||
baseUrl={baseConfiguration.baseUrl}
|
baseUrl={baseConfiguration.baseUrl}
|
||||||
disableToc={true}
|
|
||||||
onHeightChange={onHeightChange}
|
|
||||||
newLinesAreBreaks={newLinesAreBreaks}
|
newLinesAreBreaks={newLinesAreBreaks}
|
||||||
|
onHeightChange={onHeightChange}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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>
|
||||||
|
)
|
||||||
|
}
|
|
@ -3,9 +3,8 @@
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* 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 { TableOfContentsMarkdownExtension } from '../markdown-renderer/extensions/table-of-contents/table-of-contents-markdown-extension'
|
import { useExtensionEventEmitterHandler } from '../../../markdown-renderer/hooks/use-extension-event-emitter'
|
||||||
import { useExtensionEventEmitterHandler } from '../markdown-renderer/hooks/use-extension-event-emitter'
|
|
||||||
import styles from './markdown-document.module.scss'
|
import styles from './markdown-document.module.scss'
|
||||||
import { WidthBasedTableOfContents } from './width-based-table-of-contents'
|
import { WidthBasedTableOfContents } from './width-based-table-of-contents'
|
||||||
import type { TocAst } from '@hedgedoc/markdown-it-plugins'
|
import type { TocAst } from '@hedgedoc/markdown-it-plugins'
|
||||||
|
@ -13,18 +12,15 @@ import React, { useState } from 'react'
|
||||||
|
|
||||||
export interface DocumentTocSidebarProps {
|
export interface DocumentTocSidebarProps {
|
||||||
width: number
|
width: number
|
||||||
disableToc: boolean
|
|
||||||
baseUrl: string
|
baseUrl: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DocumentTocSidebar: React.FC<DocumentTocSidebarProps> = ({ disableToc, width, baseUrl }) => {
|
export const DocumentTocSidebar: React.FC<DocumentTocSidebarProps> = ({ width, baseUrl }) => {
|
||||||
const [tocAst, setTocAst] = useState<TocAst>()
|
const [tocAst, setTocAst] = useState<TocAst>()
|
||||||
useExtensionEventEmitterHandler(TableOfContentsMarkdownExtension.EVENT_NAME, setTocAst)
|
useExtensionEventEmitterHandler(TableOfContentsMarkdownExtension.EVENT_NAME, setTocAst)
|
||||||
return (
|
return (
|
||||||
<div className={`${styles['markdown-document-side']} pt-4`}>
|
<div className={`${styles['markdown-document-side']} pt-4`}>
|
||||||
<ShowIf condition={!!tocAst && !disableToc}>
|
<WidthBasedTableOfContents tocAst={tocAst as TocAst} baseUrl={baseUrl} width={width} />
|
||||||
<WidthBasedTableOfContents tocAst={tocAst as TocAst} baseUrl={baseUrl} width={width} />
|
|
||||||
</ShowIf>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -3,8 +3,8 @@
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { TableOfContents } from '../editor-page/table-of-contents/table-of-contents'
|
import { TableOfContents } from '../../../editor-page/table-of-contents/table-of-contents'
|
||||||
import { TableOfContentsHoveringButton } from './markdown-toc-button/table-of-contents-hovering-button'
|
import { TableOfContentsHoveringButton } from '../../markdown-toc-button/table-of-contents-hovering-button'
|
||||||
import type { TocAst } from '@hedgedoc/markdown-it-plugins'
|
import type { TocAst } from '@hedgedoc/markdown-it-plugins'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
|
@ -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>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
|
@ -3,13 +3,12 @@
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { ScrollProps } from '../editor-page/synced-scroll/scroll-props'
|
import { RevealMarkdownExtension } from '../../../markdown-renderer/extensions/reveal/reveal-markdown-extension'
|
||||||
import type { CommonMarkdownRendererProps } from './common-markdown-renderer-props'
|
import { useMarkdownExtensions } from '../../../markdown-renderer/hooks/use-markdown-extensions'
|
||||||
import { RevealMarkdownExtension } from './extensions/reveal/reveal-markdown-extension'
|
import { REVEAL_STATUS, useReveal } from '../../../markdown-renderer/hooks/use-reveal'
|
||||||
import { useMarkdownExtensions } from './hooks/use-markdown-extensions'
|
import { MarkdownToReact } from '../../../markdown-renderer/markdown-to-react/markdown-to-react'
|
||||||
import { REVEAL_STATUS, useReveal } from './hooks/use-reveal'
|
import type { CommonMarkdownRendererProps } from '../common-markdown-renderer-props'
|
||||||
import { LoadingSlide } from './loading-slide'
|
import { LoadingSlide } from './loading-slide'
|
||||||
import { MarkdownToReact } from './markdown-to-react/markdown-to-react'
|
|
||||||
import type { SlideOptions } from '@hedgedoc/commons'
|
import type { SlideOptions } from '@hedgedoc/commons'
|
||||||
import React, { useMemo, useRef } from 'react'
|
import React, { useMemo, useRef } from 'react'
|
||||||
|
|
||||||
|
@ -23,14 +22,12 @@ export interface SlideshowMarkdownRendererProps extends CommonMarkdownRendererPr
|
||||||
* @param className Additional class names directly given to the div
|
* @param className Additional class names directly given to the div
|
||||||
* @param markdownContentLines The markdown lines
|
* @param markdownContentLines The markdown lines
|
||||||
* @param baseUrl The base url of the renderer
|
* @param baseUrl The base url of the renderer
|
||||||
* @param newlinesAreBreaks If newlines are rendered as breaks or not
|
* @param newLinesAreBreaks If newlines are rendered as breaks or not
|
||||||
* @param slideOptions The {@link SlideOptions} to use
|
|
||||||
*/
|
*/
|
||||||
export const SlideshowMarkdownRenderer: React.FC<SlideshowMarkdownRendererProps & ScrollProps> = ({
|
export const SlideshowMarkdownRenderer: React.FC<SlideshowMarkdownRendererProps> = ({
|
||||||
className,
|
|
||||||
markdownContentLines,
|
markdownContentLines,
|
||||||
baseUrl,
|
baseUrl,
|
||||||
newlinesAreBreaks,
|
newLinesAreBreaks,
|
||||||
slideOptions
|
slideOptions
|
||||||
}) => {
|
}) => {
|
||||||
const markdownBodyRef = useRef<HTMLDivElement>(null)
|
const markdownBodyRef = useRef<HTMLDivElement>(null)
|
||||||
|
@ -49,17 +46,17 @@ export const SlideshowMarkdownRenderer: React.FC<SlideshowMarkdownRendererProps
|
||||||
markdownContentLines={markdownContentLines}
|
markdownContentLines={markdownContentLines}
|
||||||
markdownRenderExtensions={extensions}
|
markdownRenderExtensions={extensions}
|
||||||
allowHtml={true}
|
allowHtml={true}
|
||||||
newlinesAreBreaks={newlinesAreBreaks}
|
newlinesAreBreaks={newLinesAreBreaks}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<LoadingSlide />
|
<LoadingSlide />
|
||||||
),
|
),
|
||||||
[extensions, markdownContentLines, newlinesAreBreaks, revealStatus]
|
[extensions, markdownContentLines, newLinesAreBreaks, revealStatus]
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'reveal'}>
|
<div className={'reveal'}>
|
||||||
<div ref={markdownBodyRef} className={`${className ?? ''} slides`}>
|
<div ref={markdownBodyRef} className={`slides`}>
|
||||||
{slideShowDOM}
|
{slideShowDOM}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
Loading…
Reference in a new issue