markdown-it-configurator (#626)

Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de>
Co-authored-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de>
Co-authored-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
Philip Molares 2020-10-08 22:24:42 +02:00 committed by GitHub
parent 89968387c2
commit 0670cddb0b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 524 additions and 360 deletions

View file

@ -1,65 +1,19 @@
import markdownItTaskLists from '@hedgedoc/markdown-it-task-lists'
import yaml from 'js-yaml'
import MarkdownIt from 'markdown-it'
import anchor from 'markdown-it-anchor'
import markdownItContainer from 'markdown-it-container'
import frontmatter from 'markdown-it-front-matter'
import mathJax from 'markdown-it-mathjax'
import plantuml from 'markdown-it-plantuml'
import markdownItRegex from 'markdown-it-regex'
import toc from 'markdown-it-toc-done-right'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import React, { useCallback, useMemo, useRef, useState } from 'react'
import { Alert } from 'react-bootstrap'
import { Trans } from 'react-i18next'
import { useSelector } from 'react-redux'
import { TocAst } from '../../external-types/markdown-it-toc-done-right/interface'
import { ApplicationState } from '../../redux'
import { InternalLink } from '../common/links/internal-link'
import { ShowIf } from '../common/show-if/show-if'
import { slugify } from '../editor/table-of-contents/table-of-contents'
import { RawYAMLMetadata, YAMLMetaData } from '../editor/yaml-metadata/yaml-metadata'
import { BasicMarkdownRenderer } from './basic-markdown-renderer'
import { createRenderContainer, validAlertLevels } from './markdown-it-plugins/alert-container'
import { highlightedCode } from './markdown-it-plugins/highlighted-code'
import { LineMarkers, lineNumberMarker } from './markdown-it-plugins/line-number-marker'
import { plantumlError } from './markdown-it-plugins/plantuml-error'
import { replaceAsciinemaLink } from './regex-plugins/replace-asciinema-link'
import { replaceGistLink } from './regex-plugins/replace-gist-link'
import { replaceLegacyGistShortCode } from './regex-plugins/replace-legacy-gist-short-code'
import { replaceLegacySlideshareShortCode } from './regex-plugins/replace-legacy-slideshare-short-code'
import { replaceLegacySpeakerdeckShortCode } from './regex-plugins/replace-legacy-speakerdeck-short-code'
import { replaceLegacyVimeoShortCode } from './regex-plugins/replace-legacy-vimeo-short-code'
import { replaceLegacyYoutubeShortCode } from './regex-plugins/replace-legacy-youtube-short-code'
import { replacePdfShortCode } from './regex-plugins/replace-pdf-short-code'
import { replaceQuoteExtraAuthor } from './regex-plugins/replace-quote-extra-author'
import { replaceQuoteExtraColor } from './regex-plugins/replace-quote-extra-color'
import { replaceQuoteExtraTime } from './regex-plugins/replace-quote-extra-time'
import { replaceVimeoLink } from './regex-plugins/replace-vimeo-link'
import { replaceYouTubeLink } from './regex-plugins/replace-youtube-link'
import { AbcReplacer } from './replace-components/abc/abc-replacer'
import { AsciinemaReplacer } from './replace-components/asciinema/asciinema-replacer'
import { CsvReplacer } from './replace-components/csv/csv-replacer'
import { FlowchartReplacer } from './replace-components/flow/flowchart-replacer'
import { GistReplacer } from './replace-components/gist/gist-replacer'
import { GraphvizReplacer } from './replace-components/graphviz/graphviz-replacer'
import { HighlightedCodeReplacer } from './replace-components/highlighted-fence/highlighted-fence-replacer'
import { ImageReplacer } from './replace-components/image/image-replacer'
import { KatexReplacer } from './replace-components/katex/katex-replacer'
import { LinemarkerReplacer } from './replace-components/linemarker/linemarker-replacer'
import { MarkmapReplacer } from './replace-components/markmap/markmap-replacer'
import { MermaidReplacer } from './replace-components/mermaid/mermaid-replacer'
import { PdfReplacer } from './replace-components/pdf/pdf-replacer'
import { PossibleWiderReplacer } from './replace-components/possible-wider/possible-wider-replacer'
import { QuoteOptionsReplacer } from './replace-components/quote-options/quote-options-replacer'
import { SequenceDiagramReplacer } from './replace-components/sequence-diagram/sequence-diagram-replacer'
import { TaskListReplacer } from './replace-components/task-list/task-list-replacer'
import { VegaReplacer } from './replace-components/vega-lite/vega-replacer'
import { VimeoReplacer } from './replace-components/vimeo/vimeo-replacer'
import { YoutubeReplacer } from './replace-components/youtube/youtube-replacer'
import { FullMarkdownItConfigurator } from './markdown-it-configurator/FullMarkdownItConfigurator'
import { LineMarkers } from './replace-components/linemarker/line-number-marker'
import { AdditionalMarkdownRendererProps, LineMarkerPosition } from './types'
import { useCalculateLineMarkerPosition } from './utils/calculate-line-marker-positions'
import { usePostMetaDataOnChange } from './utils/use-post-meta-data-on-change'
import { usePostTocAstOnChange } from './utils/use-post-toc-ast-on-change'
import { useReplacerInstanceListCreator } from './hooks/use-replacer-instance-list-creator'
import { useExtractFirstHeadline } from './hooks/use-extract-first-headline'
import { usePostMetaDataOnChange } from './hooks/use-post-meta-data-on-change'
import { usePostTocAstOnChange } from './hooks/use-post-toc-ast-on-change'
export interface FullMarkdownRendererProps {
onFirstHeadingChange?: (firstHeading: string | undefined) => void
@ -79,150 +33,36 @@ export const FullMarkdownRenderer: React.FC<FullMarkdownRendererProps & Addition
className,
wide
}) => {
const allReplacers = useMemo(() => {
return [
new LinemarkerReplacer(),
new PossibleWiderReplacer(),
new GistReplacer(),
new YoutubeReplacer(),
new VimeoReplacer(),
new AsciinemaReplacer(),
new AbcReplacer(),
new PdfReplacer(),
new ImageReplacer(),
new SequenceDiagramReplacer(),
new CsvReplacer(),
new FlowchartReplacer(),
new MermaidReplacer(),
new GraphvizReplacer(),
new MarkmapReplacer(),
new VegaReplacer(),
new HighlightedCodeReplacer(),
new QuoteOptionsReplacer(),
new KatexReplacer(),
new TaskListReplacer(onTaskCheckedChange)
]
}, [onTaskCheckedChange])
const allReplacers = useReplacerInstanceListCreator(onTaskCheckedChange)
const [yamlError, setYamlError] = useState(false)
const plantumlServer = useSelector((state: ApplicationState) => state.config.plantumlServer)
const rawMetaRef = useRef<RawYAMLMetadata>()
const firstHeadingRef = useRef<string>()
const documentElement = useRef<HTMLDivElement>(null)
const currentLineMarkers = useRef<LineMarkers[]>()
usePostMetaDataOnChange(rawMetaRef.current, firstHeadingRef.current, onMetaDataChange, onFirstHeadingChange)
useCalculateLineMarkerPosition(documentElement, currentLineMarkers.current, onLineMarkerPositionChanged, documentElement.current?.offsetTop ?? 0)
useExtractFirstHeadline(documentElement, content, onFirstHeadingChange)
const tocAst = useRef<TocAst>()
usePostTocAstOnChange(tocAst, onTocChange)
const extractInnerText = useCallback((node: ChildNode) => {
let innerText = ''
if (node.childNodes && node.childNodes.length > 0) {
node.childNodes.forEach((child) => { innerText += extractInnerText(child) })
} else if (node.nodeName === 'IMG') {
innerText += (node as HTMLImageElement).getAttribute('alt')
} else {
innerText += node.textContent
}
return innerText
}, [])
useEffect(() => {
if (onFirstHeadingChange && documentElement.current) {
const firstHeading = documentElement.current.getElementsByTagName('h1').item(0)
if (firstHeading) {
onFirstHeadingChange(extractInnerText(firstHeading))
}
}
}, [content, extractInnerText, onFirstHeadingChange])
const configureMarkdownIt = useCallback((md: MarkdownIt): void => {
if (onMetaDataChange) {
md.use(frontmatter, (rawMeta: string) => {
try {
const meta: RawYAMLMetadata = yaml.safeLoad(rawMeta) as RawYAMLMetadata
setYamlError(false)
rawMetaRef.current = meta
} catch (e) {
console.error(e)
setYamlError(true)
}
})
}
md.use(markdownItTaskLists, { lineNumber: true })
if (plantumlServer) {
md.use(plantuml, {
openMarker: '```plantuml',
closeMarker: '```',
server: plantumlServer
})
} else {
md.use(plantumlError)
}
if (onMetaDataChange) {
md.use(frontmatter, (rawMeta: string) => {
try {
const meta: RawYAMLMetadata = yaml.safeLoad(rawMeta) as RawYAMLMetadata
setYamlError(false)
rawMetaRef.current = meta
} catch (e) {
console.error(e)
setYamlError(true)
rawMetaRef.current = ({} as RawYAMLMetadata)
}
})
}
// noinspection CheckTagEmptyBody
md.use(anchor, {
permalink: true,
permalinkBefore: true,
permalinkClass: 'heading-anchor text-dark',
permalinkSymbol: '<i class="fa fa-link"></i>'
})
md.use(mathJax({
beforeMath: '<app-katex>',
afterMath: '</app-katex>',
beforeInlineMath: '<app-katex inline>',
afterInlineMath: '</app-katex>',
beforeDisplayMath: '<app-katex>',
afterDisplayMath: '</app-katex>'
}))
md.use(markdownItRegex, replaceLegacyYoutubeShortCode)
md.use(markdownItRegex, replaceLegacyVimeoShortCode)
md.use(markdownItRegex, replaceLegacyGistShortCode)
md.use(markdownItRegex, replaceLegacySlideshareShortCode)
md.use(markdownItRegex, replaceLegacySpeakerdeckShortCode)
md.use(markdownItRegex, replacePdfShortCode)
md.use(markdownItRegex, replaceAsciinemaLink)
md.use(markdownItRegex, replaceYouTubeLink)
md.use(markdownItRegex, replaceVimeoLink)
md.use(markdownItRegex, replaceGistLink)
md.use(highlightedCode)
md.use(markdownItRegex, replaceQuoteExtraAuthor)
md.use(markdownItRegex, replaceQuoteExtraColor)
md.use(markdownItRegex, replaceQuoteExtraTime)
md.use(toc, {
placeholder: '(\\[TOC\\]|\\[toc\\])',
listType: 'ul',
level: [1, 2, 3],
callback: (code: string, ast: TocAst): void => {
tocAst.current = ast
const markdownIt = useMemo(() => {
return (new FullMarkdownItConfigurator(
!!onMetaDataChange,
error => setYamlError(error),
rawMeta => {
rawMetaRef.current = rawMeta
},
slugify: slugify
})
validAlertLevels.forEach(level => {
md.use(markdownItContainer, level, { render: createRenderContainer(level) })
})
md.use(lineNumberMarker(), {
postLineMarkers: (lineMarkers) => {
toc => {
tocAst.current = toc
},
lineMarkers => {
currentLineMarkers.current = lineMarkers
}
})
}, [onMetaDataChange, plantumlServer])
)).buildConfiguredMarkdownIt()
}, [onMetaDataChange])
const clearMetadata = useCallback(() => {
rawMetaRef.current = undefined
@ -238,7 +78,7 @@ export const FullMarkdownRenderer: React.FC<FullMarkdownRendererProps & Addition
</Alert>
</ShowIf>
<BasicMarkdownRenderer className={className} wide={wide} content={content} componentReplacers={allReplacers}
onConfigureMarkdownIt={configureMarkdownIt} documentReference={documentElement}
markdownIt={markdownIt} documentReference={documentElement}
onBeforeRendering={clearMetadata}/>
</div>
)