diff --git a/src/components/document-read-only-page/document-read-only-page-content.tsx b/src/components/document-read-only-page/document-read-only-page-content.tsx index af20694fe..ef37e4928 100644 --- a/src/components/document-read-only-page/document-read-only-page-content.tsx +++ b/src/components/document-read-only-page/document-read-only-page-content.tsx @@ -7,7 +7,6 @@ import React, { Fragment } from 'react' import { useTranslation } from 'react-i18next' 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 { RenderIframe } from '../editor-page/renderer-pane/render-iframe' import { RendererType } from '../render-page/window-post-message-communicator/rendering-message' @@ -20,7 +19,6 @@ export const DocumentReadOnlyPageContent: React.FC = () => { useTranslation() const markdownContentLines = useTrimmedNoteMarkdownContentWithoutFrontmatter() - useSendFrontmatterInfoFromReduxToRenderer() // TODO Change todo values with real ones as soon as the backend is ready. return ( diff --git a/src/components/editor-page/editor-document-renderer/editor-document-renderer.tsx b/src/components/editor-page/editor-document-renderer/editor-document-renderer.tsx index 918ffcff8..4effbd940 100644 --- a/src/components/editor-page/editor-document-renderer/editor-document-renderer.tsx +++ b/src/components/editor-page/editor-document-renderer/editor-document-renderer.tsx @@ -7,12 +7,13 @@ import React from 'react' import type { RenderIframeProps } 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 { NoteType } from '../../../redux/note-details/types/note-details' import { useApplicationState } from '../../../hooks/common/use-application-state' import { RendererType } from '../../render-page/window-post-message-communicator/rendering-message' 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< RenderIframeProps, @@ -22,17 +23,22 @@ export type EditorDocumentRendererProps = Omit< /** * 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 = (props) => { - useSendFrontmatterInfoFromReduxToRenderer() +export const EditorDocumentRenderer: React.FC = ({ scrollState, onScroll, ...props }) => { const trimmedContentLines = useTrimmedNoteMarkdownContentWithoutFrontmatter() const noteType: NoteType = useApplicationState((state) => state.noteDetails.frontmatter.type) const setCheckboxInEditor = useSetCheckboxInEditor() + const adjustedOnScroll = useOnScrollWithLineOffset(onScroll) + const adjustedScrollState = useScrollStateWithoutLineOffset(scrollState) return ( { + return useMemo(() => { + if (onScroll === undefined) { + return undefined + } else { + return (scrollState: ScrollState) => { + onScroll({ + firstLineInView: + scrollState.firstLineInView + getGlobalState().noteDetails.frontmatterRendererInfo.lineOffset, + scrolledPercentage: scrollState.scrolledPercentage + }) + } + } + }, [onScroll]) +} diff --git a/src/components/editor-page/editor-document-renderer/hooks/use-scroll-state-without-line-offset.ts b/src/components/editor-page/editor-document-renderer/hooks/use-scroll-state-without-line-offset.ts new file mode 100644 index 000000000..689fa3d6f --- /dev/null +++ b/src/components/editor-page/editor-document-renderer/hooks/use-scroll-state-without-line-offset.ts @@ -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]) +} diff --git a/src/components/editor-page/editor-pane/hooks/image-upload-from-renderer/use-on-image-upload-from-renderer.ts b/src/components/editor-page/editor-pane/hooks/image-upload-from-renderer/use-on-image-upload-from-renderer.ts index df0ff9983..3ffed9fe2 100644 --- a/src/components/editor-page/editor-pane/hooks/image-upload-from-renderer/use-on-image-upload-from-renderer.ts +++ b/src/components/editor-page/editor-pane/hooks/image-upload-from-renderer/use-on-image-upload-from-renderer.ts @@ -39,7 +39,10 @@ export const useOnImageUploadFromRenderer = (): void => { .then((blob) => { const file = new File([blob], fileName, { type: blob.type }) 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) handleUpload(file, cursorSelection, alt, title) }) diff --git a/src/components/editor-page/renderer-pane/hooks/use-send-frontmatter-info-from-redux-to-renderer.ts b/src/components/editor-page/renderer-pane/hooks/use-send-frontmatter-info-from-redux-to-renderer.ts deleted file mode 100644 index 19834216b..000000000 --- a/src/components/editor-page/renderer-pane/hooks/use-send-frontmatter-info-from-redux-to-renderer.ts +++ /dev/null @@ -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(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] - ) - ) -} diff --git a/src/components/editor-page/renderer-pane/render-iframe.tsx b/src/components/editor-page/renderer-pane/render-iframe.tsx index a4b46f044..df4f93208 100644 --- a/src/components/editor-page/renderer-pane/render-iframe.tsx +++ b/src/components/editor-page/renderer-pane/render-iframe.tsx @@ -27,6 +27,7 @@ import { Logger } from '../../../utils/logger' import { useEffectOnRenderTypeChange } from './hooks/use-effect-on-render-type-change' import { cypressAttribute, cypressId } from '../../../utils/cypress-attribute' import { ORIGIN_TYPE, useOriginFromConfig } from '../render-context/use-origin-from-config' +import { getGlobalState } from '../../../redux' export interface RenderIframeProps extends RendererProps { rendererType: RendererType @@ -89,11 +90,6 @@ export const RenderIframe: React.FC = ({ ) ) - useEditorReceiveHandler( - CommunicationMessageType.SET_SCROLL_STATE, - useCallback((values: SetScrollStateMessage) => onScroll?.(values.scrollState), [onScroll]) - ) - useEditorReceiveHandler( CommunicationMessageType.ENABLE_RENDERER_SCROLL_SOURCE, useCallback(() => onMakeScrollSource?.(), [onMakeScrollSource]) @@ -102,7 +98,10 @@ export const RenderIframe: React.FC = ({ useEditorReceiveHandler( CommunicationMessageType.ON_TASK_CHECKBOX_CHANGE, useCallback( - (values: OnTaskCheckboxChangeMessage) => onTaskCheckedChange?.(values.lineInMarkdown, values.checked), + (values: OnTaskCheckboxChangeMessage) => { + const lineOffset = getGlobalState().noteDetails.frontmatterRendererInfo.lineOffset + onTaskCheckedChange?.(values.lineInMarkdown + lineOffset, values.checked) + }, [onTaskCheckedChange] ) ) @@ -140,10 +139,16 @@ export const RenderIframe: React.FC = ({ ) useEffectOnRenderTypeChange(rendererType, onIframeLoad) - useSendScrollState(scrollState) useSendDarkModeStatusToRenderer(forcedDarkMode) useSendMarkdownToRenderer(markdownContentLines) + useSendScrollState(scrollState) + + useEditorReceiveHandler( + CommunicationMessageType.SET_SCROLL_STATE, + useCallback((values: SetScrollStateMessage) => onScroll?.(values.scrollState), [onScroll]) + ) + return ( diff --git a/src/components/editor-page/synced-scroll/scroll-props.ts b/src/components/editor-page/synced-scroll/scroll-props.ts index 7aed04764..a81ba1839 100644 --- a/src/components/editor-page/synced-scroll/scroll-props.ts +++ b/src/components/editor-page/synced-scroll/scroll-props.ts @@ -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 */ +export type ScrollCallback = (scrollState: ScrollState) => void + export interface ScrollProps { scrollState?: ScrollState - onScroll?: (scrollState: ScrollState) => void + onScroll?: ScrollCallback onMakeScrollSource?: () => void } diff --git a/src/components/markdown-renderer/document-markdown-renderer.tsx b/src/components/markdown-renderer/document-markdown-renderer.tsx index 12ca9d54f..60140d526 100644 --- a/src/components/markdown-renderer/document-markdown-renderer.tsx +++ b/src/components/markdown-renderer/document-markdown-renderer.tsx @@ -33,7 +33,6 @@ export interface DocumentMarkdownRendererProps extends CommonMarkdownRendererPro * @param onImageClick The callback to call if a image is clicked * @param outerContainerRef A reference for the outer container * @param newlinesAreBreaks If newlines are rendered as breaks or not - * @param lineOffset The line offset */ export const DocumentMarkdownRenderer: React.FC = ({ className, @@ -45,8 +44,7 @@ export const DocumentMarkdownRenderer: React.FC = baseUrl, onImageClick, outerContainerRef, - newlinesAreBreaks, - lineOffset + newlinesAreBreaks }) => { const markdownBodyRef = useRef(null) const currentLineMarkers = useRef() @@ -55,7 +53,6 @@ export const DocumentMarkdownRenderer: React.FC = baseUrl, currentLineMarkers, useMemo(() => [new HeadlineAnchorsMarkdownExtension()], []), - lineOffset ?? 0, onTaskCheckedChange, onImageClick, onTocChange diff --git a/src/components/markdown-renderer/hooks/use-markdown-extensions.ts b/src/components/markdown-renderer/hooks/use-markdown-extensions.ts index f47a4e27d..ede8ac9ba 100644 --- a/src/components/markdown-renderer/hooks/use-markdown-extensions.ts +++ b/src/components/markdown-renderer/hooks/use-markdown-extensions.ts @@ -34,7 +34,6 @@ import { SpoilerMarkdownExtension } from '../markdown-extension/spoiler-markdown import { LinkifyFixMarkdownExtension } from '../markdown-extension/linkify-fix-markdown-extension' import { HighlightedCodeMarkdownExtension } from '../markdown-extension/highlighted-fence/highlighted-code-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 { ImageClickHandler } from '../markdown-extension/image/proxy-image-replacer' 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 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 lineOffset The line offset for the {@link LinemarkerMarkdownExtension} and {@link TaskListMarkdownExtension} * @param onTaskCheckedChange The checkbox click callback for the {@link TaskListMarkdownExtension} * @param onImageClick The image click callback for the {@link ProxyImageMarkdownExtension} * @param onTocChange The toc-changed callback for the {@link TableOfContentsMarkdownExtension} @@ -60,12 +58,10 @@ export const useMarkdownExtensions = ( baseUrl: string, currentLineMarkers: MutableRefObject | undefined, additionalExtensions: MarkdownExtension[], - lineOffset: number, onTaskCheckedChange?: (lineInMarkdown: number, checked: boolean) => void, onImageClick?: ImageClickHandler, onTocChange?: (ast?: TocAst) => void ): MarkdownExtension[] => { - const plantumlServer = useApplicationState((state) => state.config.plantumlServer) const toc = useRef(undefined) useOnRefChange(toc, onTocChange) @@ -76,11 +72,10 @@ export const useMarkdownExtensions = ( new VegaLiteMarkdownExtension(), // new MarkmapMarkdownExtension(), new LinemarkerMarkdownExtension( - lineOffset, currentLineMarkers ? (lineMarkers) => (currentLineMarkers.current = lineMarkers) : undefined ), new IframeCapsuleMarkdownExtension(), - new ImagePlaceholderMarkdownExtension(lineOffset), + new ImagePlaceholderMarkdownExtension(), new UploadIndicatingImageFrameMarkdownExtension(), new GistMarkdownExtension(), new YoutubeMarkdownExtension(), @@ -95,8 +90,8 @@ export const useMarkdownExtensions = ( new BlockquoteExtraTagMarkdownExtension(), new LinkAdjustmentMarkdownExtension(baseUrl), new KatexMarkdownExtension(), - new TaskListMarkdownExtension(lineOffset, onTaskCheckedChange), - new PlantumlMarkdownExtension(plantumlServer), + new TaskListMarkdownExtension(onTaskCheckedChange), + new PlantumlMarkdownExtension(), new LegacyShortcodesMarkdownExtension(), new EmojiMarkdownExtension(), new GenericSyntaxMarkdownExtension(), @@ -106,5 +101,5 @@ export const useMarkdownExtensions = ( new HighlightedCodeMarkdownExtension(), new DebuggerMarkdownExtension() ] - }, [additionalExtensions, baseUrl, currentLineMarkers, lineOffset, onImageClick, onTaskCheckedChange, plantumlServer]) + }, [additionalExtensions, baseUrl, currentLineMarkers, onImageClick, onTaskCheckedChange]) } diff --git a/src/components/markdown-renderer/markdown-extension/image-placeholder/image-placeholder-markdown-extension.ts b/src/components/markdown-renderer/markdown-extension/image-placeholder/image-placeholder-markdown-extension.ts index 3cd055162..3ebd11418 100644 --- a/src/components/markdown-renderer/markdown-extension/image-placeholder/image-placeholder-markdown-extension.ts +++ b/src/components/markdown-renderer/markdown-extension/image-placeholder/image-placeholder-markdown-extension.ts @@ -16,7 +16,7 @@ import { ImagePlaceholderReplacer } from './image-placeholder-replacer' export class ImagePlaceholderMarkdownExtension extends MarkdownExtension { public static readonly PLACEHOLDER_URL = 'https://' - constructor(private lineOffset: number) { + constructor() { super() } @@ -25,6 +25,6 @@ export class ImagePlaceholderMarkdownExtension extends MarkdownExtension { } buildReplacers(): ComponentReplacer[] { - return [new ImagePlaceholderReplacer(this.lineOffset)] + return [new ImagePlaceholderReplacer()] } } diff --git a/src/components/markdown-renderer/markdown-extension/image-placeholder/image-placeholder-replacer.tsx b/src/components/markdown-renderer/markdown-extension/image-placeholder/image-placeholder-replacer.tsx index 6e6273f9c..f7b2556f0 100644 --- a/src/components/markdown-renderer/markdown-extension/image-placeholder/image-placeholder-replacer.tsx +++ b/src/components/markdown-renderer/markdown-extension/image-placeholder/image-placeholder-replacer.tsx @@ -16,7 +16,7 @@ import { ImagePlaceholderMarkdownExtension } from './image-placeholder-markdown- export class ImagePlaceholderReplacer extends ComponentReplacer { private countPerSourceLine = new Map() - constructor(private lineOffset: number) { + constructor() { super() } @@ -35,7 +35,7 @@ export class ImagePlaceholderReplacer extends ComponentReplacer { title={node.attribs.title} width={node.attribs.width} height={node.attribs.height} - lineIndex={isNaN(lineIndex) ? undefined : lineIndex + this.lineOffset} + lineIndex={isNaN(lineIndex) ? undefined : lineIndex} placeholderIndexInLine={indexInLine} /> ) diff --git a/src/components/markdown-renderer/markdown-extension/linemarker/add-line-marker-markdown-it-plugin.ts b/src/components/markdown-renderer/markdown-extension/linemarker/add-line-marker-markdown-it-plugin.ts index d7a3ae533..324a2d82d 100644 --- a/src/components/markdown-renderer/markdown-extension/linemarker/add-line-marker-markdown-it-plugin.ts +++ b/src/components/markdown-renderer/markdown-extension/linemarker/add-line-marker-markdown-it-plugin.ts @@ -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 */ @@ -13,19 +13,53 @@ export interface LineMarkers { 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. * It also provides a list of line numbers for the top level dom elements. */ export const addLineMarkerMarkdownItPlugin: ( markdownIt: MarkdownIt, - lineOffset: number, onLineMarkerChange?: (lineMarkers: LineMarkers[]) => void -) => void = (md: MarkdownIt, lineOffset, onLineMarkerChange) => { - // add app_linemarker token before each opening or self-closing level-0 tag +) => void = (md, onLineMarkerChange) => { md.core.ruler.push('line_number_marker', (state) => { const lineMarkers: LineMarkers[] = [] - tagTokens(state.tokens, lineMarkers, lineOffset) + tagTokens(state.tokens, lineMarkers) if (onLineMarkerChange) { onLineMarkerChange(lineMarkers) } @@ -36,52 +70,8 @@ export const addLineMarkerMarkdownItPlugin: ( const startLineNumber = tokens[index].attrGet('data-start-line') const endLineNumber = tokens[index].attrGet('data-end-line') - if (!startLineNumber || !endLineNumber) { - // don't render broken linemarkers without a linenumber - return '' - } - // noinspection CheckTagEmptyBody - return `<${LinemarkerMarkdownExtension.tagName} data-start-line='${startLineNumber}' data-end-line='${endLineNumber}'>` - } - - 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) - } - } + return startLineNumber && endLineNumber + ? `<${LinemarkerMarkdownExtension.tagName} data-start-line='${startLineNumber}' data-end-line='${endLineNumber}'>` + : '' } } diff --git a/src/components/markdown-renderer/markdown-extension/linemarker/linemarker-markdown-extension.ts b/src/components/markdown-renderer/markdown-extension/linemarker/linemarker-markdown-extension.ts index 309c591a9..07f524820 100644 --- a/src/components/markdown-renderer/markdown-extension/linemarker/linemarker-markdown-extension.ts +++ b/src/components/markdown-renderer/markdown-extension/linemarker/linemarker-markdown-extension.ts @@ -17,12 +17,12 @@ import type MarkdownIt from 'markdown-it' export class LinemarkerMarkdownExtension extends MarkdownExtension { public static readonly tagName = 'app-linemarker' - constructor(private lineOffset: number, private onLineMarkers?: (lineMarkers: LineMarkers[]) => void) { + constructor(private onLineMarkers?: (lineMarkers: LineMarkers[]) => void) { super() } public configureMarkdownIt(markdownIt: MarkdownIt): void { - addLineMarkerMarkdownItPlugin(markdownIt, this.lineOffset ?? 0, this.onLineMarkers) + addLineMarkerMarkdownItPlugin(markdownIt, this.onLineMarkers) } public buildReplacers(): ComponentReplacer[] { diff --git a/src/components/markdown-renderer/markdown-extension/plantuml/__snapshots__/plantuml-markdown-extension.test.tsx.snap b/src/components/markdown-renderer/markdown-extension/plantuml/__snapshots__/plantuml-markdown-extension.test.tsx.snap index 727040cc3..3a3dc2e3b 100644 --- a/src/components/markdown-renderer/markdown-extension/plantuml/__snapshots__/plantuml-markdown-extension.test.tsx.snap +++ b/src/components/markdown-renderer/markdown-extension/plantuml/__snapshots__/plantuml-markdown-extension.test.tsx.snap @@ -4,7 +4,7 @@ exports[`PlantUML markdown extensions renders a plantuml codeblock 1`] = `
uml diagram diff --git a/src/components/markdown-renderer/markdown-extension/plantuml/plantuml-markdown-extension.test.tsx b/src/components/markdown-renderer/markdown-extension/plantuml/plantuml-markdown-extension.test.tsx index 4414cbf1f..fa051b490 100644 --- a/src/components/markdown-renderer/markdown-extension/plantuml/plantuml-markdown-extension.test.tsx +++ b/src/components/markdown-renderer/markdown-extension/plantuml/plantuml-markdown-extension.test.tsx @@ -9,6 +9,9 @@ import { TestMarkdownRenderer } from '../../test-utils/test-markdown-renderer' import React from 'react' import { PlantumlMarkdownExtension } from './plantuml-markdown-extension' 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', () => { beforeAll(async () => { @@ -16,9 +19,17 @@ describe('PlantUML markdown extensions', () => { }) it('renders a plantuml codeblock', () => { + jest.spyOn(reduxModule, 'getGlobalState').mockReturnValue( + Mock.of({ + config: { + plantumlServer: 'https://example.org' + } + }) + ) + const view = render( ) @@ -26,9 +37,17 @@ describe('PlantUML markdown extensions', () => { }) it('renders an error if no server is defined', () => { + jest.spyOn(reduxModule, 'getGlobalState').mockReturnValue( + Mock.of({ + config: { + plantumlServer: undefined + } + }) + ) + const view = render( ) diff --git a/src/components/markdown-renderer/markdown-extension/plantuml/plantuml-markdown-extension.ts b/src/components/markdown-renderer/markdown-extension/plantuml/plantuml-markdown-extension.ts index 5fd75c08a..64e732649 100644 --- a/src/components/markdown-renderer/markdown-extension/plantuml/plantuml-markdown-extension.ts +++ b/src/components/markdown-renderer/markdown-extension/plantuml/plantuml-markdown-extension.ts @@ -12,6 +12,8 @@ import type Token from 'markdown-it/lib/token' import type { Options } from 'markdown-it/lib' import type { ComponentReplacer } from '../../replace-components/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. @@ -19,7 +21,7 @@ import { PlantumlNotConfiguredComponentReplacer } from './plantuml-not-configure * @see https://plantuml.com */ export class PlantumlMarkdownExtension extends MarkdownExtension { - constructor(private plantumlServer?: string) { + constructor() { super() } @@ -33,15 +35,15 @@ export class PlantumlMarkdownExtension extends MarkdownExtension { } public configureMarkdownIt(markdownIt: MarkdownIt): void { - if (this.plantumlServer) { - plantuml(markdownIt, { - openMarker: '```plantuml', - closeMarker: '```', - server: this.plantumlServer - }) - } else { - this.plantumlError(markdownIt) - } + Optional.ofNullable(getGlobalState().config.plantumlServer) + .map((plantumlServer) => + plantuml(markdownIt, { + openMarker: '```plantuml', + closeMarker: '```', + server: plantumlServer + }) + ) + .orElseGet(() => this.plantumlError(markdownIt)) } public buildTagNameWhitelist(): string[] { diff --git a/src/components/markdown-renderer/markdown-extension/task-list/task-list-markdown-extension.ts b/src/components/markdown-renderer/markdown-extension/task-list/task-list-markdown-extension.ts index 9d65681f5..4f8a89ca8 100644 --- a/src/components/markdown-renderer/markdown-extension/task-list/task-list-markdown-extension.ts +++ b/src/components/markdown-renderer/markdown-extension/task-list/task-list-markdown-extension.ts @@ -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. */ export class TaskListMarkdownExtension extends MarkdownExtension { - constructor(private frontmatterLinesToSkip: number, private onTaskCheckedChange?: TaskCheckedChangeHandler) { + constructor(private onTaskCheckedChange?: TaskCheckedChangeHandler) { super() } @@ -28,6 +28,6 @@ export class TaskListMarkdownExtension extends MarkdownExtension { } public buildReplacers(): ComponentReplacer[] { - return [new TaskListReplacer(this.frontmatterLinesToSkip, this.onTaskCheckedChange)] + return [new TaskListReplacer(this.onTaskCheckedChange)] } } diff --git a/src/components/markdown-renderer/markdown-extension/task-list/task-list-replacer.tsx b/src/components/markdown-renderer/markdown-extension/task-list/task-list-replacer.tsx index b6e3093b7..b96aa22df 100644 --- a/src/components/markdown-renderer/markdown-extension/task-list/task-list-replacer.tsx +++ b/src/components/markdown-renderer/markdown-extension/task-list/task-list-replacer.tsx @@ -18,13 +18,13 @@ export type TaskCheckedChangeHandler = (lineInMarkdown: number, checked: boolean export class TaskListReplacer extends ComponentReplacer { onTaskCheckedChange?: (lineInMarkdown: number, checked: boolean) => void - constructor(frontmatterLinesToSkip: number, onTaskCheckedChange?: TaskCheckedChangeHandler) { + constructor(onTaskCheckedChange?: TaskCheckedChangeHandler) { super() this.onTaskCheckedChange = (lineInMarkdown, checked) => { if (onTaskCheckedChange === undefined) { return } - onTaskCheckedChange(frontmatterLinesToSkip + lineInMarkdown, checked) + onTaskCheckedChange(lineInMarkdown, checked) } } diff --git a/src/components/markdown-renderer/slideshow-markdown-renderer.tsx b/src/components/markdown-renderer/slideshow-markdown-renderer.tsx index 4a7d43ab7..0460482b3 100644 --- a/src/components/markdown-renderer/slideshow-markdown-renderer.tsx +++ b/src/components/markdown-renderer/slideshow-markdown-renderer.tsx @@ -18,7 +18,7 @@ import { useMarkdownExtensions } from './hooks/use-markdown-extensions' import type { SlideOptions } from '../../redux/note-details/types/slide-show-options' 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 onImageClick The callback to call if a image is clicked * @param newlinesAreBreaks If newlines are rendered as breaks or not - * @param lineOffset The line offset * @param slideOptions The {@link SlideOptions} to use */ export const SlideshowMarkdownRenderer: React.FC = ({ @@ -45,7 +44,6 @@ export const SlideshowMarkdownRenderer: React.FC { const markdownBodyRef = useRef(null) @@ -55,7 +53,6 @@ export const SlideshowMarkdownRenderer: React.FC [new RevealMarkdownExtension()], []), - lineOffset ?? 0, onTaskCheckedChange, onImageClick, onTocChange diff --git a/src/components/render-page/iframe-markdown-renderer.tsx b/src/components/render-page/iframe-markdown-renderer.tsx index 8b23f1a95..ab905f447 100644 --- a/src/components/render-page/iframe-markdown-renderer.tsx +++ b/src/components/render-page/iframe-markdown-renderer.tsx @@ -16,8 +16,7 @@ import { countWords } from './word-counter' 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 { SlideshowMarkdownRenderer } from '../markdown-renderer/slideshow-markdown-renderer' -import { initialState } from '../../redux/note-details/initial-state' -import type { RendererFrontmatterInfo } from '../../redux/note-details/types/note-details' +import type { SlideOptions } from '../../redux/note-details/types/slide-show-options' /** * Wraps the markdown rendering in an iframe. @@ -26,12 +25,17 @@ export const IframeMarkdownRenderer: React.FC = () => { const [markdownContentLines, setMarkdownContentLines] = useState([]) const [scrollState, setScrollState] = useState({ firstLineInView: 1, scrolledPercentage: 0 }) const [baseConfiguration, setBaseConfiguration] = useState(undefined) - const [frontmatterInfo, setFrontmatterInfo] = useState(initialState.frontmatterRendererInfo) + const [slideOptions, setSlideOptions] = useState() const communicator = useRendererToEditorCommunicator() const sendScrolling = useRef(false) + useRendererReceiveHandler( + CommunicationMessageType.SET_SLIDE_OPTIONS, + useCallback((values) => setSlideOptions(values.slideOptions), []) + ) + useRendererReceiveHandler( CommunicationMessageType.DISABLE_RENDERER_SCROLL_SOURCE, useCallback(() => { @@ -59,11 +63,6 @@ export const IframeMarkdownRenderer: React.FC = () => { useCallback((values) => setScrollState(values.scrollState), []) ) - useRendererReceiveHandler( - CommunicationMessageType.SET_FRONTMATTER_INFO, - useCallback((values) => setFrontmatterInfo(values.frontmatterInfo), []) - ) - useRendererReceiveHandler( CommunicationMessageType.GET_WORD_COUNT, useCallback(() => { @@ -145,7 +144,6 @@ export const IframeMarkdownRenderer: React.FC = () => { onScroll={onScroll} baseUrl={baseConfiguration.baseUrl} onImageClick={onImageClick} - frontmatterInfo={frontmatterInfo} /> ) case RendererType.SLIDESHOW: @@ -156,8 +154,7 @@ export const IframeMarkdownRenderer: React.FC = () => { onFirstHeadingChange={onFirstHeadingChange} onImageClick={onImageClick} scrollState={scrollState} - lineOffset={frontmatterInfo.lineOffset} - slideOptions={frontmatterInfo.slideOptions} + slideOptions={slideOptions} /> ) case RendererType.INTRO: diff --git a/src/components/render-page/markdown-document.tsx b/src/components/render-page/markdown-document.tsx index f28771f49..696a7c814 100644 --- a/src/components/render-page/markdown-document.tsx +++ b/src/components/render-page/markdown-document.tsx @@ -16,7 +16,6 @@ import styles from './markdown-document.module.scss' import { WidthBasedTableOfContents } from './width-based-table-of-contents' import { ShowIf } from '../common/show-if/show-if' import { useApplicationState } from '../../hooks/common/use-application-state' -import type { RendererFrontmatterInfo } from '../../redux/note-details/types/note-details' export interface RendererProps extends ScrollProps { onFirstHeadingChange?: (firstHeading: string | undefined) => void @@ -32,7 +31,6 @@ export interface MarkdownDocumentProps extends RendererProps { additionalRendererClasses?: string disableToc?: boolean baseUrl: string - frontmatterInfo?: RendererFrontmatterInfo } /** @@ -50,7 +48,6 @@ export interface MarkdownDocumentProps extends RendererProps { * @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 frontmatterInfo The frontmatter information for the renderer. * @see https://markdown-it.github.io/ */ export const MarkdownDocument: React.FC = ({ @@ -65,8 +62,7 @@ export const MarkdownDocument: React.FC = ({ onScroll, scrollState, onHeightChange, - disableToc, - frontmatterInfo + disableToc }) => { const rendererRef = useRef(null) const [rendererSize, setRendererSize] = useState() @@ -115,7 +111,6 @@ export const MarkdownDocument: React.FC = ({ baseUrl={baseUrl} onImageClick={onImageClick} newlinesAreBreaks={newlinesAreBreaks} - lineOffset={frontmatterInfo?.lineOffset} />
diff --git a/src/components/render-page/window-post-message-communicator/rendering-message.ts b/src/components/render-page/window-post-message-communicator/rendering-message.ts index b1d624c5f..d4b6b9c07 100644 --- a/src/components/render-page/window-post-message-communicator/rendering-message.ts +++ b/src/components/render-page/window-post-message-communicator/rendering-message.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ 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 { SET_MARKDOWN_CONTENT = 'SET_MARKDOWN_CONTENT', @@ -20,7 +20,7 @@ export enum CommunicationMessageType { SET_BASE_CONFIGURATION = 'SET_BASE_CONFIGURATION', GET_WORD_COUNT = 'GET_WORD_COUNT', ON_WORD_COUNT_CALCULATED = 'ON_WORD_COUNT_CALCULATED', - SET_FRONTMATTER_INFO = 'SET_FRONTMATTER_INFO', + SET_SLIDE_OPTIONS = 'SET_SLIDE_OPTIONS', IMAGE_UPLOAD = 'IMAGE_UPLOAD' } @@ -82,9 +82,9 @@ export interface OnFirstHeadingChangeMessage { firstHeading: string | undefined } -export interface SetFrontmatterInfoMessage { - type: CommunicationMessageType.SET_FRONTMATTER_INFO - frontmatterInfo: RendererFrontmatterInfo +export interface SetSlideOptionsMessage { + type: CommunicationMessageType.SET_SLIDE_OPTIONS + slideOptions: SlideOptions } export interface OnHeightChangeMessage { @@ -109,7 +109,7 @@ export type CommunicationMessages = | SetScrollStateMessage | OnTaskCheckboxChangeMessage | OnFirstHeadingChangeMessage - | SetFrontmatterInfoMessage + | SetSlideOptionsMessage | OnHeightChangeMessage | OnWordCountCalculatedMessage | ImageUploadMessage @@ -120,7 +120,7 @@ export type EditorToRendererMessageType = | CommunicationMessageType.SET_SCROLL_STATE | CommunicationMessageType.SET_BASE_CONFIGURATION | CommunicationMessageType.GET_WORD_COUNT - | CommunicationMessageType.SET_FRONTMATTER_INFO + | CommunicationMessageType.SET_SLIDE_OPTIONS | CommunicationMessageType.DISABLE_RENDERER_SCROLL_SOURCE export type RendererToEditorMessageType = diff --git a/src/components/slide-show-page/slide-show-page-content.tsx b/src/components/slide-show-page/slide-show-page-content.tsx index e1fc08c8e..77362b012 100644 --- a/src/components/slide-show-page/slide-show-page-content.tsx +++ b/src/components/slide-show-page/slide-show-page-content.tsx @@ -4,13 +4,17 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import React from 'react' -import { RendererType } from '../render-page/window-post-message-communicator/rendering-message' +import React, { useMemo } from 'react' +import { + CommunicationMessageType, + RendererType +} from '../render-page/window-post-message-communicator/rendering-message' import { RenderIframe } from '../editor-page/renderer-pane/render-iframe' import { updateNoteTitleByFirstHeading } from '../../redux/note-details/methods' 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 { 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. @@ -18,7 +22,17 @@ import { useTrimmedNoteMarkdownContentWithoutFrontmatter } from '../../hooks/com export const SlideShowPageContent: React.FC = () => { const markdownContentLines = useTrimmedNoteMarkdownContentWithoutFrontmatter() useTranslation() - useSendFrontmatterInfoFromReduxToRenderer() + + const slideOptions = useApplicationState((state) => state.noteDetails.frontmatter.slideOptions) + useSendToRenderer( + useMemo( + () => ({ + type: CommunicationMessageType.SET_SLIDE_OPTIONS, + slideOptions + }), + [slideOptions] + ) + ) return (