diff --git a/frontend/src/components/common/html-to-react/__snapshots__/html-to-react.test.tsx.snap b/frontend/src/components/common/html-to-react/__snapshots__/html-to-react.test.tsx.snap
new file mode 100644
index 000000000..83f048ac9
--- /dev/null
+++ b/frontend/src/components/common/html-to-react/__snapshots__/html-to-react.test.tsx.snap
@@ -0,0 +1,30 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`HTML to React renders basic html correctly 1`] = `
+
+
+ This is a test
+
+ sentence
+
+
+
+`;
+
+exports[`HTML to React will forward the DomPurify settings 1`] = `
+
+
+ Test!
+
+
+`;
+
+exports[`HTML to React will forward the parser options 1`] = `
+
+`;
+
+exports[`HTML to React won't render script tags 1`] = ``;
diff --git a/frontend/src/components/common/html-to-react/html-to-react.test.tsx b/frontend/src/components/common/html-to-react/html-to-react.test.tsx
new file mode 100644
index 000000000..6c6f18dcb
--- /dev/null
+++ b/frontend/src/components/common/html-to-react/html-to-react.test.tsx
@@ -0,0 +1,50 @@
+/*
+ * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+import { HtmlToReact } from './html-to-react'
+import { render } from '@testing-library/react'
+
+describe('HTML to React', () => {
+ it('renders basic html correctly', () => {
+ const view = render(This is a test sentence'} />)
+ expect(view.container).toMatchSnapshot()
+ })
+
+ it("won't render script tags", () => {
+ const view = render(alert("XSS!")'} />)
+ expect(view.container).toMatchSnapshot()
+ })
+
+ it('will forward the DomPurify settings', () => {
+ const view = render(
+ Test!'} />
+ )
+ expect(view.container).toMatchSnapshot()
+ })
+
+ it('will forward the parser options', () => {
+ let transformerVisited = false
+ let preprocessNodesVisited = false
+
+ const view = render(
+ This is a sentence'}
+ parserOptions={{
+ transform: () => {
+ transformerVisited = true
+ return Hijacked!
+ },
+ preprocessNodes: (document) => {
+ preprocessNodesVisited = true
+ return document
+ }
+ }}
+ />
+ )
+ expect(view.container).toMatchSnapshot()
+ expect(preprocessNodesVisited).toBeTruthy()
+ expect(transformerVisited).toBeTruthy()
+ })
+})
diff --git a/frontend/src/components/common/html-to-react/html-to-react.tsx b/frontend/src/components/common/html-to-react/html-to-react.tsx
new file mode 100644
index 000000000..0e2dfac13
--- /dev/null
+++ b/frontend/src/components/common/html-to-react/html-to-react.tsx
@@ -0,0 +1,32 @@
+/*
+ * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+import type { ParserOptions } from '@hedgedoc/html-to-react'
+import convertHtmlToReact from '@hedgedoc/html-to-react'
+import type DOMPurify from 'dompurify'
+import { sanitize } from 'dompurify'
+import React, { Fragment, useMemo } from 'react'
+
+export interface HtmlToReactProps {
+ htmlCode: string
+ domPurifyConfig?: DOMPurify.Config
+ parserOptions?: ParserOptions
+}
+
+/**
+ * Renders
+ * @param htmlCode
+ * @param domPurifyConfig
+ * @param parserOptions
+ * @constructor
+ */
+export const HtmlToReact: React.FC = ({ htmlCode, domPurifyConfig, parserOptions }) => {
+ const elements = useMemo(() => {
+ const sanitizedHtmlCode = sanitize(htmlCode, { ...domPurifyConfig, RETURN_DOM_FRAGMENT: false, RETURN_DOM: false })
+ return convertHtmlToReact(sanitizedHtmlCode, parserOptions)
+ }, [domPurifyConfig, htmlCode, parserOptions])
+
+ return {elements}
+}
diff --git a/frontend/src/components/markdown-renderer/document-markdown-renderer.tsx b/frontend/src/components/markdown-renderer/document-markdown-renderer.tsx
index cff07a872..b6e0ef3e0 100644
--- a/frontend/src/components/markdown-renderer/document-markdown-renderer.tsx
+++ b/frontend/src/components/markdown-renderer/document-markdown-renderer.tsx
@@ -7,11 +7,12 @@ import { cypressId } from '../../utils/cypress-attribute'
import type { CommonMarkdownRendererProps } from './common-markdown-renderer-props'
import { HeadlineAnchorsMarkdownExtension } from './extensions/headline-anchors-markdown-extension'
import type { LineMarkers } from './extensions/linemarker/add-line-marker-markdown-it-plugin'
+import { LinemarkerMarkdownExtension } from './extensions/linemarker/linemarker-markdown-extension'
import type { LineMarkerPosition } from './extensions/linemarker/types'
-import { useConvertMarkdownToReactDom } from './hooks/use-convert-markdown-to-react-dom'
+import { useCalculateLineMarkerPosition } from './hooks/use-calculate-line-marker-positions'
import { useExtractFirstHeadline } from './hooks/use-extract-first-headline'
import { useMarkdownExtensions } from './hooks/use-markdown-extensions'
-import { useCalculateLineMarkerPosition } from './utils/calculate-line-marker-positions'
+import { MarkdownToReact } from './markdown-to-react/markdown-to-react'
import React, { useEffect, useMemo, useRef } from 'react'
import { useTranslation } from 'react-i18next'
@@ -27,9 +28,7 @@ export interface DocumentMarkdownRendererProps extends CommonMarkdownRendererPro
* @param onFirstHeadingChange The callback to call if the first heading changes.
* @param onLineMarkerPositionChanged The callback to call with changed {@link LineMarkers}
* @param onTaskCheckedChange The callback to call if a task is checked or unchecked.
- * @param onTocChange The callback to call if the toc changes.
* @param baseUrl The base url of the renderer
- * @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
*/
@@ -47,19 +46,17 @@ export const DocumentMarkdownRenderer: React.FC =
const extensions = useMarkdownExtensions(
baseUrl,
- currentLineMarkers,
- useMemo(() => [new HeadlineAnchorsMarkdownExtension()], [])
+ useMemo(
+ () => [
+ new HeadlineAnchorsMarkdownExtension(),
+ new LinemarkerMarkdownExtension((values) => (currentLineMarkers.current = values))
+ ],
+ []
+ )
)
- const markdownReactDom = useConvertMarkdownToReactDom(markdownContentLines, extensions, newlinesAreBreaks, true)
-
useTranslation()
- useCalculateLineMarkerPosition(
- markdownBodyRef,
- currentLineMarkers.current,
- onLineMarkerPositionChanged,
- markdownBodyRef.current?.offsetTop ?? 0
- )
+ useCalculateLineMarkerPosition(markdownBodyRef, currentLineMarkers.current, onLineMarkerPositionChanged)
const extractFirstHeadline = useExtractFirstHeadline(markdownBodyRef, onFirstHeadingChange)
useEffect(() => {
extractFirstHeadline()
@@ -72,7 +69,12 @@ export const DocumentMarkdownRenderer: React.FC =
ref={markdownBodyRef}
data-word-count-target={true}
className={`${className ?? ''} markdown-body w-100 d-flex flex-column align-items-center`}>
- {markdownReactDom}
+
)
diff --git a/frontend/src/components/markdown-renderer/extensions/image-placeholder/image-placeholder-markdown-extension.ts b/frontend/src/components/markdown-renderer/extensions/image-placeholder/image-placeholder-markdown-extension.ts
index f1f75d2b5..89b5405a6 100644
--- a/frontend/src/components/markdown-renderer/extensions/image-placeholder/image-placeholder-markdown-extension.ts
+++ b/frontend/src/components/markdown-renderer/extensions/image-placeholder/image-placeholder-markdown-extension.ts
@@ -15,10 +15,6 @@ import type MarkdownIt from 'markdown-it/lib'
export class ImagePlaceholderMarkdownExtension extends MarkdownRendererExtension {
public static readonly PLACEHOLDER_URL = 'https://'
- constructor() {
- super()
- }
-
configureMarkdownIt(markdownIt: MarkdownIt): void {
addLineToPlaceholderImageTags(markdownIt)
}
diff --git a/frontend/src/components/markdown-renderer/extensions/linemarker/types.d.ts b/frontend/src/components/markdown-renderer/extensions/linemarker/types.d.ts
index 44566a65a..5fcdba88b 100644
--- a/frontend/src/components/markdown-renderer/extensions/linemarker/types.d.ts
+++ b/frontend/src/components/markdown-renderer/extensions/linemarker/types.d.ts
@@ -9,6 +9,9 @@ export interface LineWithId {
id: number
}
+/**
+ * Defines the line number of a line marker and its absolute scroll position on the page.
+ */
export interface LineMarkerPosition {
line: number
position: number
diff --git a/frontend/src/components/markdown-renderer/extensions/sanitizer/dom-purifier-node-preprocessor.ts b/frontend/src/components/markdown-renderer/extensions/sanitizer/dom-purifier-node-preprocessor.ts
deleted file mode 100644
index 6f0c87ec4..000000000
--- a/frontend/src/components/markdown-renderer/extensions/sanitizer/dom-purifier-node-preprocessor.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
- *
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-import { NodeProcessor } from '../../node-preprocessors/node-processor'
-import render from 'dom-serializer'
-import type { Document } from 'domhandler'
-import DOMPurify from 'dompurify'
-import { parseDocument } from 'htmlparser2'
-
-/**
- * Sanitizes the given {@link Document document}.
- *
- * @see https://cure53.de/purify
- */
-export class SanitizerNodePreprocessor extends NodeProcessor {
- constructor(private tagNameWhiteList: string[]) {
- super()
- }
-
- process(nodes: Document): Document {
- const sanitizedHtml = DOMPurify.sanitize(render(nodes), {
- ADD_TAGS: this.tagNameWhiteList
- })
- return parseDocument(sanitizedHtml)
- }
-}
diff --git a/frontend/src/components/markdown-renderer/extensions/sanitizer/sanitizer-markdown-extension.ts b/frontend/src/components/markdown-renderer/extensions/sanitizer/sanitizer-markdown-extension.ts
deleted file mode 100644
index ce1d1ad87..000000000
--- a/frontend/src/components/markdown-renderer/extensions/sanitizer/sanitizer-markdown-extension.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
- *
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-import type { NodeProcessor } from '../../node-preprocessors/node-processor'
-import { MarkdownRendererExtension } from '../base/markdown-renderer-extension'
-import { SanitizerNodePreprocessor } from './dom-purifier-node-preprocessor'
-
-/**
- * Adds support for html sanitizing using dompurify to the markdown rendering.
- */
-export class SanitizerMarkdownExtension extends MarkdownRendererExtension {
- constructor(private tagNameWhiteList: string[]) {
- super()
- }
-
- public buildNodeProcessors(): NodeProcessor[] {
- return [new SanitizerNodePreprocessor(this.tagNameWhiteList)]
- }
-}
diff --git a/frontend/src/components/markdown-renderer/extensions/table-of-contents-markdown-extension.ts b/frontend/src/components/markdown-renderer/extensions/table-of-contents-markdown-extension.ts
index ed8278149..6a436d680 100644
--- a/frontend/src/components/markdown-renderer/extensions/table-of-contents-markdown-extension.ts
+++ b/frontend/src/components/markdown-renderer/extensions/table-of-contents-markdown-extension.ts
@@ -18,17 +18,20 @@ export class TableOfContentsMarkdownExtension extends MarkdownRendererExtension
private lastAst: TocAst | undefined = undefined
public configureMarkdownIt(markdownIt: MarkdownIt): void {
- toc(markdownIt, {
- listType: 'ul',
- level: [1, 2, 3],
- callback: (ast: TocAst): void => {
- if (equal(ast, this.lastAst)) {
- return
- }
- this.lastAst = ast
- this.eventEmitter?.emit(TableOfContentsMarkdownExtension.EVENT_NAME, ast)
- },
- slugify: tocSlugify
- })
+ const eventEmitter = this.eventEmitter
+ if (eventEmitter !== undefined) {
+ toc(markdownIt, {
+ listType: 'ul',
+ level: [1, 2, 3],
+ callback: (ast: TocAst): void => {
+ if (equal(ast, this.lastAst)) {
+ return
+ }
+ this.lastAst = ast
+ eventEmitter.emit(TableOfContentsMarkdownExtension.EVENT_NAME, ast)
+ },
+ slugify: tocSlugify
+ })
+ }
}
}
diff --git a/frontend/src/components/markdown-renderer/utils/calculate-line-marker-positions.ts b/frontend/src/components/markdown-renderer/hooks/use-calculate-line-marker-positions.ts
similarity index 90%
rename from frontend/src/components/markdown-renderer/utils/calculate-line-marker-positions.ts
rename to frontend/src/components/markdown-renderer/hooks/use-calculate-line-marker-positions.ts
index 3c2ebb2d0..09aed8e29 100644
--- a/frontend/src/components/markdown-renderer/utils/calculate-line-marker-positions.ts
+++ b/frontend/src/components/markdown-renderer/hooks/use-calculate-line-marker-positions.ts
@@ -52,13 +52,11 @@ const calculateLineMarkerPositions = (
* @param documentElement A reference to the rendered document.
* @param lineMarkers A list of {@link LineMarkers}
* @param onLineMarkerPositionChanged The callback to call if the {@link LineMarkerPosition line marker positions} change e.g. by rendering or resizing.
- * @param offset The optional offset
*/
export const useCalculateLineMarkerPosition = (
documentElement: RefObject,
- lineMarkers?: LineMarkers[],
- onLineMarkerPositionChanged?: (lineMarkerPosition: LineMarkerPosition[]) => void,
- offset?: number
+ lineMarkers: LineMarkers[] | undefined,
+ onLineMarkerPositionChanged?: (lineMarkerPosition: LineMarkerPosition[]) => void
): void => {
const lastLineMarkerPositions = useRef()
@@ -67,13 +65,17 @@ export const useCalculateLineMarkerPosition = (
return
}
- const newLines = calculateLineMarkerPositions(documentElement.current, lineMarkers, offset)
+ const newLines = calculateLineMarkerPositions(
+ documentElement.current,
+ lineMarkers,
+ documentElement.current.offsetTop ?? 0
+ )
if (!equal(newLines, lastLineMarkerPositions)) {
lastLineMarkerPositions.current = newLines
onLineMarkerPositionChanged(newLines)
}
- }, [documentElement, lineMarkers, offset, onLineMarkerPositionChanged])
+ }, [documentElement, lineMarkers, onLineMarkerPositionChanged])
useEffect(() => {
calculateNewLineMarkerPositions()
diff --git a/frontend/src/components/markdown-renderer/hooks/use-convert-markdown-to-react-dom.tsx b/frontend/src/components/markdown-renderer/hooks/use-convert-markdown-to-react-dom.tsx
deleted file mode 100644
index d03ac959f..000000000
--- a/frontend/src/components/markdown-renderer/hooks/use-convert-markdown-to-react-dom.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
- *
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-import type { MarkdownRendererExtension } from '../extensions/base/markdown-renderer-extension'
-import { SanitizerMarkdownExtension } from '../extensions/sanitizer/sanitizer-markdown-extension'
-import type { ValidReactDomElement } from '../replace-components/component-replacer'
-import { LineIdMapper } from '../utils/line-id-mapper'
-import { NodeToReactTransformer } from '../utils/node-to-react-transformer'
-import { useCombinedNodePreprocessor } from './use-combined-node-preprocessor'
-import { useConfiguredMarkdownIt } from './use-configured-markdown-it'
-import convertHtmlToReact from '@hedgedoc/html-to-react'
-import React, { Fragment, useMemo } from 'react'
-
-/**
- * Renders Markdown-Code into react elements.
- *
- * @param markdownContentLines The Markdown code lines that should be rendered
- * @param additionalMarkdownExtensions A list of {@link MarkdownRendererExtension markdown extensions} that should be used
- * @param newlinesAreBreaks Defines if the alternative break mode of markdown it should be used
- * @param allowHtml Defines if html is allowed in markdown
- * @return The React DOM that represents the rendered Markdown code
- */
-export const useConvertMarkdownToReactDom = (
- markdownContentLines: string[],
- additionalMarkdownExtensions: MarkdownRendererExtension[],
- newlinesAreBreaks = true,
- allowHtml = true
-): ValidReactDomElement => {
- const lineNumberMapper = useMemo(() => new LineIdMapper(), [])
- const htmlToReactTransformer = useMemo(() => new NodeToReactTransformer(), [])
- const markdownExtensions = useMemo(() => {
- const tagWhiteLists = additionalMarkdownExtensions.flatMap((extension) => extension.buildTagNameAllowList())
- return [...additionalMarkdownExtensions, new SanitizerMarkdownExtension(tagWhiteLists)]
- }, [additionalMarkdownExtensions])
-
- useMemo(() => {
- htmlToReactTransformer.setReplacers(markdownExtensions.flatMap((extension) => extension.buildReplacers()))
- }, [htmlToReactTransformer, markdownExtensions])
-
- useMemo(() => {
- htmlToReactTransformer.setLineIds(lineNumberMapper.updateLineMapping(markdownContentLines))
- }, [htmlToReactTransformer, lineNumberMapper, markdownContentLines])
-
- const nodePreProcessor = useCombinedNodePreprocessor(markdownExtensions)
- const markdownIt = useConfiguredMarkdownIt(markdownExtensions, allowHtml, newlinesAreBreaks)
-
- return useMemo(() => {
- const html = markdownIt.render(markdownContentLines.join('\n'))
- htmlToReactTransformer.resetReplacers()
-
- return (
-
- {convertHtmlToReact(html, {
- transform: (node, index) => htmlToReactTransformer.translateNodeToReactElement(node, index),
- preprocessNodes: (document) => nodePreProcessor(document)
- })}
-
- )
- }, [htmlToReactTransformer, markdownContentLines, markdownIt, nodePreProcessor])
-}
diff --git a/frontend/src/components/markdown-renderer/hooks/use-markdown-extensions.ts b/frontend/src/components/markdown-renderer/hooks/use-markdown-extensions.ts
index c8484ef00..c3997e29a 100644
--- a/frontend/src/components/markdown-renderer/hooks/use-markdown-extensions.ts
+++ b/frontend/src/components/markdown-renderer/hooks/use-markdown-extensions.ts
@@ -12,43 +12,30 @@ import { GenericSyntaxMarkdownExtension } from '../extensions/generic-syntax-mar
import { IframeCapsuleMarkdownExtension } from '../extensions/iframe-capsule/iframe-capsule-markdown-extension'
import { ImagePlaceholderMarkdownExtension } from '../extensions/image-placeholder/image-placeholder-markdown-extension'
import { ProxyImageMarkdownExtension } from '../extensions/image/proxy-image-markdown-extension'
-import type { LineMarkers } from '../extensions/linemarker/add-line-marker-markdown-it-plugin'
-import { LinemarkerMarkdownExtension } from '../extensions/linemarker/linemarker-markdown-extension'
import { LinkAdjustmentMarkdownExtension } from '../extensions/link-replacer/link-adjustment-markdown-extension'
import { LinkifyFixMarkdownExtension } from '../extensions/linkify-fix/linkify-fix-markdown-extension'
import { TableOfContentsMarkdownExtension } from '../extensions/table-of-contents-markdown-extension'
import { UploadIndicatingImageFrameMarkdownExtension } from '../extensions/upload-indicating-image-frame/upload-indicating-image-frame-markdown-extension'
import { useExtensionEventEmitter } from './use-extension-event-emitter'
-import type { MutableRefObject } from 'react'
import { useMemo } from 'react'
-const optionalMarkdownRendererExtensions = optionalAppExtensions.flatMap((value) =>
- value.buildMarkdownRendererExtensions()
-)
-
/**
* Provides a list of {@link MarkdownRendererExtension markdown extensions} that is a combination of the common extensions and the given additional.
*
* @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
* @return The created list of markdown extensions
*/
export const useMarkdownExtensions = (
baseUrl: string,
- currentLineMarkers: MutableRefObject | undefined,
additionalExtensions: MarkdownRendererExtension[]
): MarkdownRendererExtension[] => {
const extensionEventEmitter = useExtensionEventEmitter()
- //replace with global list
return useMemo(() => {
return [
- ...optionalMarkdownRendererExtensions,
+ ...optionalAppExtensions.flatMap((extension) => extension.buildMarkdownRendererExtensions(extensionEventEmitter)),
...additionalExtensions,
- new TableOfContentsMarkdownExtension(extensionEventEmitter),
- new LinemarkerMarkdownExtension(
- currentLineMarkers ? (lineMarkers) => (currentLineMarkers.current = lineMarkers) : undefined
- ),
+ new TableOfContentsMarkdownExtension(),
new IframeCapsuleMarkdownExtension(),
new ImagePlaceholderMarkdownExtension(),
new UploadIndicatingImageFrameMarkdownExtension(),
@@ -60,5 +47,5 @@ export const useMarkdownExtensions = (
new DebuggerMarkdownExtension(),
new ProxyImageMarkdownExtension()
]
- }, [additionalExtensions, baseUrl, currentLineMarkers, extensionEventEmitter])
+ }, [additionalExtensions, baseUrl, extensionEventEmitter])
}
diff --git a/frontend/src/components/markdown-renderer/markdown-to-react/__snapshots__/markdown-to-react.test.tsx.snap b/frontend/src/components/markdown-renderer/markdown-to-react/__snapshots__/markdown-to-react.test.tsx.snap
new file mode 100644
index 000000000..4152abd0d
--- /dev/null
+++ b/frontend/src/components/markdown-renderer/markdown-to-react/__snapshots__/markdown-to-react.test.tsx.snap
@@ -0,0 +1,92 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`markdown to react can render html if allowed 1`] = `
+
+`;
+
+exports[`markdown to react can render markdown with newlines as line breaks 1`] = `
+
+
+ This is a headline
+
+
+
+
+ This is content
+
+
+This Too
+
+
+
+
+`;
+
+exports[`markdown to react will use markdown render extensions 1`] = `
+
+
+ <span>test</span>
+
+
+
+
+ configure
+
+
+ post
+
+
+ NodeProcessor!
+
+
+
+ node processor children
+
+
+
+
+
+ node processor children
+
+
+
+
+`;
+
+exports[`markdown to react won't render html if forbidden 1`] = `
+
+
+ <span>test</span>
+
+
+
+
+`;
+
+exports[`markdown to react won't render markdown with newlines as line breaks if forbidden 1`] = `
+
+
+ This is a headline
+
+
+
+
+ This is content
+This Too
+
+
+
+
+`;
diff --git a/frontend/src/components/markdown-renderer/hooks/use-combined-node-preprocessor.ts b/frontend/src/components/markdown-renderer/markdown-to-react/hooks/use-combined-node-preprocessor.ts
similarity index 89%
rename from frontend/src/components/markdown-renderer/hooks/use-combined-node-preprocessor.ts
rename to frontend/src/components/markdown-renderer/markdown-to-react/hooks/use-combined-node-preprocessor.ts
index 6d8255907..dd7594c7c 100644
--- a/frontend/src/components/markdown-renderer/hooks/use-combined-node-preprocessor.ts
+++ b/frontend/src/components/markdown-renderer/markdown-to-react/hooks/use-combined-node-preprocessor.ts
@@ -3,7 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import type { MarkdownRendererExtension } from '../extensions/base/markdown-renderer-extension'
+import type { MarkdownRendererExtension } from '../../extensions/base/markdown-renderer-extension'
import type { Document } from 'domhandler'
import { useMemo } from 'react'
diff --git a/frontend/src/components/markdown-renderer/hooks/use-configured-markdown-it.ts b/frontend/src/components/markdown-renderer/markdown-to-react/hooks/use-configured-markdown-it.ts
similarity index 92%
rename from frontend/src/components/markdown-renderer/hooks/use-configured-markdown-it.ts
rename to frontend/src/components/markdown-renderer/markdown-to-react/hooks/use-configured-markdown-it.ts
index c1ea7f04d..7057839bb 100644
--- a/frontend/src/components/markdown-renderer/hooks/use-configured-markdown-it.ts
+++ b/frontend/src/components/markdown-renderer/markdown-to-react/hooks/use-configured-markdown-it.ts
@@ -3,7 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import type { MarkdownRendererExtension } from '../extensions/base/markdown-renderer-extension'
+import type { MarkdownRendererExtension } from '../../extensions/base/markdown-renderer-extension'
import MarkdownIt from 'markdown-it/lib'
import { useMemo } from 'react'
diff --git a/frontend/src/components/markdown-renderer/markdown-to-react/markdown-to-react.test.tsx b/frontend/src/components/markdown-renderer/markdown-to-react/markdown-to-react.test.tsx
new file mode 100644
index 000000000..310eb3af1
--- /dev/null
+++ b/frontend/src/components/markdown-renderer/markdown-to-react/markdown-to-react.test.tsx
@@ -0,0 +1,70 @@
+/*
+ * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+import { MarkdownToReact } from './markdown-to-react'
+import { TestMarkdownRendererExtension } from './test-utils/test-markdown-renderer-extension'
+import { render } from '@testing-library/react'
+import type { EventMap } from 'eventemitter2'
+import { EventEmitter2 } from 'eventemitter2'
+
+describe('markdown to react', () => {
+ it('can render markdown with newlines as line breaks', () => {
+ const view = render(
+
+ )
+ expect(view.container).toMatchSnapshot()
+ })
+
+ it("won't render markdown with newlines as line breaks if forbidden", () => {
+ const view = render(
+
+ )
+ expect(view.container).toMatchSnapshot()
+ })
+
+ it('can render html if allowed', () => {
+ const view = render(
+ test']}
+ markdownRenderExtensions={[]}
+ newlinesAreBreaks={true}
+ allowHtml={true}
+ />
+ )
+ expect(view.container).toMatchSnapshot()
+ })
+
+ it("won't render html if forbidden", () => {
+ const view = render(
+ test']}
+ markdownRenderExtensions={[]}
+ newlinesAreBreaks={true}
+ allowHtml={false}
+ />
+ )
+ expect(view.container).toMatchSnapshot()
+ })
+
+ it('will use markdown render extensions', () => {
+ const view = render(
+ test']}
+ markdownRenderExtensions={[new TestMarkdownRendererExtension(new EventEmitter2())]}
+ newlinesAreBreaks={true}
+ allowHtml={false}
+ />
+ )
+ expect(view.container).toMatchSnapshot()
+ })
+})
diff --git a/frontend/src/components/markdown-renderer/markdown-to-react/markdown-to-react.tsx b/frontend/src/components/markdown-renderer/markdown-to-react/markdown-to-react.tsx
new file mode 100644
index 000000000..c39892f58
--- /dev/null
+++ b/frontend/src/components/markdown-renderer/markdown-to-react/markdown-to-react.tsx
@@ -0,0 +1,71 @@
+/*
+ * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+import { HtmlToReact } from '../../common/html-to-react/html-to-react'
+import type { MarkdownRendererExtension } from '../extensions/base/markdown-renderer-extension'
+import { useCombinedNodePreprocessor } from './hooks/use-combined-node-preprocessor'
+import { useConfiguredMarkdownIt } from './hooks/use-configured-markdown-it'
+import { LineContentToLineIdMapper } from './utils/line-content-to-line-id-mapper'
+import { NodeToReactTransformer } from './utils/node-to-react-transformer'
+import type { ParserOptions } from '@hedgedoc/html-to-react'
+import type DOMPurify from 'dompurify'
+import React, { useMemo } from 'react'
+
+export interface MarkdownToReactProps {
+ markdownContentLines: string[]
+ markdownRenderExtensions: MarkdownRendererExtension[]
+ newlinesAreBreaks?: boolean
+ allowHtml: boolean
+}
+
+/**
+ * Renders Markdown code.
+ *
+ * @param markdownContentLines The Markdown code lines that should be rendered
+ * @param additionalMarkdownExtensions A list of {@link MarkdownRendererExtension markdown extensions} that should be used
+ * @param newlinesAreBreaks Defines if the alternative break mode of markdown it should be used
+ * @param allowHtml Defines if html is allowed in markdown
+ */
+export const MarkdownToReact: React.FC = ({
+ markdownContentLines,
+ markdownRenderExtensions,
+ newlinesAreBreaks,
+ allowHtml
+}) => {
+ const lineNumberMapper = useMemo(() => new LineContentToLineIdMapper(), [])
+ const nodeToReactTransformer = useMemo(() => new NodeToReactTransformer(), [])
+
+ useMemo(() => {
+ nodeToReactTransformer.setReplacers(markdownRenderExtensions.flatMap((extension) => extension.buildReplacers()))
+ }, [nodeToReactTransformer, markdownRenderExtensions])
+
+ useMemo(() => {
+ nodeToReactTransformer.setLineIds(lineNumberMapper.updateLineMapping(markdownContentLines))
+ }, [nodeToReactTransformer, lineNumberMapper, markdownContentLines])
+
+ const nodePreProcessor = useCombinedNodePreprocessor(markdownRenderExtensions)
+ const markdownIt = useConfiguredMarkdownIt(markdownRenderExtensions, allowHtml, newlinesAreBreaks ?? true)
+
+ const parserOptions: ParserOptions = useMemo(
+ () => ({
+ transform: (node, index) => nodeToReactTransformer.translateNodeToReactElement(node, index),
+ preprocessNodes: (document) => {
+ nodeToReactTransformer.resetReplacers()
+ return nodePreProcessor(document)
+ }
+ }),
+ [nodeToReactTransformer, nodePreProcessor]
+ )
+
+ const html = useMemo(() => markdownIt.render(markdownContentLines.join('\n')), [markdownContentLines, markdownIt])
+ const domPurifyConfig: DOMPurify.Config = useMemo(
+ () => ({
+ ADD_TAGS: markdownRenderExtensions.flatMap((extension) => extension.buildTagNameAllowList())
+ }),
+ [markdownRenderExtensions]
+ )
+
+ return
+}
diff --git a/frontend/src/components/markdown-renderer/markdown-to-react/test-utils/test-markdown-renderer-extension.ts b/frontend/src/components/markdown-renderer/markdown-to-react/test-utils/test-markdown-renderer-extension.ts
new file mode 100644
index 000000000..8555b8c7a
--- /dev/null
+++ b/frontend/src/components/markdown-renderer/markdown-to-react/test-utils/test-markdown-renderer-extension.ts
@@ -0,0 +1,40 @@
+/*
+ * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+import { MarkdownRendererExtension } from '../../extensions/base/markdown-renderer-extension'
+import type { NodeProcessor } from '../../node-preprocessors/node-processor'
+import type { ComponentReplacer } from '../../replace-components/component-replacer'
+import { TestNodeProcessor } from './test-node-processor'
+import { TestReplacer } from './test-replacer'
+import type MarkdownIt from 'markdown-it'
+import Token from 'markdown-it/lib/token'
+
+export class TestMarkdownRendererExtension extends MarkdownRendererExtension {
+ buildNodeProcessors(): NodeProcessor[] {
+ return [new TestNodeProcessor()]
+ }
+
+ configureMarkdownIt(markdownIt: MarkdownIt) {
+ markdownIt.use(() => {
+ markdownIt.core.ruler.push('configure', (core) => core.tokens.push(new Token('configure', 'configure', 0)))
+ markdownIt.renderer.rules.configure = () => 'configure'
+ })
+ }
+
+ buildReplacers(): ComponentReplacer[] {
+ return [new TestReplacer()]
+ }
+
+ buildTagNameAllowList(): string[] {
+ return ['nodeProcessor']
+ }
+
+ configureMarkdownItPost(markdownIt: MarkdownIt) {
+ markdownIt.use(() => {
+ markdownIt.core.ruler.push('post', (core) => core.tokens.push(new Token('post', 'post', 0)))
+ markdownIt.renderer.rules.post = () => 'post'
+ })
+ }
+}
diff --git a/frontend/src/components/markdown-renderer/markdown-to-react/test-utils/test-node-processor.ts b/frontend/src/components/markdown-renderer/markdown-to-react/test-utils/test-node-processor.ts
new file mode 100644
index 000000000..4cca862e8
--- /dev/null
+++ b/frontend/src/components/markdown-renderer/markdown-to-react/test-utils/test-node-processor.ts
@@ -0,0 +1,15 @@
+/*
+ * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+import { NodeProcessor } from '../../node-preprocessors/node-processor'
+import type { Document } from 'domhandler'
+import { Element, Text } from 'domhandler'
+
+export class TestNodeProcessor extends NodeProcessor {
+ process(document: Document): Document {
+ document.childNodes.push(new Element('nodeProcessor', {}, [new Text('node processor children')]))
+ return document
+ }
+}
diff --git a/frontend/src/components/markdown-renderer/markdown-to-react/test-utils/test-replacer.tsx b/frontend/src/components/markdown-renderer/markdown-to-react/test-utils/test-replacer.tsx
new file mode 100644
index 000000000..378a3036e
--- /dev/null
+++ b/frontend/src/components/markdown-renderer/markdown-to-react/test-utils/test-replacer.tsx
@@ -0,0 +1,23 @@
+/*
+ * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+import type { NativeRenderer, NodeReplacement, SubNodeTransform } from '../../replace-components/component-replacer'
+import { ComponentReplacer, DO_NOT_REPLACE } from '../../replace-components/component-replacer'
+import type { Element } from 'domhandler'
+import React, { Fragment } from 'react'
+
+export class TestReplacer extends ComponentReplacer {
+ replace(node: Element, subNodeTransform: SubNodeTransform, nativeRenderer: NativeRenderer): NodeReplacement {
+ return node.tagName === 'nodeProcessor' ? (
+
+ NodeProcessor!
+ {node.childNodes.map(subNodeTransform)}
+ {nativeRenderer()}
+
+ ) : (
+ DO_NOT_REPLACE
+ )
+ }
+}
diff --git a/frontend/src/components/markdown-renderer/utils/line-id-mapper.test.ts b/frontend/src/components/markdown-renderer/markdown-to-react/utils/line-content-to-line-id-mapper.test.ts
similarity index 92%
rename from frontend/src/components/markdown-renderer/utils/line-id-mapper.test.ts
rename to frontend/src/components/markdown-renderer/markdown-to-react/utils/line-content-to-line-id-mapper.test.ts
index 1e964cb2c..be5c1602b 100644
--- a/frontend/src/components/markdown-renderer/utils/line-id-mapper.test.ts
+++ b/frontend/src/components/markdown-renderer/markdown-to-react/utils/line-content-to-line-id-mapper.test.ts
@@ -3,13 +3,13 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { LineIdMapper } from './line-id-mapper'
+import { LineContentToLineIdMapper } from './line-content-to-line-id-mapper'
describe('line id mapper', () => {
- let lineIdMapper: LineIdMapper
+ let lineIdMapper: LineContentToLineIdMapper
beforeEach(() => {
- lineIdMapper = new LineIdMapper()
+ lineIdMapper = new LineContentToLineIdMapper()
})
it('should be case sensitive', () => {
diff --git a/frontend/src/components/markdown-renderer/utils/line-id-mapper.ts b/frontend/src/components/markdown-renderer/markdown-to-react/utils/line-content-to-line-id-mapper.ts
similarity index 92%
rename from frontend/src/components/markdown-renderer/utils/line-id-mapper.ts
rename to frontend/src/components/markdown-renderer/markdown-to-react/utils/line-content-to-line-id-mapper.ts
index f0f08df0f..91e800803 100644
--- a/frontend/src/components/markdown-renderer/utils/line-id-mapper.ts
+++ b/frontend/src/components/markdown-renderer/markdown-to-react/utils/line-content-to-line-id-mapper.ts
@@ -3,7 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import type { LineWithId } from '../extensions/linemarker/types'
+import type { LineWithId } from '../../extensions/linemarker/types'
import type { ArrayChange } from 'diff'
import { diffArrays } from 'diff'
@@ -14,7 +14,7 @@ type LineChange = ArrayChange
* Calculates ids for every line in a given text and memorized the state of the last given text.
* It also assigns ids for new lines on every update.
*/
-export class LineIdMapper {
+export class LineContentToLineIdMapper {
private lastLines: LineWithId[] = []
private lastUsedLineId = 0
@@ -58,7 +58,11 @@ export class LineIdMapper {
*/
private convertChangesToLinesWithIds(changes: LineChange[]): LineWithId[] {
return changes
- .filter((change) => LineIdMapper.changeIsNotChangingLines(change) || LineIdMapper.changeIsAddingLines(change))
+ .filter(
+ (change) =>
+ LineContentToLineIdMapper.changeIsNotChangingLines(change) ||
+ LineContentToLineIdMapper.changeIsAddingLines(change)
+ )
.reduce(
(previousLineKeys, currentChange) => [...previousLineKeys, ...this.convertChangeToLinesWithIds(currentChange)],
[] as LineWithId[]
@@ -94,7 +98,7 @@ export class LineIdMapper {
* @return The created or reused {@link LineWithId lines with ids}
*/
private convertChangeToLinesWithIds(change: LineChange): LineWithId[] {
- if (LineIdMapper.changeIsAddingLines(change)) {
+ if (LineContentToLineIdMapper.changeIsAddingLines(change)) {
return change.value.map((line) => {
this.lastUsedLineId += 1
return { line: line, id: this.lastUsedLineId }
diff --git a/frontend/src/components/markdown-renderer/utils/node-to-react-transformer.test.tsx b/frontend/src/components/markdown-renderer/markdown-to-react/utils/node-to-react-transformer.test.tsx
similarity index 95%
rename from frontend/src/components/markdown-renderer/utils/node-to-react-transformer.test.tsx
rename to frontend/src/components/markdown-renderer/markdown-to-react/utils/node-to-react-transformer.test.tsx
index 933d0214a..aa62cb90d 100644
--- a/frontend/src/components/markdown-renderer/utils/node-to-react-transformer.test.tsx
+++ b/frontend/src/components/markdown-renderer/markdown-to-react/utils/node-to-react-transformer.test.tsx
@@ -3,8 +3,8 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import type { NodeReplacement } from '../replace-components/component-replacer'
-import { ComponentReplacer, DO_NOT_REPLACE } from '../replace-components/component-replacer'
+import type { NodeReplacement } from '../../replace-components/component-replacer'
+import { ComponentReplacer, DO_NOT_REPLACE } from '../../replace-components/component-replacer'
import { NodeToReactTransformer } from './node-to-react-transformer'
import { Element } from 'domhandler'
import type { ReactElement, ReactHTMLElement } from 'react'
diff --git a/frontend/src/components/markdown-renderer/utils/node-to-react-transformer.tsx b/frontend/src/components/markdown-renderer/markdown-to-react/utils/node-to-react-transformer.tsx
similarity index 94%
rename from frontend/src/components/markdown-renderer/utils/node-to-react-transformer.tsx
rename to frontend/src/components/markdown-renderer/markdown-to-react/utils/node-to-react-transformer.tsx
index 9668f8355..a7d2027d7 100644
--- a/frontend/src/components/markdown-renderer/utils/node-to-react-transformer.tsx
+++ b/frontend/src/components/markdown-renderer/markdown-to-react/utils/node-to-react-transformer.tsx
@@ -3,10 +3,14 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { LinemarkerMarkdownExtension } from '../extensions/linemarker/linemarker-markdown-extension'
-import type { LineWithId } from '../extensions/linemarker/types'
-import type { ComponentReplacer, NodeReplacement, ValidReactDomElement } from '../replace-components/component-replacer'
-import { DO_NOT_REPLACE } from '../replace-components/component-replacer'
+import { LinemarkerMarkdownExtension } from '../../extensions/linemarker/linemarker-markdown-extension'
+import type { LineWithId } from '../../extensions/linemarker/types'
+import type {
+ ComponentReplacer,
+ NodeReplacement,
+ ValidReactDomElement
+} from '../../replace-components/component-replacer'
+import { DO_NOT_REPLACE } from '../../replace-components/component-replacer'
import { convertNodeToReactElement } from '@hedgedoc/html-to-react/dist/convertNodeToReactElement'
import { Optional } from '@mrdrogdrog/optional'
import type { Element, Node } from 'domhandler'
diff --git a/frontend/src/components/markdown-renderer/slideshow-markdown-renderer.tsx b/frontend/src/components/markdown-renderer/slideshow-markdown-renderer.tsx
index 479f6ffc8..22fa5b28b 100644
--- a/frontend/src/components/markdown-renderer/slideshow-markdown-renderer.tsx
+++ b/frontend/src/components/markdown-renderer/slideshow-markdown-renderer.tsx
@@ -7,11 +7,11 @@ import type { SlideOptions } from '../../redux/note-details/types/slide-show-opt
import type { ScrollProps } from '../editor-page/synced-scroll/scroll-props'
import type { CommonMarkdownRendererProps } from './common-markdown-renderer-props'
import { RevealMarkdownExtension } from './extensions/reveal/reveal-markdown-extension'
-import { useConvertMarkdownToReactDom } from './hooks/use-convert-markdown-to-react-dom'
import { useExtractFirstHeadline } from './hooks/use-extract-first-headline'
import { useMarkdownExtensions } from './hooks/use-markdown-extensions'
import { REVEAL_STATUS, useReveal } from './hooks/use-reveal'
import { LoadingSlide } from './loading-slide'
+import { MarkdownToReact } from './markdown-to-react/markdown-to-react'
import React, { useEffect, useMemo, useRef } from 'react'
export interface SlideshowMarkdownRendererProps extends CommonMarkdownRendererProps {
@@ -25,10 +25,7 @@ export interface SlideshowMarkdownRendererProps extends CommonMarkdownRendererPr
* @param markdownContentLines The markdown lines
* @param onFirstHeadingChange The callback to call if the first heading changes.
* @param onLineMarkerPositionChanged The callback to call with changed {@link LineMarkers}
- * @param onTaskCheckedChange The callback to call if a task is checked or unchecked.
- * @param onTocChange The callback to call if the toc changes.
* @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 slideOptions The {@link SlideOptions} to use
*/
@@ -44,11 +41,9 @@ export const SlideshowMarkdownRenderer: React.FC [new RevealMarkdownExtension()], [])
)
- const markdownReactDom = useConvertMarkdownToReactDom(markdownContentLines, extensions, newlinesAreBreaks, true)
const revealStatus = useReveal(markdownContentLines, slideOptions)
const extractFirstHeadline = useExtractFirstHeadline(markdownBodyRef, onFirstHeadingChange)
@@ -59,8 +54,18 @@ export const SlideshowMarkdownRenderer: React.FC (revealStatus === REVEAL_STATUS.INITIALISED ? markdownReactDom : ),
- [markdownReactDom, revealStatus]
+ () =>
+ revealStatus === REVEAL_STATUS.INITIALISED ? (
+
+ ) : (
+
+ ),
+ [extensions, markdownContentLines, newlinesAreBreaks, revealStatus]
)
return (
diff --git a/frontend/src/components/markdown-renderer/test-utils/test-markdown-renderer.tsx b/frontend/src/components/markdown-renderer/test-utils/test-markdown-renderer.tsx
index 32bfaccb3..13a6f8e7a 100644
--- a/frontend/src/components/markdown-renderer/test-utils/test-markdown-renderer.tsx
+++ b/frontend/src/components/markdown-renderer/test-utils/test-markdown-renderer.tsx
@@ -5,7 +5,7 @@
*/
import { StoreProvider } from '../../../redux/store-provider'
import type { MarkdownRendererExtension } from '../extensions/base/markdown-renderer-extension'
-import { useConvertMarkdownToReactDom } from '../hooks/use-convert-markdown-to-react-dom'
+import { MarkdownToReact } from '../markdown-to-react/markdown-to-react'
import React, { useMemo } from 'react'
export interface SimpleMarkdownRendererProps {
@@ -21,7 +21,14 @@ export interface SimpleMarkdownRendererProps {
*/
export const TestMarkdownRenderer: React.FC = ({ content, extensions }) => {
const lines = useMemo(() => content.split('\n'), [content])
- const dom = useConvertMarkdownToReactDom(lines, extensions, true, false)
-
- return {dom}
+ return (
+
+
+
+ )
}
diff --git a/frontend/src/components/render-page/hooks/sync-scroll/use-document-sync-scrolling.ts b/frontend/src/components/render-page/hooks/sync-scroll/use-document-sync-scrolling.ts
index b3711af83..2c23bcdbf 100644
--- a/frontend/src/components/render-page/hooks/sync-scroll/use-document-sync-scrolling.ts
+++ b/frontend/src/components/render-page/hooks/sync-scroll/use-document-sync-scrolling.ts
@@ -31,7 +31,7 @@ export const useDocumentSyncScrolling = (
): [(lineMarkers: LineMarkerPosition[]) => void, React.UIEventHandler] => {
const [lineMarks, setLineMarks] = useState()
- const onLineMarkerPositionChanged = useCallback(
+ const recalculateLineMarkerPositions = useCallback(
(linkMarkerPositions: LineMarkerPosition[]) => {
if (!outerContainerRef.current || !rendererRef.current) {
return
@@ -51,5 +51,5 @@ export const useDocumentSyncScrolling = (
const onUserScroll = useOnUserScroll(lineMarks, outerContainerRef, onScroll)
useScrollToLineMark(scrollState, lineMarks, numberOfLines, outerContainerRef)
- return useMemo(() => [onLineMarkerPositionChanged, onUserScroll], [onLineMarkerPositionChanged, onUserScroll])
+ return useMemo(() => [recalculateLineMarkerPositions, onUserScroll], [recalculateLineMarkerPositions, onUserScroll])
}
diff --git a/frontend/src/components/render-page/markdown-document.tsx b/frontend/src/components/render-page/markdown-document.tsx
index 94b522373..d0eab827c 100644
--- a/frontend/src/components/render-page/markdown-document.tsx
+++ b/frontend/src/components/render-page/markdown-document.tsx
@@ -72,7 +72,7 @@ export const MarkdownDocument: React.FC = ({
const newlinesAreBreaks = useApplicationState((state) => state.noteDetails.frontmatter.newlinesAreBreaks)
const contentLineCount = useMemo(() => markdownContentLines.length, [markdownContentLines])
- const [onLineMarkerPositionChanged, onUserScroll] = useDocumentSyncScrolling(
+ const [recalculateLineMarkers, onUserScroll] = useDocumentSyncScrolling(
internalDocumentRenderPaneRef,
rendererRef,
contentLineCount,
@@ -95,7 +95,7 @@ export const MarkdownDocument: React.FC = ({
className={`mb-3 ${additionalRendererClasses ?? ''}`}
markdownContentLines={markdownContentLines}
onFirstHeadingChange={onFirstHeadingChange}
- onLineMarkerPositionChanged={onLineMarkerPositionChanged}
+ onLineMarkerPositionChanged={recalculateLineMarkers}
baseUrl={baseUrl}
newlinesAreBreaks={newlinesAreBreaks}
/>
diff --git a/frontend/src/extensions/extra-integrations/highlighted-code-fence/hooks/use-code-dom.tsx b/frontend/src/extensions/extra-integrations/highlighted-code-fence/hooks/use-code-dom.tsx
index d104d2a44..17dc3f24e 100644
--- a/frontend/src/extensions/extra-integrations/highlighted-code-fence/hooks/use-code-dom.tsx
+++ b/frontend/src/extensions/extra-integrations/highlighted-code-fence/hooks/use-code-dom.tsx
@@ -3,11 +3,10 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import convertHtmlToReact from '@hedgedoc/html-to-react'
-import { sanitize } from 'dompurify'
+import { HtmlToReact } from '../../../../components/common/html-to-react/html-to-react'
import type { HLJSApi } from 'highlight.js'
import type { ReactElement } from 'react'
-import React, { Fragment, useMemo } from 'react'
+import React, { useMemo } from 'react'
/**
* Highlights the given code using highlight.js. If the language wasn't recognized then it won't be highlighted.
@@ -38,7 +37,7 @@ export const useCodeDom = (code: string, hljs: HLJSApi | undefined, language?: s
* @return the code represented as react elements
*/
const createHtmlLinesToReactDOM = (code: string[]): ReactElement[] => {
- return code.map((line, lineIndex) => {convertHtmlToReact(sanitize(line))})
+ return code.map((line, lineIndex) => )
}
/**
diff --git a/frontend/src/extensions/extra-integrations/plantuml/plantuml-markdown-extension.ts b/frontend/src/extensions/extra-integrations/plantuml/plantuml-markdown-extension.ts
index 4d0dd6657..0d3c61721 100644
--- a/frontend/src/extensions/extra-integrations/plantuml/plantuml-markdown-extension.ts
+++ b/frontend/src/extensions/extra-integrations/plantuml/plantuml-markdown-extension.ts
@@ -20,10 +20,6 @@ import type Token from 'markdown-it/lib/token'
* @see https://plantuml.com
*/
export class PlantumlMarkdownExtension extends MarkdownRendererExtension {
- constructor() {
- super()
- }
-
private plantumlError(markdownIt: MarkdownIt): void {
const defaultRenderer: Renderer.RenderRule = markdownIt.renderer.rules.fence || (() => '')
markdownIt.renderer.rules.fence = (tokens: Token[], idx: number, options: Options, env, slf: Renderer) => {