Add markdown-extension-collection.ts

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2022-03-28 22:01:54 +02:00
parent ed6ab1b1fe
commit f2958a77fe
2 changed files with 67 additions and 31 deletions

View file

@ -6,17 +6,15 @@
import MarkdownIt from 'markdown-it/lib' import MarkdownIt from 'markdown-it/lib'
import { useMemo } from 'react' import { useMemo } from 'react'
import type { ComponentReplacer, ValidReactDomElement } from '../replace-components/component-replacer' import type { ValidReactDomElement } from '../replace-components/component-replacer'
import convertHtmlToReact from '@hedgedoc/html-to-react' import convertHtmlToReact from '@hedgedoc/html-to-react'
import { NodeToReactTransformer } from '../utils/node-to-react-transformer' import { NodeToReactTransformer } from '../utils/node-to-react-transformer'
import { LineIdMapper } from '../utils/line-id-mapper' import { LineIdMapper } from '../utils/line-id-mapper'
import type { MarkdownExtension } from '../markdown-extension/markdown-extension' import type { MarkdownExtension } from '../markdown-extension/markdown-extension'
import type { NodeProcessor } from '../node-preprocessors/node-processor' import { MarkdownExtensionCollection } from '../markdown-extension/markdown-extension-collection'
import type { Document } from 'domhandler'
import { SanitizerMarkdownExtension } from '../markdown-extension/sanitizer/sanitizer-markdown-extension'
/** /**
* Renders markdown code into react elements * Renders Markdown-Code into react elements
* *
* @param markdownContentLines The markdown code lines that should be rendered * @param markdownContentLines The markdown code lines that should be rendered
* @param additionalMarkdownExtensions A list of {@link MarkdownExtension markdown extensions} that should be used * @param additionalMarkdownExtensions A list of {@link MarkdownExtension markdown extensions} that should be used
@ -32,13 +30,10 @@ export const useConvertMarkdownToReactDom = (
): ValidReactDomElement[] => { ): ValidReactDomElement[] => {
const lineNumberMapper = useMemo(() => new LineIdMapper(), []) const lineNumberMapper = useMemo(() => new LineIdMapper(), [])
const htmlToReactTransformer = useMemo(() => new NodeToReactTransformer(), []) const htmlToReactTransformer = useMemo(() => new NodeToReactTransformer(), [])
const markdownExtensions = useMemo(() => { const markdownExtensions = useMemo(
const tagNameWhiteList = additionalMarkdownExtensions.reduce( () => new MarkdownExtensionCollection(additionalMarkdownExtensions),
(state, extension) => [...state, ...extension.buildTagNameWhitelist()], [additionalMarkdownExtensions]
[] as string[] )
)
return [...additionalMarkdownExtensions, new SanitizerMarkdownExtension(tagNameWhiteList)]
}, [additionalMarkdownExtensions])
const markdownIt = useMemo(() => { const markdownIt = useMemo(() => {
const newMarkdownIt = new MarkdownIt('default', { const newMarkdownIt = new MarkdownIt('default', {
@ -47,35 +42,19 @@ export const useConvertMarkdownToReactDom = (
langPrefix: '', langPrefix: '',
typographer: true typographer: true
}) })
markdownExtensions.forEach((extension) => markdownExtensions.configureMarkdownIt(newMarkdownIt)
newMarkdownIt.use((markdownIt) => extension.configureMarkdownIt(markdownIt))
)
markdownExtensions.forEach((extension) =>
newMarkdownIt.use((markdownIt) => extension.configureMarkdownItPost(markdownIt))
)
return newMarkdownIt return newMarkdownIt
}, [allowHtml, markdownExtensions, newlinesAreBreaks]) }, [allowHtml, markdownExtensions, newlinesAreBreaks])
useMemo(() => { useMemo(() => {
const replacers = markdownExtensions.reduce( htmlToReactTransformer.setReplacers(markdownExtensions.buildReplacers())
(state, extension) => [...state, ...extension.buildReplacers()],
[] as ComponentReplacer[]
)
htmlToReactTransformer.setReplacers(replacers)
}, [htmlToReactTransformer, markdownExtensions]) }, [htmlToReactTransformer, markdownExtensions])
useMemo(() => { useMemo(() => {
htmlToReactTransformer.setLineIds(lineNumberMapper.updateLineMapping(markdownContentLines)) htmlToReactTransformer.setLineIds(lineNumberMapper.updateLineMapping(markdownContentLines))
}, [htmlToReactTransformer, lineNumberMapper, markdownContentLines]) }, [htmlToReactTransformer, lineNumberMapper, markdownContentLines])
const nodePreProcessor = useMemo(() => { const nodePreProcessor = useMemo(() => markdownExtensions.buildFlatNodeProcessor(), [markdownExtensions])
return markdownExtensions
.reduce((state, extension) => [...state, ...extension.buildNodeProcessors()], [] as NodeProcessor[])
.reduce(
(state, processor) => (document: Document) => state(processor.process(document)),
(document: Document) => document
)
}, [markdownExtensions])
return useMemo(() => { return useMemo(() => {
const html = markdownIt.render(markdownContentLines.join('\n')) const html = markdownIt.render(markdownContentLines.join('\n'))

View file

@ -0,0 +1,57 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { MarkdownExtension } from './markdown-extension'
import type { ComponentReplacer } from '../replace-components/component-replacer'
import type { Document } from 'domhandler'
import type MarkdownIt from 'markdown-it'
import { SanitizerMarkdownExtension } from './sanitizer/sanitizer-markdown-extension'
/**
* Contains multiple {@link MarkdownExtension} and uses them to configure parts of the renderer.
*/
export class MarkdownExtensionCollection {
private extensions: MarkdownExtension[]
public constructor(additionalExtensions: MarkdownExtension[]) {
const tagWhiteLists = additionalExtensions.flatMap((extension) => extension.buildTagNameWhitelist())
this.extensions = [...additionalExtensions, new SanitizerMarkdownExtension(tagWhiteLists)]
}
/**
* Configures the given {@link MarkdownIt markdown-it instance} using every saved {@link MarkdownExtension extension}.
*
* @param markdownIt The markdown-it instance to configure.
*/
public configureMarkdownIt(markdownIt: MarkdownIt): void {
this.extensions.forEach((extension) => markdownIt.use((markdownIt) => extension.configureMarkdownIt(markdownIt)))
this.extensions.forEach((extension) =>
markdownIt.use((markdownIt) => extension.configureMarkdownItPost(markdownIt))
)
}
/**
* Creates a node processor that applies the node processor of every saved {@link MarkdownExtension extension}.
*
* @return the created node processor function
*/
public buildFlatNodeProcessor(): (document: Document) => Document {
return this.extensions
.flatMap((extension) => extension.buildNodeProcessors())
.reduce(
(state, processor) => (document: Document) => state(processor.process(document)),
(document: Document) => document
)
}
/**
* Collects all {@link ComponentReplacer component replacers} from all saved {@link MarkdownExtension extension}.
*/
public buildReplacers(): ComponentReplacer[] {
return this.extensions.flatMap((extension) => extension.buildReplacers())
}
}