Don't send frontmatter to renderer (#2259)

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2022-08-04 12:40:37 +02:00 committed by GitHub
parent c50037fc9f
commit f68b3ff056
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 215 additions and 176 deletions

View file

@ -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 (

View file

@ -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<EditorDocumentRendererProps> = (props) => {
useSendFrontmatterInfoFromReduxToRenderer()
export const EditorDocumentRenderer: React.FC<EditorDocumentRendererProps> = ({ 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 (
<RenderIframe
{...props}
onScroll={adjustedOnScroll}
scrollState={adjustedScrollState}
onTaskCheckedChange={setCheckboxInEditor}
rendererType={noteType === NoteType.SLIDE ? RendererType.SLIDESHOW : RendererType.DOCUMENT}
markdownContentLines={trimmedContentLines}

View file

@ -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])
}

View file

@ -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])
}

View file

@ -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)
})

View file

@ -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]
)
)
}

View file

@ -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<RenderIframeProps> = ({
)
)
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<RenderIframeProps> = ({
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<RenderIframeProps> = ({
)
useEffectOnRenderTypeChange(rendererType, onIframeLoad)
useSendScrollState(scrollState)
useSendDarkModeStatusToRenderer(forcedDarkMode)
useSendMarkdownToRenderer(markdownContentLines)
useSendScrollState(scrollState)
useEditorReceiveHandler(
CommunicationMessageType.SET_SCROLL_STATE,
useCallback((values: SetScrollStateMessage) => onScroll?.(values.scrollState), [onScroll])
)
return (
<Fragment>
<CommunicatorImageLightbox />

View file

@ -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
}

View file

@ -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<DocumentMarkdownRendererProps> = ({
className,
@ -45,8 +44,7 @@ export const DocumentMarkdownRenderer: React.FC<DocumentMarkdownRendererProps> =
baseUrl,
onImageClick,
outerContainerRef,
newlinesAreBreaks,
lineOffset
newlinesAreBreaks
}) => {
const markdownBodyRef = useRef<HTMLDivElement>(null)
const currentLineMarkers = useRef<LineMarkers[]>()
@ -55,7 +53,6 @@ export const DocumentMarkdownRenderer: React.FC<DocumentMarkdownRendererProps> =
baseUrl,
currentLineMarkers,
useMemo(() => [new HeadlineAnchorsMarkdownExtension()], []),
lineOffset ?? 0,
onTaskCheckedChange,
onImageClick,
onTocChange

View file

@ -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<LineMarkers[] | undefined> | 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<TocAst | undefined>(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])
}

View file

@ -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()]
}
}

View file

@ -16,7 +16,7 @@ import { ImagePlaceholderMarkdownExtension } from './image-placeholder-markdown-
export class ImagePlaceholderReplacer extends ComponentReplacer {
private countPerSourceLine = new Map<number, number>()
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}
/>
)

View file

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -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}'></${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)
}
}
return startLineNumber && endLineNumber
? `<${LinemarkerMarkdownExtension.tagName} data-start-line='${startLineNumber}' data-end-line='${endLineNumber}'></${LinemarkerMarkdownExtension.tagName}>`
: ''
}
}

View file

@ -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[] {

View file

@ -4,7 +4,7 @@ exports[`PlantUML markdown extensions renders a plantuml codeblock 1`] = `
<div>
<img
alt="uml diagram"
src="http://example.org/svg/SoWkIImgAStDuKhEIImkLd2jICmjo4dbSaZDIm6A0W00"
src="https://example.org/svg/SoWkIImgAStDuKhEIImkLd2jICmjo4dbSaZDIm6A0W00"
/>

View file

@ -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<ApplicationState>({
config: {
plantumlServer: 'https://example.org'
}
})
)
const view = render(
<TestMarkdownRenderer
extensions={[new PlantumlMarkdownExtension('http://example.org')]}
extensions={[new PlantumlMarkdownExtension()]}
content={'```plantuml\nclass Example\n```'}
/>
)
@ -26,9 +37,17 @@ describe('PlantUML markdown extensions', () => {
})
it('renders an error if no server is defined', () => {
jest.spyOn(reduxModule, 'getGlobalState').mockReturnValue(
Mock.of<ApplicationState>({
config: {
plantumlServer: undefined
}
})
)
const view = render(
<TestMarkdownRenderer
extensions={[new PlantumlMarkdownExtension(null)]}
extensions={[new PlantumlMarkdownExtension()]}
content={'```plantuml\nclass Example\n```'}
/>
)

View file

@ -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[] {

View file

@ -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)]
}
}

View file

@ -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)
}
}

View file

@ -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<SlideshowMarkdownRendererProps & ScrollProps> = ({
@ -45,7 +44,6 @@ export const SlideshowMarkdownRenderer: React.FC<SlideshowMarkdownRendererProps
baseUrl,
onImageClick,
newlinesAreBreaks,
lineOffset,
slideOptions
}) => {
const markdownBodyRef = useRef<HTMLDivElement>(null)
@ -55,7 +53,6 @@ export const SlideshowMarkdownRenderer: React.FC<SlideshowMarkdownRendererProps
baseUrl,
undefined,
useMemo(() => [new RevealMarkdownExtension()], []),
lineOffset ?? 0,
onTaskCheckedChange,
onImageClick,
onTocChange

View file

@ -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<string[]>([])
const [scrollState, setScrollState] = useState<ScrollState>({ firstLineInView: 1, scrolledPercentage: 0 })
const [baseConfiguration, setBaseConfiguration] = useState<BaseConfiguration | undefined>(undefined)
const [frontmatterInfo, setFrontmatterInfo] = useState<RendererFrontmatterInfo>(initialState.frontmatterRendererInfo)
const [slideOptions, setSlideOptions] = useState<SlideOptions>()
const communicator = useRendererToEditorCommunicator()
const sendScrolling = useRef<boolean>(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:

View file

@ -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<MarkdownDocumentProps> = ({
@ -65,8 +62,7 @@ export const MarkdownDocument: React.FC<MarkdownDocumentProps> = ({
onScroll,
scrollState,
onHeightChange,
disableToc,
frontmatterInfo
disableToc
}) => {
const rendererRef = useRef<HTMLDivElement | null>(null)
const [rendererSize, setRendererSize] = useState<DOMRectReadOnly>()
@ -115,7 +111,6 @@ export const MarkdownDocument: React.FC<MarkdownDocumentProps> = ({
baseUrl={baseUrl}
onImageClick={onImageClick}
newlinesAreBreaks={newlinesAreBreaks}
lineOffset={frontmatterInfo?.lineOffset}
/>
</div>
<div className={`${styles['markdown-document-side']} pt-4`}>

View file

@ -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 =

View file

@ -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 (
<div className={'vh-100 vw-100'}>