mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-13 14:44:43 -04:00
Don't send frontmatter to renderer (#2259)
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
c50037fc9f
commit
f68b3ff056
24 changed files with 215 additions and 176 deletions
|
@ -7,7 +7,6 @@
|
||||||
import React, { Fragment } from 'react'
|
import React, { Fragment } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { updateNoteTitleByFirstHeading } from '../../redux/note-details/methods'
|
import { updateNoteTitleByFirstHeading } from '../../redux/note-details/methods'
|
||||||
import { useSendFrontmatterInfoFromReduxToRenderer } from '../editor-page/renderer-pane/hooks/use-send-frontmatter-info-from-redux-to-renderer'
|
|
||||||
import { DocumentInfobar } from './document-infobar'
|
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'
|
||||||
|
@ -20,7 +19,6 @@ export const DocumentReadOnlyPageContent: React.FC = () => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
|
|
||||||
const markdownContentLines = useTrimmedNoteMarkdownContentWithoutFrontmatter()
|
const markdownContentLines = useTrimmedNoteMarkdownContentWithoutFrontmatter()
|
||||||
useSendFrontmatterInfoFromReduxToRenderer()
|
|
||||||
|
|
||||||
// TODO Change todo values with real ones as soon as the backend is ready.
|
// TODO Change todo values with real ones as soon as the backend is ready.
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -7,12 +7,13 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import type { RenderIframeProps } from '../renderer-pane/render-iframe'
|
import type { RenderIframeProps } from '../renderer-pane/render-iframe'
|
||||||
import { RenderIframe } from '../renderer-pane/render-iframe'
|
import { RenderIframe } from '../renderer-pane/render-iframe'
|
||||||
import { useSendFrontmatterInfoFromReduxToRenderer } from '../renderer-pane/hooks/use-send-frontmatter-info-from-redux-to-renderer'
|
|
||||||
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 { NoteType } from '../../../redux/note-details/types/note-details'
|
import { NoteType } from '../../../redux/note-details/types/note-details'
|
||||||
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||||
import { RendererType } from '../../render-page/window-post-message-communicator/rendering-message'
|
import { RendererType } from '../../render-page/window-post-message-communicator/rendering-message'
|
||||||
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 { useScrollStateWithoutLineOffset } from './hooks/use-scroll-state-without-line-offset'
|
||||||
|
|
||||||
export type EditorDocumentRendererProps = Omit<
|
export type EditorDocumentRendererProps = Omit<
|
||||||
RenderIframeProps,
|
RenderIframeProps,
|
||||||
|
@ -22,17 +23,22 @@ export type EditorDocumentRendererProps = Omit<
|
||||||
/**
|
/**
|
||||||
* Renders the markdown content from the global application state with the iframe renderer.
|
* Renders the markdown content from the global application state with the iframe renderer.
|
||||||
*
|
*
|
||||||
* @param props Every property from the {@link RenderIframe} except the markdown content.
|
* @param scrollState The {@link ScrollState} that should be sent to the renderer
|
||||||
|
* @param onScroll A callback that is executed when the view in the rendered is scrolled
|
||||||
|
* @param props Every property from the {@link RenderIframe} except the markdown content
|
||||||
*/
|
*/
|
||||||
export const EditorDocumentRenderer: React.FC<EditorDocumentRendererProps> = (props) => {
|
export const EditorDocumentRenderer: React.FC<EditorDocumentRendererProps> = ({ scrollState, onScroll, ...props }) => {
|
||||||
useSendFrontmatterInfoFromReduxToRenderer()
|
|
||||||
const trimmedContentLines = useTrimmedNoteMarkdownContentWithoutFrontmatter()
|
const trimmedContentLines = useTrimmedNoteMarkdownContentWithoutFrontmatter()
|
||||||
const noteType: NoteType = useApplicationState((state) => state.noteDetails.frontmatter.type)
|
const noteType: NoteType = useApplicationState((state) => state.noteDetails.frontmatter.type)
|
||||||
const setCheckboxInEditor = useSetCheckboxInEditor()
|
const setCheckboxInEditor = useSetCheckboxInEditor()
|
||||||
|
const adjustedOnScroll = useOnScrollWithLineOffset(onScroll)
|
||||||
|
const adjustedScrollState = useScrollStateWithoutLineOffset(scrollState)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RenderIframe
|
<RenderIframe
|
||||||
{...props}
|
{...props}
|
||||||
|
onScroll={adjustedOnScroll}
|
||||||
|
scrollState={adjustedScrollState}
|
||||||
onTaskCheckedChange={setCheckboxInEditor}
|
onTaskCheckedChange={setCheckboxInEditor}
|
||||||
rendererType={noteType === NoteType.SLIDE ? RendererType.SLIDESHOW : RendererType.DOCUMENT}
|
rendererType={noteType === NoteType.SLIDE ? RendererType.SLIDESHOW : RendererType.DOCUMENT}
|
||||||
markdownContentLines={trimmedContentLines}
|
markdownContentLines={trimmedContentLines}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { ScrollState } from '../../synced-scroll/scroll-props'
|
||||||
|
import { getGlobalState } from '../../../../redux'
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
import type { ScrollCallback } from '../../synced-scroll/scroll-props'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjusts the given onScroll callback to include the frontmatter line offset.
|
||||||
|
*
|
||||||
|
* @param onScroll The callback that posts a scroll state
|
||||||
|
* @return the modified callback that posts the same scroll state but with line offset
|
||||||
|
*/
|
||||||
|
export const useOnScrollWithLineOffset = (onScroll: ScrollCallback | undefined): ScrollCallback | undefined => {
|
||||||
|
return useMemo(() => {
|
||||||
|
if (onScroll === undefined) {
|
||||||
|
return undefined
|
||||||
|
} else {
|
||||||
|
return (scrollState: ScrollState) => {
|
||||||
|
onScroll({
|
||||||
|
firstLineInView:
|
||||||
|
scrollState.firstLineInView + getGlobalState().noteDetails.frontmatterRendererInfo.lineOffset,
|
||||||
|
scrolledPercentage: scrollState.scrolledPercentage
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [onScroll])
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* 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 { ScrollState } from '../../synced-scroll/scroll-props'
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjusts the given {@link ScrollState scroll state} to exclude the frontmatter line offset.
|
||||||
|
*
|
||||||
|
* @param scrollState The original scroll state with the line offset
|
||||||
|
* @return the adjusted scroll state without the line offset
|
||||||
|
*/
|
||||||
|
export const useScrollStateWithoutLineOffset = (scrollState: ScrollState | undefined): ScrollState | undefined => {
|
||||||
|
const lineOffset = useApplicationState((state) => state.noteDetails.frontmatterRendererInfo.lineOffset)
|
||||||
|
return useMemo(() => {
|
||||||
|
return scrollState === undefined
|
||||||
|
? undefined
|
||||||
|
: {
|
||||||
|
firstLineInView: scrollState.firstLineInView - lineOffset,
|
||||||
|
scrolledPercentage: scrollState.scrolledPercentage
|
||||||
|
}
|
||||||
|
}, [lineOffset, scrollState])
|
||||||
|
}
|
|
@ -39,7 +39,10 @@ export const useOnImageUploadFromRenderer = (): void => {
|
||||||
.then((blob) => {
|
.then((blob) => {
|
||||||
const file = new File([blob], fileName, { type: blob.type })
|
const file = new File([blob], fileName, { type: blob.type })
|
||||||
const { cursorSelection, alt, title } = Optional.ofNullable(lineIndex)
|
const { cursorSelection, alt, title } = Optional.ofNullable(lineIndex)
|
||||||
.flatMap((actualLineIndex) => findPlaceholderInMarkdownContent(actualLineIndex, placeholderIndexInLine))
|
.flatMap((actualLineIndex) => {
|
||||||
|
const lineOffset = getGlobalState().noteDetails.frontmatterRendererInfo.lineOffset
|
||||||
|
return findPlaceholderInMarkdownContent(actualLineIndex + lineOffset, placeholderIndexInLine)
|
||||||
|
})
|
||||||
.orElse({} as ExtractResult)
|
.orElse({} as ExtractResult)
|
||||||
handleUpload(file, cursorSelection, alt, title)
|
handleUpload(file, cursorSelection, alt, title)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: 2021 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, useRef } from 'react'
|
|
||||||
import { CommunicationMessageType } from '../../../render-page/window-post-message-communicator/rendering-message'
|
|
||||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
|
||||||
import equal from 'fast-deep-equal'
|
|
||||||
import type { RendererFrontmatterInfo } from '../../../../redux/note-details/types/note-details'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts the {@link RendererFrontmatterInfo frontmatter data}
|
|
||||||
* from the global application state and sends it to the renderer.
|
|
||||||
*/
|
|
||||||
export const useSendFrontmatterInfoFromReduxToRenderer = (): void => {
|
|
||||||
const frontmatterInfo = useApplicationState((state) => state.noteDetails.frontmatterRendererInfo)
|
|
||||||
const lastFrontmatter = useRef<RendererFrontmatterInfo | undefined>(undefined)
|
|
||||||
|
|
||||||
const cachedFrontmatterInfo = useMemo(() => {
|
|
||||||
if (lastFrontmatter.current !== undefined && equal(lastFrontmatter.current, frontmatterInfo)) {
|
|
||||||
return lastFrontmatter.current
|
|
||||||
} else {
|
|
||||||
lastFrontmatter.current = frontmatterInfo
|
|
||||||
return frontmatterInfo
|
|
||||||
}
|
|
||||||
}, [frontmatterInfo])
|
|
||||||
|
|
||||||
return useSendToRenderer(
|
|
||||||
useMemo(
|
|
||||||
() => ({
|
|
||||||
type: CommunicationMessageType.SET_FRONTMATTER_INFO,
|
|
||||||
frontmatterInfo: cachedFrontmatterInfo
|
|
||||||
}),
|
|
||||||
[cachedFrontmatterInfo]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -27,6 +27,7 @@ import { Logger } from '../../../utils/logger'
|
||||||
import { useEffectOnRenderTypeChange } from './hooks/use-effect-on-render-type-change'
|
import { useEffectOnRenderTypeChange } from './hooks/use-effect-on-render-type-change'
|
||||||
import { cypressAttribute, cypressId } from '../../../utils/cypress-attribute'
|
import { cypressAttribute, cypressId } from '../../../utils/cypress-attribute'
|
||||||
import { ORIGIN_TYPE, useOriginFromConfig } from '../render-context/use-origin-from-config'
|
import { ORIGIN_TYPE, useOriginFromConfig } from '../render-context/use-origin-from-config'
|
||||||
|
import { getGlobalState } from '../../../redux'
|
||||||
|
|
||||||
export interface RenderIframeProps extends RendererProps {
|
export interface RenderIframeProps extends RendererProps {
|
||||||
rendererType: RendererType
|
rendererType: RendererType
|
||||||
|
@ -89,11 +90,6 @@ export const RenderIframe: React.FC<RenderIframeProps> = ({
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
useEditorReceiveHandler(
|
|
||||||
CommunicationMessageType.SET_SCROLL_STATE,
|
|
||||||
useCallback((values: SetScrollStateMessage) => onScroll?.(values.scrollState), [onScroll])
|
|
||||||
)
|
|
||||||
|
|
||||||
useEditorReceiveHandler(
|
useEditorReceiveHandler(
|
||||||
CommunicationMessageType.ENABLE_RENDERER_SCROLL_SOURCE,
|
CommunicationMessageType.ENABLE_RENDERER_SCROLL_SOURCE,
|
||||||
useCallback(() => onMakeScrollSource?.(), [onMakeScrollSource])
|
useCallback(() => onMakeScrollSource?.(), [onMakeScrollSource])
|
||||||
|
@ -102,7 +98,10 @@ export const RenderIframe: React.FC<RenderIframeProps> = ({
|
||||||
useEditorReceiveHandler(
|
useEditorReceiveHandler(
|
||||||
CommunicationMessageType.ON_TASK_CHECKBOX_CHANGE,
|
CommunicationMessageType.ON_TASK_CHECKBOX_CHANGE,
|
||||||
useCallback(
|
useCallback(
|
||||||
(values: OnTaskCheckboxChangeMessage) => onTaskCheckedChange?.(values.lineInMarkdown, values.checked),
|
(values: OnTaskCheckboxChangeMessage) => {
|
||||||
|
const lineOffset = getGlobalState().noteDetails.frontmatterRendererInfo.lineOffset
|
||||||
|
onTaskCheckedChange?.(values.lineInMarkdown + lineOffset, values.checked)
|
||||||
|
},
|
||||||
[onTaskCheckedChange]
|
[onTaskCheckedChange]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -140,10 +139,16 @@ export const RenderIframe: React.FC<RenderIframeProps> = ({
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffectOnRenderTypeChange(rendererType, onIframeLoad)
|
useEffectOnRenderTypeChange(rendererType, onIframeLoad)
|
||||||
useSendScrollState(scrollState)
|
|
||||||
useSendDarkModeStatusToRenderer(forcedDarkMode)
|
useSendDarkModeStatusToRenderer(forcedDarkMode)
|
||||||
useSendMarkdownToRenderer(markdownContentLines)
|
useSendMarkdownToRenderer(markdownContentLines)
|
||||||
|
|
||||||
|
useSendScrollState(scrollState)
|
||||||
|
|
||||||
|
useEditorReceiveHandler(
|
||||||
|
CommunicationMessageType.SET_SCROLL_STATE,
|
||||||
|
useCallback((values: SetScrollStateMessage) => onScroll?.(values.scrollState), [onScroll])
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<CommunicatorImageLightbox />
|
<CommunicatorImageLightbox />
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
/*
|
/*
|
||||||
* 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
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export type ScrollCallback = (scrollState: ScrollState) => void
|
||||||
|
|
||||||
export interface ScrollProps {
|
export interface ScrollProps {
|
||||||
scrollState?: ScrollState
|
scrollState?: ScrollState
|
||||||
onScroll?: (scrollState: ScrollState) => void
|
onScroll?: ScrollCallback
|
||||||
onMakeScrollSource?: () => void
|
onMakeScrollSource?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,6 @@ export interface DocumentMarkdownRendererProps extends CommonMarkdownRendererPro
|
||||||
* @param onImageClick The callback to call if a image is clicked
|
* @param onImageClick The callback to call if a image is clicked
|
||||||
* @param outerContainerRef A reference for the outer container
|
* @param outerContainerRef A reference for the outer container
|
||||||
* @param newlinesAreBreaks If newlines are rendered as breaks or not
|
* @param newlinesAreBreaks If newlines are rendered as breaks or not
|
||||||
* @param lineOffset The line offset
|
|
||||||
*/
|
*/
|
||||||
export const DocumentMarkdownRenderer: React.FC<DocumentMarkdownRendererProps> = ({
|
export const DocumentMarkdownRenderer: React.FC<DocumentMarkdownRendererProps> = ({
|
||||||
className,
|
className,
|
||||||
|
@ -45,8 +44,7 @@ export const DocumentMarkdownRenderer: React.FC<DocumentMarkdownRendererProps> =
|
||||||
baseUrl,
|
baseUrl,
|
||||||
onImageClick,
|
onImageClick,
|
||||||
outerContainerRef,
|
outerContainerRef,
|
||||||
newlinesAreBreaks,
|
newlinesAreBreaks
|
||||||
lineOffset
|
|
||||||
}) => {
|
}) => {
|
||||||
const markdownBodyRef = useRef<HTMLDivElement>(null)
|
const markdownBodyRef = useRef<HTMLDivElement>(null)
|
||||||
const currentLineMarkers = useRef<LineMarkers[]>()
|
const currentLineMarkers = useRef<LineMarkers[]>()
|
||||||
|
@ -55,7 +53,6 @@ export const DocumentMarkdownRenderer: React.FC<DocumentMarkdownRendererProps> =
|
||||||
baseUrl,
|
baseUrl,
|
||||||
currentLineMarkers,
|
currentLineMarkers,
|
||||||
useMemo(() => [new HeadlineAnchorsMarkdownExtension()], []),
|
useMemo(() => [new HeadlineAnchorsMarkdownExtension()], []),
|
||||||
lineOffset ?? 0,
|
|
||||||
onTaskCheckedChange,
|
onTaskCheckedChange,
|
||||||
onImageClick,
|
onImageClick,
|
||||||
onTocChange
|
onTocChange
|
||||||
|
|
|
@ -34,7 +34,6 @@ import { SpoilerMarkdownExtension } from '../markdown-extension/spoiler-markdown
|
||||||
import { LinkifyFixMarkdownExtension } from '../markdown-extension/linkify-fix-markdown-extension'
|
import { LinkifyFixMarkdownExtension } from '../markdown-extension/linkify-fix-markdown-extension'
|
||||||
import { HighlightedCodeMarkdownExtension } from '../markdown-extension/highlighted-fence/highlighted-code-markdown-extension'
|
import { HighlightedCodeMarkdownExtension } from '../markdown-extension/highlighted-fence/highlighted-code-markdown-extension'
|
||||||
import { DebuggerMarkdownExtension } from '../markdown-extension/debugger-markdown-extension'
|
import { DebuggerMarkdownExtension } from '../markdown-extension/debugger-markdown-extension'
|
||||||
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
|
||||||
import type { LineMarkers } from '../markdown-extension/linemarker/add-line-marker-markdown-it-plugin'
|
import type { LineMarkers } from '../markdown-extension/linemarker/add-line-marker-markdown-it-plugin'
|
||||||
import type { ImageClickHandler } from '../markdown-extension/image/proxy-image-replacer'
|
import type { ImageClickHandler } from '../markdown-extension/image/proxy-image-replacer'
|
||||||
import type { TocAst } from 'markdown-it-toc-done-right'
|
import type { TocAst } from 'markdown-it-toc-done-right'
|
||||||
|
@ -50,7 +49,6 @@ import { useOnRefChange } from './use-on-ref-change'
|
||||||
* @param baseUrl The base url for the {@link LinkAdjustmentMarkdownExtension}
|
* @param baseUrl The base url for the {@link LinkAdjustmentMarkdownExtension}
|
||||||
* @param currentLineMarkers A {@link MutableRefObject reference} to {@link LineMarkers} for the {@link LinemarkerMarkdownExtension}
|
* @param currentLineMarkers A {@link MutableRefObject reference} to {@link LineMarkers} for the {@link LinemarkerMarkdownExtension}
|
||||||
* @param additionalExtensions The additional extensions that should be included in the list
|
* @param additionalExtensions The additional extensions that should be included in the list
|
||||||
* @param lineOffset The line offset for the {@link LinemarkerMarkdownExtension} and {@link TaskListMarkdownExtension}
|
|
||||||
* @param onTaskCheckedChange The checkbox click callback for the {@link TaskListMarkdownExtension}
|
* @param onTaskCheckedChange The checkbox click callback for the {@link TaskListMarkdownExtension}
|
||||||
* @param onImageClick The image click callback for the {@link ProxyImageMarkdownExtension}
|
* @param onImageClick The image click callback for the {@link ProxyImageMarkdownExtension}
|
||||||
* @param onTocChange The toc-changed callback for the {@link TableOfContentsMarkdownExtension}
|
* @param onTocChange The toc-changed callback for the {@link TableOfContentsMarkdownExtension}
|
||||||
|
@ -60,12 +58,10 @@ export const useMarkdownExtensions = (
|
||||||
baseUrl: string,
|
baseUrl: string,
|
||||||
currentLineMarkers: MutableRefObject<LineMarkers[] | undefined> | undefined,
|
currentLineMarkers: MutableRefObject<LineMarkers[] | undefined> | undefined,
|
||||||
additionalExtensions: MarkdownExtension[],
|
additionalExtensions: MarkdownExtension[],
|
||||||
lineOffset: number,
|
|
||||||
onTaskCheckedChange?: (lineInMarkdown: number, checked: boolean) => void,
|
onTaskCheckedChange?: (lineInMarkdown: number, checked: boolean) => void,
|
||||||
onImageClick?: ImageClickHandler,
|
onImageClick?: ImageClickHandler,
|
||||||
onTocChange?: (ast?: TocAst) => void
|
onTocChange?: (ast?: TocAst) => void
|
||||||
): MarkdownExtension[] => {
|
): MarkdownExtension[] => {
|
||||||
const plantumlServer = useApplicationState((state) => state.config.plantumlServer)
|
|
||||||
const toc = useRef<TocAst | undefined>(undefined)
|
const toc = useRef<TocAst | undefined>(undefined)
|
||||||
useOnRefChange(toc, onTocChange)
|
useOnRefChange(toc, onTocChange)
|
||||||
|
|
||||||
|
@ -76,11 +72,10 @@ export const useMarkdownExtensions = (
|
||||||
new VegaLiteMarkdownExtension(),
|
new VegaLiteMarkdownExtension(),
|
||||||
// new MarkmapMarkdownExtension(),
|
// new MarkmapMarkdownExtension(),
|
||||||
new LinemarkerMarkdownExtension(
|
new LinemarkerMarkdownExtension(
|
||||||
lineOffset,
|
|
||||||
currentLineMarkers ? (lineMarkers) => (currentLineMarkers.current = lineMarkers) : undefined
|
currentLineMarkers ? (lineMarkers) => (currentLineMarkers.current = lineMarkers) : undefined
|
||||||
),
|
),
|
||||||
new IframeCapsuleMarkdownExtension(),
|
new IframeCapsuleMarkdownExtension(),
|
||||||
new ImagePlaceholderMarkdownExtension(lineOffset),
|
new ImagePlaceholderMarkdownExtension(),
|
||||||
new UploadIndicatingImageFrameMarkdownExtension(),
|
new UploadIndicatingImageFrameMarkdownExtension(),
|
||||||
new GistMarkdownExtension(),
|
new GistMarkdownExtension(),
|
||||||
new YoutubeMarkdownExtension(),
|
new YoutubeMarkdownExtension(),
|
||||||
|
@ -95,8 +90,8 @@ export const useMarkdownExtensions = (
|
||||||
new BlockquoteExtraTagMarkdownExtension(),
|
new BlockquoteExtraTagMarkdownExtension(),
|
||||||
new LinkAdjustmentMarkdownExtension(baseUrl),
|
new LinkAdjustmentMarkdownExtension(baseUrl),
|
||||||
new KatexMarkdownExtension(),
|
new KatexMarkdownExtension(),
|
||||||
new TaskListMarkdownExtension(lineOffset, onTaskCheckedChange),
|
new TaskListMarkdownExtension(onTaskCheckedChange),
|
||||||
new PlantumlMarkdownExtension(plantumlServer),
|
new PlantumlMarkdownExtension(),
|
||||||
new LegacyShortcodesMarkdownExtension(),
|
new LegacyShortcodesMarkdownExtension(),
|
||||||
new EmojiMarkdownExtension(),
|
new EmojiMarkdownExtension(),
|
||||||
new GenericSyntaxMarkdownExtension(),
|
new GenericSyntaxMarkdownExtension(),
|
||||||
|
@ -106,5 +101,5 @@ export const useMarkdownExtensions = (
|
||||||
new HighlightedCodeMarkdownExtension(),
|
new HighlightedCodeMarkdownExtension(),
|
||||||
new DebuggerMarkdownExtension()
|
new DebuggerMarkdownExtension()
|
||||||
]
|
]
|
||||||
}, [additionalExtensions, baseUrl, currentLineMarkers, lineOffset, onImageClick, onTaskCheckedChange, plantumlServer])
|
}, [additionalExtensions, baseUrl, currentLineMarkers, onImageClick, onTaskCheckedChange])
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { ImagePlaceholderReplacer } from './image-placeholder-replacer'
|
||||||
export class ImagePlaceholderMarkdownExtension extends MarkdownExtension {
|
export class ImagePlaceholderMarkdownExtension extends MarkdownExtension {
|
||||||
public static readonly PLACEHOLDER_URL = 'https://'
|
public static readonly PLACEHOLDER_URL = 'https://'
|
||||||
|
|
||||||
constructor(private lineOffset: number) {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +25,6 @@ export class ImagePlaceholderMarkdownExtension extends MarkdownExtension {
|
||||||
}
|
}
|
||||||
|
|
||||||
buildReplacers(): ComponentReplacer[] {
|
buildReplacers(): ComponentReplacer[] {
|
||||||
return [new ImagePlaceholderReplacer(this.lineOffset)]
|
return [new ImagePlaceholderReplacer()]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { ImagePlaceholderMarkdownExtension } from './image-placeholder-markdown-
|
||||||
export class ImagePlaceholderReplacer extends ComponentReplacer {
|
export class ImagePlaceholderReplacer extends ComponentReplacer {
|
||||||
private countPerSourceLine = new Map<number, number>()
|
private countPerSourceLine = new Map<number, number>()
|
||||||
|
|
||||||
constructor(private lineOffset: number) {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ export class ImagePlaceholderReplacer extends ComponentReplacer {
|
||||||
title={node.attribs.title}
|
title={node.attribs.title}
|
||||||
width={node.attribs.width}
|
width={node.attribs.width}
|
||||||
height={node.attribs.height}
|
height={node.attribs.height}
|
||||||
lineIndex={isNaN(lineIndex) ? undefined : lineIndex + this.lineOffset}
|
lineIndex={isNaN(lineIndex) ? undefined : lineIndex}
|
||||||
placeholderIndexInLine={indexInLine}
|
placeholderIndexInLine={indexInLine}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
@ -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,19 +13,53 @@ export interface LineMarkers {
|
||||||
endLine: number
|
endLine: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const insertNewLineMarker = (
|
||||||
|
startLineNumber: number,
|
||||||
|
endLineNumber: number,
|
||||||
|
tokenPosition: number,
|
||||||
|
level: number,
|
||||||
|
tokens: Token[]
|
||||||
|
) => {
|
||||||
|
const startToken = new Token('app_linemarker', LinemarkerMarkdownExtension.tagName, 0)
|
||||||
|
startToken.level = level
|
||||||
|
startToken.attrPush(['data-start-line', `${startLineNumber}`])
|
||||||
|
startToken.attrPush(['data-end-line', `${endLineNumber}`])
|
||||||
|
tokens.splice(tokenPosition, 0, startToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
const tagTokens = (tokens: Token[], lineMarkers: LineMarkers[]) => {
|
||||||
|
for (let tokenPosition = 0; tokenPosition < tokens.length; tokenPosition++) {
|
||||||
|
const token = tokens[tokenPosition]
|
||||||
|
if (token.hidden || !token.map) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const startLineNumber = token.map[0] + 1
|
||||||
|
const endLineNumber = token.map[1] + 1
|
||||||
|
|
||||||
|
if (token.level === 0) {
|
||||||
|
lineMarkers.push({ startLine: startLineNumber, endLine: endLineNumber })
|
||||||
|
insertNewLineMarker(startLineNumber, endLineNumber, tokenPosition, token.level, tokens)
|
||||||
|
tokenPosition += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token.children) {
|
||||||
|
tagTokens(token.children, lineMarkers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This plugin adds markers to the dom, that are used to map line numbers to dom elements.
|
* This plugin adds markers to the dom, that are used to map line numbers to dom elements.
|
||||||
* It also provides a list of line numbers for the top level dom elements.
|
* It also provides a list of line numbers for the top level dom elements.
|
||||||
*/
|
*/
|
||||||
export const addLineMarkerMarkdownItPlugin: (
|
export const addLineMarkerMarkdownItPlugin: (
|
||||||
markdownIt: MarkdownIt,
|
markdownIt: MarkdownIt,
|
||||||
lineOffset: number,
|
|
||||||
onLineMarkerChange?: (lineMarkers: LineMarkers[]) => void
|
onLineMarkerChange?: (lineMarkers: LineMarkers[]) => void
|
||||||
) => void = (md: MarkdownIt, lineOffset, onLineMarkerChange) => {
|
) => void = (md, onLineMarkerChange) => {
|
||||||
// add app_linemarker token before each opening or self-closing level-0 tag
|
|
||||||
md.core.ruler.push('line_number_marker', (state) => {
|
md.core.ruler.push('line_number_marker', (state) => {
|
||||||
const lineMarkers: LineMarkers[] = []
|
const lineMarkers: LineMarkers[] = []
|
||||||
tagTokens(state.tokens, lineMarkers, lineOffset)
|
tagTokens(state.tokens, lineMarkers)
|
||||||
if (onLineMarkerChange) {
|
if (onLineMarkerChange) {
|
||||||
onLineMarkerChange(lineMarkers)
|
onLineMarkerChange(lineMarkers)
|
||||||
}
|
}
|
||||||
|
@ -36,52 +70,8 @@ export const addLineMarkerMarkdownItPlugin: (
|
||||||
const startLineNumber = tokens[index].attrGet('data-start-line')
|
const startLineNumber = tokens[index].attrGet('data-start-line')
|
||||||
const endLineNumber = tokens[index].attrGet('data-end-line')
|
const endLineNumber = tokens[index].attrGet('data-end-line')
|
||||||
|
|
||||||
if (!startLineNumber || !endLineNumber) {
|
return startLineNumber && endLineNumber
|
||||||
// don't render broken linemarkers without a linenumber
|
? `<${LinemarkerMarkdownExtension.tagName} data-start-line='${startLineNumber}' data-end-line='${endLineNumber}'></${LinemarkerMarkdownExtension.tagName}>`
|
||||||
return ''
|
: ''
|
||||||
}
|
|
||||||
// noinspection CheckTagEmptyBody
|
|
||||||
return `<${LinemarkerMarkdownExtension.tagName} data-start-line='${startLineNumber}' data-end-line='${endLineNumber}'></${LinemarkerMarkdownExtension.tagName}>`
|
|
||||||
}
|
|
||||||
|
|
||||||
const insertNewLineMarker = (
|
|
||||||
startLineNumber: number,
|
|
||||||
endLineNumber: number,
|
|
||||||
tokenPosition: number,
|
|
||||||
level: number,
|
|
||||||
tokens: Token[]
|
|
||||||
) => {
|
|
||||||
const startToken = new Token('app_linemarker', LinemarkerMarkdownExtension.tagName, 0)
|
|
||||||
startToken.level = level
|
|
||||||
startToken.attrPush(['data-start-line', `${startLineNumber}`])
|
|
||||||
startToken.attrPush(['data-end-line', `${endLineNumber}`])
|
|
||||||
tokens.splice(tokenPosition, 0, startToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
const tagTokens = (tokens: Token[], lineMarkers: LineMarkers[], lineOffset: number) => {
|
|
||||||
for (let tokenPosition = 0; tokenPosition < tokens.length; tokenPosition++) {
|
|
||||||
const token = tokens[tokenPosition]
|
|
||||||
if (token.hidden) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!token.map) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const startLineNumber = token.map[0] + 1
|
|
||||||
const endLineNumber = token.map[1] + 1
|
|
||||||
|
|
||||||
if (token.level === 0) {
|
|
||||||
lineMarkers.push({ startLine: startLineNumber + lineOffset, endLine: endLineNumber + lineOffset })
|
|
||||||
}
|
|
||||||
|
|
||||||
insertNewLineMarker(startLineNumber, endLineNumber, tokenPosition, token.level, tokens)
|
|
||||||
tokenPosition += 1
|
|
||||||
|
|
||||||
if (token.children) {
|
|
||||||
tagTokens(token.children, lineMarkers, lineOffset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,12 +17,12 @@ import type MarkdownIt from 'markdown-it'
|
||||||
export class LinemarkerMarkdownExtension extends MarkdownExtension {
|
export class LinemarkerMarkdownExtension extends MarkdownExtension {
|
||||||
public static readonly tagName = 'app-linemarker'
|
public static readonly tagName = 'app-linemarker'
|
||||||
|
|
||||||
constructor(private lineOffset: number, private onLineMarkers?: (lineMarkers: LineMarkers[]) => void) {
|
constructor(private onLineMarkers?: (lineMarkers: LineMarkers[]) => void) {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
public configureMarkdownIt(markdownIt: MarkdownIt): void {
|
public configureMarkdownIt(markdownIt: MarkdownIt): void {
|
||||||
addLineMarkerMarkdownItPlugin(markdownIt, this.lineOffset ?? 0, this.onLineMarkers)
|
addLineMarkerMarkdownItPlugin(markdownIt, this.onLineMarkers)
|
||||||
}
|
}
|
||||||
|
|
||||||
public buildReplacers(): ComponentReplacer[] {
|
public buildReplacers(): ComponentReplacer[] {
|
||||||
|
|
|
@ -4,7 +4,7 @@ exports[`PlantUML markdown extensions renders a plantuml codeblock 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<img
|
<img
|
||||||
alt="uml diagram"
|
alt="uml diagram"
|
||||||
src="http://example.org/svg/SoWkIImgAStDuKhEIImkLd2jICmjo4dbSaZDIm6A0W00"
|
src="https://example.org/svg/SoWkIImgAStDuKhEIImkLd2jICmjo4dbSaZDIm6A0W00"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,9 @@ import { TestMarkdownRenderer } from '../../test-utils/test-markdown-renderer'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { PlantumlMarkdownExtension } from './plantuml-markdown-extension'
|
import { PlantumlMarkdownExtension } from './plantuml-markdown-extension'
|
||||||
import { mockI18n } from '../../test-utils/mock-i18n'
|
import { mockI18n } from '../../test-utils/mock-i18n'
|
||||||
|
import * as reduxModule from '../../../../redux'
|
||||||
|
import { Mock } from 'ts-mockery'
|
||||||
|
import type { ApplicationState } from '../../../../redux/application-state'
|
||||||
|
|
||||||
describe('PlantUML markdown extensions', () => {
|
describe('PlantUML markdown extensions', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
|
@ -16,9 +19,17 @@ describe('PlantUML markdown extensions', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders a plantuml codeblock', () => {
|
it('renders a plantuml codeblock', () => {
|
||||||
|
jest.spyOn(reduxModule, 'getGlobalState').mockReturnValue(
|
||||||
|
Mock.of<ApplicationState>({
|
||||||
|
config: {
|
||||||
|
plantumlServer: 'https://example.org'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
const view = render(
|
const view = render(
|
||||||
<TestMarkdownRenderer
|
<TestMarkdownRenderer
|
||||||
extensions={[new PlantumlMarkdownExtension('http://example.org')]}
|
extensions={[new PlantumlMarkdownExtension()]}
|
||||||
content={'```plantuml\nclass Example\n```'}
|
content={'```plantuml\nclass Example\n```'}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -26,9 +37,17 @@ describe('PlantUML markdown extensions', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders an error if no server is defined', () => {
|
it('renders an error if no server is defined', () => {
|
||||||
|
jest.spyOn(reduxModule, 'getGlobalState').mockReturnValue(
|
||||||
|
Mock.of<ApplicationState>({
|
||||||
|
config: {
|
||||||
|
plantumlServer: undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
const view = render(
|
const view = render(
|
||||||
<TestMarkdownRenderer
|
<TestMarkdownRenderer
|
||||||
extensions={[new PlantumlMarkdownExtension(null)]}
|
extensions={[new PlantumlMarkdownExtension()]}
|
||||||
content={'```plantuml\nclass Example\n```'}
|
content={'```plantuml\nclass Example\n```'}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
@ -12,6 +12,8 @@ import type Token from 'markdown-it/lib/token'
|
||||||
import type { Options } from 'markdown-it/lib'
|
import type { Options } from 'markdown-it/lib'
|
||||||
import type { ComponentReplacer } from '../../replace-components/component-replacer'
|
import type { ComponentReplacer } from '../../replace-components/component-replacer'
|
||||||
import { PlantumlNotConfiguredComponentReplacer } from './plantuml-not-configured-component-replacer'
|
import { PlantumlNotConfiguredComponentReplacer } from './plantuml-not-configured-component-replacer'
|
||||||
|
import { getGlobalState } from '../../../../redux'
|
||||||
|
import { Optional } from '@mrdrogdrog/optional'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds support for chart rendering using plantuml to the markdown rendering using code fences with "plantuml" as language.
|
* Adds support for chart rendering using plantuml to the markdown rendering using code fences with "plantuml" as language.
|
||||||
|
@ -19,7 +21,7 @@ import { PlantumlNotConfiguredComponentReplacer } from './plantuml-not-configure
|
||||||
* @see https://plantuml.com
|
* @see https://plantuml.com
|
||||||
*/
|
*/
|
||||||
export class PlantumlMarkdownExtension extends MarkdownExtension {
|
export class PlantumlMarkdownExtension extends MarkdownExtension {
|
||||||
constructor(private plantumlServer?: string) {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,15 +35,15 @@ export class PlantumlMarkdownExtension extends MarkdownExtension {
|
||||||
}
|
}
|
||||||
|
|
||||||
public configureMarkdownIt(markdownIt: MarkdownIt): void {
|
public configureMarkdownIt(markdownIt: MarkdownIt): void {
|
||||||
if (this.plantumlServer) {
|
Optional.ofNullable(getGlobalState().config.plantumlServer)
|
||||||
|
.map((plantumlServer) =>
|
||||||
plantuml(markdownIt, {
|
plantuml(markdownIt, {
|
||||||
openMarker: '```plantuml',
|
openMarker: '```plantuml',
|
||||||
closeMarker: '```',
|
closeMarker: '```',
|
||||||
server: this.plantumlServer
|
server: plantumlServer
|
||||||
})
|
})
|
||||||
} else {
|
)
|
||||||
this.plantumlError(markdownIt)
|
.orElseGet(() => this.plantumlError(markdownIt))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public buildTagNameWhitelist(): string[] {
|
public buildTagNameWhitelist(): string[] {
|
||||||
|
|
|
@ -15,7 +15,7 @@ import markdownItTaskLists from '@hedgedoc/markdown-it-task-lists'
|
||||||
* Adds support for interactive checkbox lists to the markdown rendering using the github checklist syntax.
|
* Adds support for interactive checkbox lists to the markdown rendering using the github checklist syntax.
|
||||||
*/
|
*/
|
||||||
export class TaskListMarkdownExtension extends MarkdownExtension {
|
export class TaskListMarkdownExtension extends MarkdownExtension {
|
||||||
constructor(private frontmatterLinesToSkip: number, private onTaskCheckedChange?: TaskCheckedChangeHandler) {
|
constructor(private onTaskCheckedChange?: TaskCheckedChangeHandler) {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +28,6 @@ export class TaskListMarkdownExtension extends MarkdownExtension {
|
||||||
}
|
}
|
||||||
|
|
||||||
public buildReplacers(): ComponentReplacer[] {
|
public buildReplacers(): ComponentReplacer[] {
|
||||||
return [new TaskListReplacer(this.frontmatterLinesToSkip, this.onTaskCheckedChange)]
|
return [new TaskListReplacer(this.onTaskCheckedChange)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,13 +18,13 @@ export type TaskCheckedChangeHandler = (lineInMarkdown: number, checked: boolean
|
||||||
export class TaskListReplacer extends ComponentReplacer {
|
export class TaskListReplacer extends ComponentReplacer {
|
||||||
onTaskCheckedChange?: (lineInMarkdown: number, checked: boolean) => void
|
onTaskCheckedChange?: (lineInMarkdown: number, checked: boolean) => void
|
||||||
|
|
||||||
constructor(frontmatterLinesToSkip: number, onTaskCheckedChange?: TaskCheckedChangeHandler) {
|
constructor(onTaskCheckedChange?: TaskCheckedChangeHandler) {
|
||||||
super()
|
super()
|
||||||
this.onTaskCheckedChange = (lineInMarkdown, checked) => {
|
this.onTaskCheckedChange = (lineInMarkdown, checked) => {
|
||||||
if (onTaskCheckedChange === undefined) {
|
if (onTaskCheckedChange === undefined) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
onTaskCheckedChange(frontmatterLinesToSkip + lineInMarkdown, checked)
|
onTaskCheckedChange(lineInMarkdown, checked)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { useMarkdownExtensions } from './hooks/use-markdown-extensions'
|
||||||
import type { SlideOptions } from '../../redux/note-details/types/slide-show-options'
|
import type { SlideOptions } from '../../redux/note-details/types/slide-show-options'
|
||||||
|
|
||||||
export interface SlideshowMarkdownRendererProps extends CommonMarkdownRendererProps {
|
export interface SlideshowMarkdownRendererProps extends CommonMarkdownRendererProps {
|
||||||
slideOptions: SlideOptions
|
slideOptions?: SlideOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,7 +33,6 @@ export interface SlideshowMarkdownRendererProps extends CommonMarkdownRendererPr
|
||||||
* @param baseUrl The base url of the renderer
|
* @param baseUrl The base url of the renderer
|
||||||
* @param onImageClick The callback to call if a image is clicked
|
* @param onImageClick The callback to call if a image is clicked
|
||||||
* @param newlinesAreBreaks If newlines are rendered as breaks or not
|
* @param newlinesAreBreaks If newlines are rendered as breaks or not
|
||||||
* @param lineOffset The line offset
|
|
||||||
* @param slideOptions The {@link SlideOptions} to use
|
* @param slideOptions The {@link SlideOptions} to use
|
||||||
*/
|
*/
|
||||||
export const SlideshowMarkdownRenderer: React.FC<SlideshowMarkdownRendererProps & ScrollProps> = ({
|
export const SlideshowMarkdownRenderer: React.FC<SlideshowMarkdownRendererProps & ScrollProps> = ({
|
||||||
|
@ -45,7 +44,6 @@ export const SlideshowMarkdownRenderer: React.FC<SlideshowMarkdownRendererProps
|
||||||
baseUrl,
|
baseUrl,
|
||||||
onImageClick,
|
onImageClick,
|
||||||
newlinesAreBreaks,
|
newlinesAreBreaks,
|
||||||
lineOffset,
|
|
||||||
slideOptions
|
slideOptions
|
||||||
}) => {
|
}) => {
|
||||||
const markdownBodyRef = useRef<HTMLDivElement>(null)
|
const markdownBodyRef = useRef<HTMLDivElement>(null)
|
||||||
|
@ -55,7 +53,6 @@ export const SlideshowMarkdownRenderer: React.FC<SlideshowMarkdownRendererProps
|
||||||
baseUrl,
|
baseUrl,
|
||||||
undefined,
|
undefined,
|
||||||
useMemo(() => [new RevealMarkdownExtension()], []),
|
useMemo(() => [new RevealMarkdownExtension()], []),
|
||||||
lineOffset ?? 0,
|
|
||||||
onTaskCheckedChange,
|
onTaskCheckedChange,
|
||||||
onImageClick,
|
onImageClick,
|
||||||
onTocChange
|
onTocChange
|
||||||
|
|
|
@ -16,8 +16,7 @@ import { countWords } from './word-counter'
|
||||||
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 { useRendererReceiveHandler } from './window-post-message-communicator/hooks/use-renderer-receive-handler'
|
import { useRendererReceiveHandler } from './window-post-message-communicator/hooks/use-renderer-receive-handler'
|
||||||
import { SlideshowMarkdownRenderer } from '../markdown-renderer/slideshow-markdown-renderer'
|
import { SlideshowMarkdownRenderer } from '../markdown-renderer/slideshow-markdown-renderer'
|
||||||
import { initialState } from '../../redux/note-details/initial-state'
|
import type { SlideOptions } from '../../redux/note-details/types/slide-show-options'
|
||||||
import type { RendererFrontmatterInfo } from '../../redux/note-details/types/note-details'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wraps the markdown rendering in an iframe.
|
* Wraps the markdown rendering in an iframe.
|
||||||
|
@ -26,12 +25,17 @@ export const IframeMarkdownRenderer: 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 [frontmatterInfo, setFrontmatterInfo] = useState<RendererFrontmatterInfo>(initialState.frontmatterRendererInfo)
|
const [slideOptions, setSlideOptions] = useState<SlideOptions>()
|
||||||
|
|
||||||
const communicator = useRendererToEditorCommunicator()
|
const communicator = useRendererToEditorCommunicator()
|
||||||
|
|
||||||
const sendScrolling = useRef<boolean>(false)
|
const sendScrolling = useRef<boolean>(false)
|
||||||
|
|
||||||
|
useRendererReceiveHandler(
|
||||||
|
CommunicationMessageType.SET_SLIDE_OPTIONS,
|
||||||
|
useCallback((values) => setSlideOptions(values.slideOptions), [])
|
||||||
|
)
|
||||||
|
|
||||||
useRendererReceiveHandler(
|
useRendererReceiveHandler(
|
||||||
CommunicationMessageType.DISABLE_RENDERER_SCROLL_SOURCE,
|
CommunicationMessageType.DISABLE_RENDERER_SCROLL_SOURCE,
|
||||||
useCallback(() => {
|
useCallback(() => {
|
||||||
|
@ -59,11 +63,6 @@ export const IframeMarkdownRenderer: React.FC = () => {
|
||||||
useCallback((values) => setScrollState(values.scrollState), [])
|
useCallback((values) => setScrollState(values.scrollState), [])
|
||||||
)
|
)
|
||||||
|
|
||||||
useRendererReceiveHandler(
|
|
||||||
CommunicationMessageType.SET_FRONTMATTER_INFO,
|
|
||||||
useCallback((values) => setFrontmatterInfo(values.frontmatterInfo), [])
|
|
||||||
)
|
|
||||||
|
|
||||||
useRendererReceiveHandler(
|
useRendererReceiveHandler(
|
||||||
CommunicationMessageType.GET_WORD_COUNT,
|
CommunicationMessageType.GET_WORD_COUNT,
|
||||||
useCallback(() => {
|
useCallback(() => {
|
||||||
|
@ -145,7 +144,6 @@ export const IframeMarkdownRenderer: React.FC = () => {
|
||||||
onScroll={onScroll}
|
onScroll={onScroll}
|
||||||
baseUrl={baseConfiguration.baseUrl}
|
baseUrl={baseConfiguration.baseUrl}
|
||||||
onImageClick={onImageClick}
|
onImageClick={onImageClick}
|
||||||
frontmatterInfo={frontmatterInfo}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
case RendererType.SLIDESHOW:
|
case RendererType.SLIDESHOW:
|
||||||
|
@ -156,8 +154,7 @@ export const IframeMarkdownRenderer: React.FC = () => {
|
||||||
onFirstHeadingChange={onFirstHeadingChange}
|
onFirstHeadingChange={onFirstHeadingChange}
|
||||||
onImageClick={onImageClick}
|
onImageClick={onImageClick}
|
||||||
scrollState={scrollState}
|
scrollState={scrollState}
|
||||||
lineOffset={frontmatterInfo.lineOffset}
|
slideOptions={slideOptions}
|
||||||
slideOptions={frontmatterInfo.slideOptions}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
case RendererType.INTRO:
|
case RendererType.INTRO:
|
||||||
|
|
|
@ -16,7 +16,6 @@ import styles from './markdown-document.module.scss'
|
||||||
import { WidthBasedTableOfContents } from './width-based-table-of-contents'
|
import { WidthBasedTableOfContents } from './width-based-table-of-contents'
|
||||||
import { ShowIf } from '../common/show-if/show-if'
|
import { ShowIf } from '../common/show-if/show-if'
|
||||||
import { useApplicationState } from '../../hooks/common/use-application-state'
|
import { useApplicationState } from '../../hooks/common/use-application-state'
|
||||||
import type { RendererFrontmatterInfo } from '../../redux/note-details/types/note-details'
|
|
||||||
|
|
||||||
export interface RendererProps extends ScrollProps {
|
export interface RendererProps extends ScrollProps {
|
||||||
onFirstHeadingChange?: (firstHeading: string | undefined) => void
|
onFirstHeadingChange?: (firstHeading: string | undefined) => void
|
||||||
|
@ -32,7 +31,6 @@ export interface MarkdownDocumentProps extends RendererProps {
|
||||||
additionalRendererClasses?: string
|
additionalRendererClasses?: string
|
||||||
disableToc?: boolean
|
disableToc?: boolean
|
||||||
baseUrl: string
|
baseUrl: string
|
||||||
frontmatterInfo?: RendererFrontmatterInfo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -50,7 +48,6 @@ export interface MarkdownDocumentProps extends RendererProps {
|
||||||
* @param scrollState The current {@link ScrollState}
|
* @param scrollState The current {@link ScrollState}
|
||||||
* @param onHeightChange The callback to call if the height of the document changes
|
* @param onHeightChange The callback to call if the height of the document changes
|
||||||
* @param disableToc If the table of contents should be disabled.
|
* @param disableToc If the table of contents should be disabled.
|
||||||
* @param frontmatterInfo The frontmatter information for the renderer.
|
|
||||||
* @see https://markdown-it.github.io/
|
* @see https://markdown-it.github.io/
|
||||||
*/
|
*/
|
||||||
export const MarkdownDocument: React.FC<MarkdownDocumentProps> = ({
|
export const MarkdownDocument: React.FC<MarkdownDocumentProps> = ({
|
||||||
|
@ -65,8 +62,7 @@ export const MarkdownDocument: React.FC<MarkdownDocumentProps> = ({
|
||||||
onScroll,
|
onScroll,
|
||||||
scrollState,
|
scrollState,
|
||||||
onHeightChange,
|
onHeightChange,
|
||||||
disableToc,
|
disableToc
|
||||||
frontmatterInfo
|
|
||||||
}) => {
|
}) => {
|
||||||
const rendererRef = useRef<HTMLDivElement | null>(null)
|
const rendererRef = useRef<HTMLDivElement | null>(null)
|
||||||
const [rendererSize, setRendererSize] = useState<DOMRectReadOnly>()
|
const [rendererSize, setRendererSize] = useState<DOMRectReadOnly>()
|
||||||
|
@ -115,7 +111,6 @@ export const MarkdownDocument: React.FC<MarkdownDocumentProps> = ({
|
||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
onImageClick={onImageClick}
|
onImageClick={onImageClick}
|
||||||
newlinesAreBreaks={newlinesAreBreaks}
|
newlinesAreBreaks={newlinesAreBreaks}
|
||||||
lineOffset={frontmatterInfo?.lineOffset}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={`${styles['markdown-document-side']} pt-4`}>
|
<div className={`${styles['markdown-document-side']} pt-4`}>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { ScrollState } from '../../editor-page/synced-scroll/scroll-props'
|
import type { ScrollState } from '../../editor-page/synced-scroll/scroll-props'
|
||||||
import type { RendererFrontmatterInfo } from '../../../redux/note-details/types/note-details'
|
import type { SlideOptions } from '../../../redux/note-details/types/slide-show-options'
|
||||||
|
|
||||||
export enum CommunicationMessageType {
|
export enum CommunicationMessageType {
|
||||||
SET_MARKDOWN_CONTENT = 'SET_MARKDOWN_CONTENT',
|
SET_MARKDOWN_CONTENT = 'SET_MARKDOWN_CONTENT',
|
||||||
|
@ -20,7 +20,7 @@ export enum CommunicationMessageType {
|
||||||
SET_BASE_CONFIGURATION = 'SET_BASE_CONFIGURATION',
|
SET_BASE_CONFIGURATION = 'SET_BASE_CONFIGURATION',
|
||||||
GET_WORD_COUNT = 'GET_WORD_COUNT',
|
GET_WORD_COUNT = 'GET_WORD_COUNT',
|
||||||
ON_WORD_COUNT_CALCULATED = 'ON_WORD_COUNT_CALCULATED',
|
ON_WORD_COUNT_CALCULATED = 'ON_WORD_COUNT_CALCULATED',
|
||||||
SET_FRONTMATTER_INFO = 'SET_FRONTMATTER_INFO',
|
SET_SLIDE_OPTIONS = 'SET_SLIDE_OPTIONS',
|
||||||
IMAGE_UPLOAD = 'IMAGE_UPLOAD'
|
IMAGE_UPLOAD = 'IMAGE_UPLOAD'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,9 +82,9 @@ export interface OnFirstHeadingChangeMessage {
|
||||||
firstHeading: string | undefined
|
firstHeading: string | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SetFrontmatterInfoMessage {
|
export interface SetSlideOptionsMessage {
|
||||||
type: CommunicationMessageType.SET_FRONTMATTER_INFO
|
type: CommunicationMessageType.SET_SLIDE_OPTIONS
|
||||||
frontmatterInfo: RendererFrontmatterInfo
|
slideOptions: SlideOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OnHeightChangeMessage {
|
export interface OnHeightChangeMessage {
|
||||||
|
@ -109,7 +109,7 @@ export type CommunicationMessages =
|
||||||
| SetScrollStateMessage
|
| SetScrollStateMessage
|
||||||
| OnTaskCheckboxChangeMessage
|
| OnTaskCheckboxChangeMessage
|
||||||
| OnFirstHeadingChangeMessage
|
| OnFirstHeadingChangeMessage
|
||||||
| SetFrontmatterInfoMessage
|
| SetSlideOptionsMessage
|
||||||
| OnHeightChangeMessage
|
| OnHeightChangeMessage
|
||||||
| OnWordCountCalculatedMessage
|
| OnWordCountCalculatedMessage
|
||||||
| ImageUploadMessage
|
| ImageUploadMessage
|
||||||
|
@ -120,7 +120,7 @@ export type EditorToRendererMessageType =
|
||||||
| CommunicationMessageType.SET_SCROLL_STATE
|
| CommunicationMessageType.SET_SCROLL_STATE
|
||||||
| CommunicationMessageType.SET_BASE_CONFIGURATION
|
| CommunicationMessageType.SET_BASE_CONFIGURATION
|
||||||
| CommunicationMessageType.GET_WORD_COUNT
|
| CommunicationMessageType.GET_WORD_COUNT
|
||||||
| CommunicationMessageType.SET_FRONTMATTER_INFO
|
| CommunicationMessageType.SET_SLIDE_OPTIONS
|
||||||
| CommunicationMessageType.DISABLE_RENDERER_SCROLL_SOURCE
|
| CommunicationMessageType.DISABLE_RENDERER_SCROLL_SOURCE
|
||||||
|
|
||||||
export type RendererToEditorMessageType =
|
export type RendererToEditorMessageType =
|
||||||
|
|
|
@ -4,13 +4,17 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import { RendererType } from '../render-page/window-post-message-communicator/rendering-message'
|
import {
|
||||||
|
CommunicationMessageType,
|
||||||
|
RendererType
|
||||||
|
} from '../render-page/window-post-message-communicator/rendering-message'
|
||||||
import { RenderIframe } from '../editor-page/renderer-pane/render-iframe'
|
import { RenderIframe } from '../editor-page/renderer-pane/render-iframe'
|
||||||
import { updateNoteTitleByFirstHeading } from '../../redux/note-details/methods'
|
import { updateNoteTitleByFirstHeading } from '../../redux/note-details/methods'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useSendFrontmatterInfoFromReduxToRenderer } from '../editor-page/renderer-pane/hooks/use-send-frontmatter-info-from-redux-to-renderer'
|
|
||||||
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 { useApplicationState } from '../../hooks/common/use-application-state'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the current markdown content as a slideshow.
|
* Renders the current markdown content as a slideshow.
|
||||||
|
@ -18,7 +22,17 @@ import { useTrimmedNoteMarkdownContentWithoutFrontmatter } from '../../hooks/com
|
||||||
export const SlideShowPageContent: React.FC = () => {
|
export const SlideShowPageContent: React.FC = () => {
|
||||||
const markdownContentLines = useTrimmedNoteMarkdownContentWithoutFrontmatter()
|
const markdownContentLines = useTrimmedNoteMarkdownContentWithoutFrontmatter()
|
||||||
useTranslation()
|
useTranslation()
|
||||||
useSendFrontmatterInfoFromReduxToRenderer()
|
|
||||||
|
const slideOptions = useApplicationState((state) => state.noteDetails.frontmatter.slideOptions)
|
||||||
|
useSendToRenderer(
|
||||||
|
useMemo(
|
||||||
|
() => ({
|
||||||
|
type: CommunicationMessageType.SET_SLIDE_OPTIONS,
|
||||||
|
slideOptions
|
||||||
|
}),
|
||||||
|
[slideOptions]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'vh-100 vw-100'}>
|
<div className={'vh-100 vw-100'}>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue