diff --git a/src/components/editor/app-bar/help-button/cheatsheet.tsx b/src/components/editor/app-bar/help-button/cheatsheet.tsx
index da83c0602..02bfb1d31 100644
--- a/src/components/editor/app-bar/help-button/cheatsheet.tsx
+++ b/src/components/editor/app-bar/help-button/cheatsheet.tsx
@@ -1,10 +1,9 @@
-import MarkdownIt from 'markdown-it'
-import markdownItContainer from 'markdown-it-container'
-import React, { useCallback } from 'react'
+import React, { useMemo } from 'react'
import { Table } from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next'
import { BasicMarkdownRenderer } from '../../../markdown-renderer/basic-markdown-renderer'
-import { createRenderContainer, validAlertLevels } from '../../../markdown-renderer/markdown-it-plugins/alert-container'
+import { BasicMarkdownItConfigurator } from '../../../markdown-renderer/markdown-it-configurator/BasicMarkdownItConfigurator'
+import { alertContainer } from '../../../markdown-renderer/markdown-it-plugins/alert-container'
import { HighlightedCode } from '../../../markdown-renderer/replace-components/highlighted-fence/highlighted-code/highlighted-code'
import './cheatsheet.scss'
@@ -31,10 +30,10 @@ export const Cheatsheet: React.FC = () => {
`:::info\n${t('editor.help.cheatsheet.exampleAlert')}\n:::`
]
- const markdownItPlugins = useCallback((md: MarkdownIt) => {
- validAlertLevels.forEach(level => {
- md.use(markdownItContainer, level, { render: createRenderContainer(level) })
- })
+ const markdownIt = useMemo(() => {
+ return new BasicMarkdownItConfigurator()
+ .pushConfig(alertContainer)
+ .buildConfiguredMarkdownIt()
}, [])
return (
@@ -53,7 +52,7 @@ export const Cheatsheet: React.FC = () => {
diff --git a/src/components/markdown-renderer/basic-markdown-renderer.tsx b/src/components/markdown-renderer/basic-markdown-renderer.tsx
index 34c0c1198..c7d11d065 100644
--- a/src/components/markdown-renderer/basic-markdown-renderer.tsx
+++ b/src/components/markdown-renderer/basic-markdown-renderer.tsx
@@ -1,13 +1,4 @@
import MarkdownIt from 'markdown-it'
-import abbreviation from 'markdown-it-abbr'
-import definitionList from 'markdown-it-deflist'
-import emoji from 'markdown-it-emoji'
-import footnote from 'markdown-it-footnote'
-import imsize from 'markdown-it-imsize'
-import inserted from 'markdown-it-ins'
-import marked from 'markdown-it-mark'
-import subscript from 'markdown-it-sub'
-import superscript from 'markdown-it-sup'
import React, { ReactElement, RefObject, useMemo, useRef } from 'react'
import { Alert } from 'react-bootstrap'
import ReactHtmlParser from 'react-html-parser'
@@ -15,9 +6,6 @@ import { Trans } from 'react-i18next'
import { useSelector } from 'react-redux'
import { ApplicationState } from '../../redux'
import { ShowIf } from '../common/show-if/show-if'
-import { combinedEmojiData } from './markdown-it-plugins/emoji/mapping'
-import { linkifyExtra } from './markdown-it-plugins/linkify-extra'
-import { MarkdownItParserDebugger } from './markdown-it-plugins/parser-debugger'
import './markdown-renderer.scss'
import { ComponentReplacer } from './replace-components/ComponentReplacer'
import { AdditionalMarkdownRendererProps, LineKeys } from './types'
@@ -25,8 +13,8 @@ import { buildTransformer } from './utils/html-react-transformer'
import { calculateNewLineNumberMapping } from './utils/line-number-mapping'
export interface BasicMarkdownRendererProps {
- componentReplacers?: ComponentReplacer[],
- onConfigureMarkdownIt?: (md: MarkdownIt) => void,
+ componentReplacers?: () => ComponentReplacer[],
+ markdownIt: MarkdownIt,
documentReference?: RefObject
onBeforeRendering?: () => void
}
@@ -36,44 +24,12 @@ export const BasicMarkdownRenderer: React.FC {
const maxLength = useSelector((state: ApplicationState) => state.config.maxDocumentLength)
- const markdownIt = useMemo(() => {
- const md = new MarkdownIt('default', {
- html: true,
- breaks: true,
- langPrefix: '',
- typographer: true
- })
-
- md.use(emoji, {
- defs: combinedEmojiData
- })
- md.use(abbreviation)
- md.use(definitionList)
- md.use(subscript)
- md.use(superscript)
- md.use(inserted)
- md.use(marked)
- md.use(footnote)
- md.use(imsize)
-
- if (onConfigureMarkdownIt) {
- onConfigureMarkdownIt(md)
- }
-
- md.use(linkifyExtra)
- if (process.env.NODE_ENV !== 'production') {
- md.use(MarkdownItParserDebugger)
- }
-
- return md
- }, [onConfigureMarkdownIt])
-
const oldMarkdownLineKeys = useRef()
const lastUsedLineId = useRef(0)
@@ -87,7 +43,7 @@ export const BasicMarkdownRenderer: React.FC void
@@ -79,150 +33,36 @@ export const FullMarkdownRenderer: React.FC {
- 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()
const firstHeadingRef = useRef()
const documentElement = useRef(null)
const currentLineMarkers = useRef()
usePostMetaDataOnChange(rawMetaRef.current, firstHeadingRef.current, onMetaDataChange, onFirstHeadingChange)
useCalculateLineMarkerPosition(documentElement, currentLineMarkers.current, onLineMarkerPositionChanged, documentElement.current?.offsetTop ?? 0)
+ useExtractFirstHeadline(documentElement, content, onFirstHeadingChange)
const tocAst = useRef()
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: ''
- })
- md.use(mathJax({
- beforeMath: '',
- afterMath: '',
- beforeInlineMath: '',
- afterInlineMath: '',
- beforeDisplayMath: '',
- afterDisplayMath: ''
- }))
- 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
)
diff --git a/src/components/markdown-renderer/hooks/use-extract-first-headline.ts b/src/components/markdown-renderer/hooks/use-extract-first-headline.ts
new file mode 100644
index 000000000..af2799998
--- /dev/null
+++ b/src/components/markdown-renderer/hooks/use-extract-first-headline.ts
@@ -0,0 +1,24 @@
+import React, { useCallback, useEffect } from 'react'
+
+export const useExtractFirstHeadline = (documentElement: React.RefObject, content: string, onFirstHeadingChange?: (firstHeading: string | undefined) => void): void => {
+ const extractInnerText = useCallback((node: ChildNode): string => {
+ 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))
+ }
+ }
+ }, [documentElement, extractInnerText, onFirstHeadingChange, content])
+}
diff --git a/src/components/markdown-renderer/utils/use-post-meta-data-on-change.ts b/src/components/markdown-renderer/hooks/use-post-meta-data-on-change.ts
similarity index 100%
rename from src/components/markdown-renderer/utils/use-post-meta-data-on-change.ts
rename to src/components/markdown-renderer/hooks/use-post-meta-data-on-change.ts
diff --git a/src/components/markdown-renderer/utils/use-post-toc-ast-on-change.ts b/src/components/markdown-renderer/hooks/use-post-toc-ast-on-change.ts
similarity index 100%
rename from src/components/markdown-renderer/utils/use-post-toc-ast-on-change.ts
rename to src/components/markdown-renderer/hooks/use-post-toc-ast-on-change.ts
diff --git a/src/components/markdown-renderer/hooks/use-replacer-instance-list-creator.ts b/src/components/markdown-renderer/hooks/use-replacer-instance-list-creator.ts
new file mode 100644
index 000000000..8e64f0a21
--- /dev/null
+++ b/src/components/markdown-renderer/hooks/use-replacer-instance-list-creator.ts
@@ -0,0 +1,47 @@
+import { useMemo } from 'react'
+import { AbcReplacer } from '../replace-components/abc/abc-replacer'
+import { AsciinemaReplacer } from '../replace-components/asciinema/asciinema-replacer'
+import { ComponentReplacer } from '../replace-components/ComponentReplacer'
+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'
+
+export const useReplacerInstanceListCreator = (onTaskCheckedChange: (lineInMarkdown: number, checked: boolean) => void): ()=>ComponentReplacer[] => {
+ return useMemo(() => () => [
+ 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])
+}
diff --git a/src/components/markdown-renderer/markdown-it-configurator/BasicMarkdownItConfigurator.tsx b/src/components/markdown-renderer/markdown-it-configurator/BasicMarkdownItConfigurator.tsx
new file mode 100644
index 000000000..c018a8d9d
--- /dev/null
+++ b/src/components/markdown-renderer/markdown-it-configurator/BasicMarkdownItConfigurator.tsx
@@ -0,0 +1,33 @@
+import MarkdownIt from 'markdown-it'
+import abbreviation from 'markdown-it-abbr'
+import definitionList from 'markdown-it-deflist'
+import footnote from 'markdown-it-footnote'
+import imsize from 'markdown-it-imsize'
+import inserted from 'markdown-it-ins'
+import marked from 'markdown-it-mark'
+import subscript from 'markdown-it-sub'
+import superscript from 'markdown-it-sup'
+import { linkifyExtra } from '../markdown-it-plugins/linkify-extra'
+import { MarkdownItParserDebugger } from '../markdown-it-plugins/parser-debugger'
+import { twitterEmojis } from '../markdown-it-plugins/twitter-emojis'
+import { MarkdownItConfigurator } from './MarkdownItConfigurator'
+
+export class BasicMarkdownItConfigurator extends MarkdownItConfigurator {
+ protected configure (markdownIt: MarkdownIt): void {
+ this.configurations.push(
+ twitterEmojis,
+ abbreviation,
+ definitionList,
+ subscript,
+ superscript,
+ inserted,
+ marked,
+ footnote,
+ imsize
+ )
+ this.postConfigurations.push(
+ linkifyExtra,
+ MarkdownItParserDebugger
+ )
+ }
+}
diff --git a/src/components/markdown-renderer/markdown-it-configurator/FullMarkdownItConfigurator.tsx b/src/components/markdown-renderer/markdown-it-configurator/FullMarkdownItConfigurator.tsx
new file mode 100644
index 000000000..c04c634d3
--- /dev/null
+++ b/src/components/markdown-renderer/markdown-it-configurator/FullMarkdownItConfigurator.tsx
@@ -0,0 +1,63 @@
+import MarkdownIt from 'markdown-it'
+import { TocAst } from '../../../external-types/markdown-it-toc-done-right/interface'
+import { RawYAMLMetadata } from '../../editor/yaml-metadata/yaml-metadata'
+import { alertContainer } from '../markdown-it-plugins/alert-container'
+import { documentToc } from '../markdown-it-plugins/document-toc'
+import { frontmatterExtract } from '../markdown-it-plugins/frontmatter'
+import { headlineAnchors } from '../markdown-it-plugins/headline-anchors'
+import { highlightedCode } from '../markdown-it-plugins/highlighted-code'
+import { plantumlWithError } from '../markdown-it-plugins/plantuml'
+import { quoteExtra } from '../markdown-it-plugins/quote-extra'
+import { tasksLists } from '../markdown-it-plugins/tasks-lists'
+import { legacySlideshareShortCode } from '../regex-plugins/replace-legacy-slideshare-short-code'
+import { legacySpeakerdeckShortCode } from '../regex-plugins/replace-legacy-speakerdeck-short-code'
+import { AsciinemaReplacer } from '../replace-components/asciinema/asciinema-replacer'
+import { GistReplacer } from '../replace-components/gist/gist-replacer'
+import { KatexReplacer } from '../replace-components/katex/katex-replacer'
+import { LineMarkers, lineNumberMarker } from '../replace-components/linemarker/line-number-marker'
+import { PdfReplacer } from '../replace-components/pdf/pdf-replacer'
+import { VimeoReplacer } from '../replace-components/vimeo/vimeo-replacer'
+import { YoutubeReplacer } from '../replace-components/youtube/youtube-replacer'
+import { BasicMarkdownItConfigurator } from './BasicMarkdownItConfigurator'
+
+export class FullMarkdownItConfigurator extends BasicMarkdownItConfigurator {
+ constructor (
+ private useFrontmatter: boolean,
+ private onYamlError: (error: boolean) => void,
+ private onRawMeta: (rawMeta: RawYAMLMetadata) => void,
+ private onToc: (toc: TocAst) => void,
+ private onLineMarkers: (lineMarkers: LineMarkers[]) => void
+ ) {
+ super()
+ }
+
+ protected configure (markdownIt: MarkdownIt): void {
+ super.configure(markdownIt)
+
+ this.configurations.push(
+ plantumlWithError,
+ tasksLists,
+ (markdownIt) => {
+ frontmatterExtract(markdownIt,
+ !this.useFrontmatter ? undefined : {
+ onYamlError: (error: boolean) => this.onYamlError(error),
+ onRawMeta: (rawMeta: RawYAMLMetadata) => this.onRawMeta(rawMeta)
+ })
+ },
+ headlineAnchors,
+ KatexReplacer.markdownItPlugin,
+ YoutubeReplacer.markdownItPlugin,
+ VimeoReplacer.markdownItPlugin,
+ GistReplacer.markdownItPlugin,
+ legacySlideshareShortCode,
+ legacySpeakerdeckShortCode,
+ PdfReplacer.markdownItPlugin,
+ AsciinemaReplacer.markdownItPlugin,
+ highlightedCode,
+ quoteExtra,
+ (markdownIt) => documentToc(markdownIt, this.onToc),
+ alertContainer,
+ (markdownIt) => lineNumberMarker(markdownIt, (lineMarkers) => this.onLineMarkers(lineMarkers))
+ )
+ }
+}
diff --git a/src/components/markdown-renderer/markdown-it-configurator/MarkdownItConfigurator.tsx b/src/components/markdown-renderer/markdown-it-configurator/MarkdownItConfigurator.tsx
new file mode 100644
index 000000000..cc7e25c04
--- /dev/null
+++ b/src/components/markdown-renderer/markdown-it-configurator/MarkdownItConfigurator.tsx
@@ -0,0 +1,26 @@
+import MarkdownIt from 'markdown-it'
+
+export abstract class MarkdownItConfigurator {
+ protected configurations:MarkdownIt.PluginSimple[] = [];
+ protected postConfigurations:MarkdownIt.PluginSimple[] = [];
+
+ protected abstract configure(markdownIt: MarkdownIt): void;
+
+ public pushConfig (plugin: MarkdownIt.PluginSimple): this {
+ this.configurations.push(plugin)
+ return this
+ }
+
+ public buildConfiguredMarkdownIt (): MarkdownIt {
+ const markdownIt = new MarkdownIt('default', {
+ html: true,
+ breaks: true,
+ langPrefix: '',
+ typographer: true
+ })
+ this.configure(markdownIt)
+ this.configurations.forEach((configuration) => markdownIt.use(configuration))
+ this.postConfigurations.forEach((postConfiguration) => markdownIt.use(postConfiguration))
+ return markdownIt
+ }
+}
diff --git a/src/components/markdown-renderer/markdown-it-plugins/alert-container.ts b/src/components/markdown-renderer/markdown-it-plugins/alert-container.ts
index de92d4643..800c4ace7 100644
--- a/src/components/markdown-renderer/markdown-it-plugins/alert-container.ts
+++ b/src/components/markdown-renderer/markdown-it-plugins/alert-container.ts
@@ -1,12 +1,14 @@
import MarkdownIt from 'markdown-it'
+import markdownItContainer from 'markdown-it-container'
import Renderer from 'markdown-it/lib/renderer'
import Token from 'markdown-it/lib/token'
+import { MarkdownItPlugin } from '../replace-components/ComponentReplacer'
type RenderContainerReturn = (tokens: Token[], index: number, options: MarkdownIt.Options, env: unknown, self: Renderer) => void;
type ValidAlertLevels = ('warning' | 'danger' | 'success' | 'info')
export const validAlertLevels: ValidAlertLevels[] = ['success', 'danger', 'info', 'warning']
-export const createRenderContainer = (level: ValidAlertLevels): RenderContainerReturn => {
+const createRenderContainer = (level: ValidAlertLevels): RenderContainerReturn => {
return (tokens: Token[], index: number, options: MarkdownIt.Options, env: unknown, self: Renderer) => {
tokens[index].attrJoin('role', 'alert')
tokens[index].attrJoin('class', 'alert')
@@ -14,3 +16,9 @@ export const createRenderContainer = (level: ValidAlertLevels): RenderContainerR
return self.renderToken(tokens, index, options)
}
}
+
+export const alertContainer: MarkdownItPlugin = (markdownIt: MarkdownIt) => {
+ validAlertLevels.forEach(level => {
+ markdownItContainer(markdownIt, level, { render: createRenderContainer(level) })
+ })
+}
diff --git a/src/components/markdown-renderer/markdown-it-plugins/document-toc.ts b/src/components/markdown-renderer/markdown-it-plugins/document-toc.ts
new file mode 100644
index 000000000..0337faa36
--- /dev/null
+++ b/src/components/markdown-renderer/markdown-it-plugins/document-toc.ts
@@ -0,0 +1,21 @@
+import MarkdownIt from 'markdown-it'
+import toc from 'markdown-it-toc-done-right'
+import { TocAst } from '../../../external-types/markdown-it-toc-done-right/interface'
+import { slugify } from '../../editor/table-of-contents/table-of-contents'
+
+export type DocumentTocPluginOptions = (ast: TocAst) => void
+
+export const documentToc:MarkdownIt.PluginWithOptions = (markdownIt, onToc) => {
+ if (!onToc) {
+ return
+ }
+ toc(markdownIt, {
+ placeholder: '(\\[TOC\\]|\\[toc\\])',
+ listType: 'ul',
+ level: [1, 2, 3],
+ callback: (code: string, ast: TocAst): void => {
+ onToc(ast)
+ },
+ slugify: slugify
+ })
+}
diff --git a/src/components/markdown-renderer/markdown-it-plugins/frontmatter.ts b/src/components/markdown-renderer/markdown-it-plugins/frontmatter.ts
new file mode 100644
index 000000000..a0d723e03
--- /dev/null
+++ b/src/components/markdown-renderer/markdown-it-plugins/frontmatter.ts
@@ -0,0 +1,26 @@
+import yaml from 'js-yaml'
+import MarkdownIt from 'markdown-it'
+import frontmatter from 'markdown-it-front-matter'
+import { RawYAMLMetadata } from '../../editor/yaml-metadata/yaml-metadata'
+
+interface FrontmatterPluginOptions {
+ onYamlError: (error: boolean) => void,
+ onRawMeta: (rawMeta: RawYAMLMetadata) => void,
+}
+
+export const frontmatterExtract: MarkdownIt.PluginWithOptions = (markdownIt: MarkdownIt, options) => {
+ if (!options) {
+ return
+ }
+ frontmatter(markdownIt, (rawMeta: string) => {
+ try {
+ const meta: RawYAMLMetadata = yaml.safeLoad(rawMeta) as RawYAMLMetadata
+ options.onYamlError(false)
+ options.onRawMeta(meta)
+ } catch (e) {
+ console.error(e)
+ options.onYamlError(true)
+ options.onRawMeta({} as RawYAMLMetadata)
+ }
+ })
+}
diff --git a/src/components/markdown-renderer/markdown-it-plugins/headline-anchors.ts b/src/components/markdown-renderer/markdown-it-plugins/headline-anchors.ts
new file mode 100644
index 000000000..7e29d8262
--- /dev/null
+++ b/src/components/markdown-renderer/markdown-it-plugins/headline-anchors.ts
@@ -0,0 +1,12 @@
+import MarkdownIt from 'markdown-it'
+import anchor from 'markdown-it-anchor'
+
+export const headlineAnchors: MarkdownIt.PluginSimple = (markdownIt) => {
+ // noinspection CheckTagEmptyBody
+ anchor(markdownIt, {
+ permalink: true,
+ permalinkBefore: true,
+ permalinkClass: 'heading-anchor text-dark',
+ permalinkSymbol: ''
+ })
+}
diff --git a/src/components/markdown-renderer/markdown-it-plugins/line-number-marker.ts b/src/components/markdown-renderer/markdown-it-plugins/line-number-marker.ts
deleted file mode 100644
index d636709dd..000000000
--- a/src/components/markdown-renderer/markdown-it-plugins/line-number-marker.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-import MarkdownIt from 'markdown-it/lib'
-import Token from 'markdown-it/lib/token'
-
-export interface LineMarkers {
- startLine: number
- endLine: number
-}
-
-export interface LineNumberMarkerOptions {
- postLineMarkers: (lineMarkers: LineMarkers[]) => void
-}
-
-/**
- * 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 lineNumberMarker: () => MarkdownIt.PluginWithOptions = () => {
- return (md: MarkdownIt, options) => {
- // add app_linemarker token before each opening or self-closing level-0 tag
- md.core.ruler.push('line_number_marker', (state) => {
- const lineMarkers: LineMarkers[] = []
- tagTokens(state.tokens, lineMarkers)
- if (options?.postLineMarkers) {
- options.postLineMarkers(lineMarkers)
- }
- return true
- })
-
- md.renderer.rules.app_linemarker = (tokens: Token[], index: number): string => {
- 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 ``
- }
-
- const insertNewLineMarker = (startLineNumber: number, endLineNumber: number, tokenPosition: number, level: number, tokens: Token[]) => {
- const startToken = new Token('app_linemarker', 'app-linemarker', 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) {
- 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, endLine: endLineNumber })
- }
-
- insertNewLineMarker(startLineNumber, endLineNumber, tokenPosition, token.level, tokens)
- tokenPosition += 1
-
- if (token.children) {
- tagTokens(token.children, lineMarkers)
- }
- }
- }
- }
-}
diff --git a/src/components/markdown-renderer/markdown-it-plugins/parser-debugger.ts b/src/components/markdown-renderer/markdown-it-plugins/parser-debugger.ts
index 5a6ba13dc..8518eb746 100644
--- a/src/components/markdown-renderer/markdown-it-plugins/parser-debugger.ts
+++ b/src/components/markdown-renderer/markdown-it-plugins/parser-debugger.ts
@@ -1,8 +1,10 @@
import MarkdownIt from 'markdown-it/lib'
export const MarkdownItParserDebugger: MarkdownIt.PluginSimple = (md: MarkdownIt) => {
- md.core.ruler.push('test', (state) => {
- console.log(state)
- return true
- })
+ if (process.env.NODE_ENV !== 'production') {
+ md.core.ruler.push('test', (state) => {
+ console.log(state)
+ return true
+ })
+ }
}
diff --git a/src/components/markdown-renderer/markdown-it-plugins/plantuml-error.ts b/src/components/markdown-renderer/markdown-it-plugins/plantuml.ts
similarity index 52%
rename from src/components/markdown-renderer/markdown-it-plugins/plantuml-error.ts
rename to src/components/markdown-renderer/markdown-it-plugins/plantuml.ts
index 355d3d827..9b6e710f6 100644
--- a/src/components/markdown-renderer/markdown-it-plugins/plantuml-error.ts
+++ b/src/components/markdown-renderer/markdown-it-plugins/plantuml.ts
@@ -1,8 +1,24 @@
+import plantuml from 'markdown-it-plantuml'
import MarkdownIt, { Options } from 'markdown-it/lib'
import Renderer, { RenderRule } from 'markdown-it/lib/renderer'
import Token from 'markdown-it/lib/token'
+import { store } from '../../../redux'
+import { MarkdownItPlugin } from '../replace-components/ComponentReplacer'
-export const plantumlError: MarkdownIt.PluginSimple = (md) => {
+export const plantumlWithError: MarkdownItPlugin = (markdownIt: MarkdownIt) => {
+ const plantumlServer = store.getState().config.plantumlServer
+ if (plantumlServer) {
+ plantuml(markdownIt, {
+ openMarker: '```plantuml',
+ closeMarker: '```',
+ server: plantumlServer
+ })
+ } else {
+ plantumlError(markdownIt)
+ }
+}
+
+const plantumlError: MarkdownIt.PluginSimple = (md) => {
const defaultRenderer: RenderRule = md.renderer.rules.fence || (() => '')
md.renderer.rules.fence = (tokens: Token[], idx: number, options: Options, env, slf: Renderer) => {
const token = tokens[idx]
diff --git a/src/components/markdown-renderer/regex-plugins/replace-quote-extra-color.ts b/src/components/markdown-renderer/markdown-it-plugins/quote-extra.ts
similarity index 64%
rename from src/components/markdown-renderer/regex-plugins/replace-quote-extra-color.ts
rename to src/components/markdown-renderer/markdown-it-plugins/quote-extra.ts
index 41c868f01..94db71527 100644
--- a/src/components/markdown-renderer/regex-plugins/replace-quote-extra-color.ts
+++ b/src/components/markdown-renderer/markdown-it-plugins/quote-extra.ts
@@ -1,8 +1,26 @@
+import MarkdownIt from 'markdown-it'
+import markdownItRegex from 'markdown-it-regex'
import { RegexOptions } from '../../../external-types/markdown-it-regex/interface'
+export const quoteExtra: MarkdownIt.PluginSimple = (markdownIt) => {
+ markdownItRegex(markdownIt, replaceQuoteExtraAuthor)
+ markdownItRegex(markdownIt, replaceQuoteExtraColor)
+ markdownItRegex(markdownIt, replaceQuoteExtraTime)
+}
+
+const replaceQuoteExtraTime: RegexOptions = {
+ name: 'quote-extra-time',
+ regex: /\[time=([^\]]+)]/,
+ replace: (match) => {
+ // ESLint wants to collapse this tag, but then the tag won't be valid html anymore.
+ // noinspection CheckTagEmptyBody
+ return ``
+ }
+}
+
const cssColorRegex = /\[color=(#(?:[0-9a-f]{2}){2,4}|(?:#[0-9a-f]{3})|black|silver|gray|whitesmoke|maroon|red|purple|fuchsia|green|lime|olivedrab|yellow|navy|blue|teal|aquamarine|orange|aliceblue|antiquewhite|aqua|azure|beige|bisque|blanchedalmond|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|currentcolor|darkblue|darkcyan|darkgoldenrod|darkgray|darkgreen|darkgrey|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|firebrick|floralwhite|forestgreen|gainsboro|ghostwhite|goldenrod|gold|greenyellow|grey|honeydew|hotpink|indianred|indigo|ivory|khaki|lavenderblush|lavender|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgreen|lightgrey|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|lightyellow|limegreen|linen|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|oldlace|olive|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum|powderblue|rebeccapurple|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|skyblue|slateblue|slategray|slategrey|snow|springgreen|steelblue|tan|thistle|tomato|transparent|turquoise|violet|wheat|white|yellowgreen)]/i
-export const replaceQuoteExtraColor: RegexOptions = {
+const replaceQuoteExtraColor: RegexOptions = {
name: 'quote-extra-color',
regex: cssColorRegex,
replace: (match) => {
@@ -11,3 +29,13 @@ export const replaceQuoteExtraColor: RegexOptions = {
return ``
}
}
+
+const replaceQuoteExtraAuthor: RegexOptions = {
+ name: 'quote-extra-name',
+ regex: /\[name=([^\]]+)]/,
+ replace: (match) => {
+ // ESLint wants to collapse this tag, but then the tag won't be valid html anymore.
+ // noinspection CheckTagEmptyBody
+ return ``
+ }
+}
diff --git a/src/components/markdown-renderer/markdown-it-plugins/tasks-lists.ts b/src/components/markdown-renderer/markdown-it-plugins/tasks-lists.ts
new file mode 100644
index 000000000..8f01f01f3
--- /dev/null
+++ b/src/components/markdown-renderer/markdown-it-plugins/tasks-lists.ts
@@ -0,0 +1,11 @@
+import markdownItTaskLists from '@hedgedoc/markdown-it-task-lists'
+import MarkdownIt from 'markdown-it'
+
+export const tasksLists: MarkdownIt.PluginSimple = (markdownIt) => {
+ markdownItTaskLists(markdownIt, {
+ enabled: true,
+ label: true,
+ labelAfter: true,
+ lineNumber: true
+ })
+}
diff --git a/src/components/markdown-renderer/markdown-it-plugins/twitter-emojis.ts b/src/components/markdown-renderer/markdown-it-plugins/twitter-emojis.ts
new file mode 100644
index 000000000..afb75a494
--- /dev/null
+++ b/src/components/markdown-renderer/markdown-it-plugins/twitter-emojis.ts
@@ -0,0 +1,9 @@
+import MarkdownIt from 'markdown-it'
+import emoji from 'markdown-it-emoji'
+import { combinedEmojiData } from './emoji/mapping'
+
+export const twitterEmojis: MarkdownIt.PluginSimple = (markdownIt) => {
+ emoji(markdownIt, {
+ defs: combinedEmojiData
+ })
+}
diff --git a/src/components/markdown-renderer/regex-plugins/replace-legacy-slideshare-short-code.ts b/src/components/markdown-renderer/regex-plugins/replace-legacy-slideshare-short-code.ts
index ec7282b47..35f814ebf 100644
--- a/src/components/markdown-renderer/regex-plugins/replace-legacy-slideshare-short-code.ts
+++ b/src/components/markdown-renderer/regex-plugins/replace-legacy-slideshare-short-code.ts
@@ -1,7 +1,13 @@
+import markdownItRegex from 'markdown-it-regex'
+import MarkdownIt from 'markdown-it/lib'
import { RegexOptions } from '../../../external-types/markdown-it-regex/interface'
const finalRegex = /^{%slideshare (\w+\/[\w-]+) ?%}$/
+export const legacySlideshareShortCode: MarkdownIt.PluginSimple = (markdownIt) => {
+ markdownItRegex(markdownIt, replaceLegacySlideshareShortCode)
+}
+
export const replaceLegacySlideshareShortCode: RegexOptions = {
name: 'legacy-slideshare-short-code',
regex: finalRegex,
diff --git a/src/components/markdown-renderer/regex-plugins/replace-legacy-speakerdeck-short-code.ts b/src/components/markdown-renderer/regex-plugins/replace-legacy-speakerdeck-short-code.ts
index f5267aa43..0a20949c0 100644
--- a/src/components/markdown-renderer/regex-plugins/replace-legacy-speakerdeck-short-code.ts
+++ b/src/components/markdown-renderer/regex-plugins/replace-legacy-speakerdeck-short-code.ts
@@ -1,7 +1,13 @@
+import markdownItRegex from 'markdown-it-regex'
+import MarkdownIt from 'markdown-it/lib'
import { RegexOptions } from '../../../external-types/markdown-it-regex/interface'
const finalRegex = /^{%speakerdeck (\w+\/[\w-]+) ?%}$/
+export const legacySpeakerdeckShortCode: MarkdownIt.PluginSimple = (markdownIt) => {
+ markdownItRegex(markdownIt, replaceLegacySpeakerdeckShortCode)
+}
+
export const replaceLegacySpeakerdeckShortCode: RegexOptions = {
name: 'legacy-speakerdeck-short-code',
regex: finalRegex,
diff --git a/src/components/markdown-renderer/regex-plugins/replace-quote-extra-author.ts b/src/components/markdown-renderer/regex-plugins/replace-quote-extra-author.ts
deleted file mode 100644
index ae97acd89..000000000
--- a/src/components/markdown-renderer/regex-plugins/replace-quote-extra-author.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { RegexOptions } from '../../../external-types/markdown-it-regex/interface'
-
-export const replaceQuoteExtraAuthor: RegexOptions = {
- name: 'quote-extra-name',
- regex: /\[name=([^\]]+)]/,
- replace: (match) => {
- // ESLint wants to collapse this tag, but then the tag won't be valid html anymore.
- // noinspection CheckTagEmptyBody
- return ``
- }
-}
diff --git a/src/components/markdown-renderer/regex-plugins/replace-quote-extra-time.ts b/src/components/markdown-renderer/regex-plugins/replace-quote-extra-time.ts
deleted file mode 100644
index c8c0ae3ed..000000000
--- a/src/components/markdown-renderer/regex-plugins/replace-quote-extra-time.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { RegexOptions } from '../../../external-types/markdown-it-regex/interface'
-
-export const replaceQuoteExtraTime: RegexOptions = {
- name: 'quote-extra-time',
- regex: /\[time=([^\]]+)]/,
- replace: (match) => {
- // ESLint wants to collapse this tag, but then the tag won't be valid html anymore.
- // noinspection CheckTagEmptyBody
- return ``
- }
-}
diff --git a/src/components/markdown-renderer/replace-components/ComponentReplacer.ts b/src/components/markdown-renderer/replace-components/ComponentReplacer.ts
index 217eb9db7..0af4943e4 100644
--- a/src/components/markdown-renderer/replace-components/ComponentReplacer.ts
+++ b/src/components/markdown-renderer/replace-components/ComponentReplacer.ts
@@ -1,10 +1,13 @@
import { DomElement } from 'domhandler'
+import MarkdownIt from 'markdown-it'
import { ReactElement } from 'react'
export type SubNodeTransform = (node: DomElement, subIndex: number) => ReactElement | void | null
export type NativeRenderer = (node: DomElement, key: number) => ReactElement
+export type MarkdownItPlugin = MarkdownIt.PluginSimple | MarkdownIt.PluginWithOptions | MarkdownIt.PluginWithParams
+
export abstract class ComponentReplacer {
- public abstract getReplacement(node: DomElement, subNodeTransform: SubNodeTransform): (ReactElement | null | undefined);
+ public abstract getReplacement (node: DomElement, subNodeTransform: SubNodeTransform): (ReactElement | null | undefined);
}
diff --git a/src/components/markdown-renderer/replace-components/asciinema/asciinema-replacer.tsx b/src/components/markdown-renderer/replace-components/asciinema/asciinema-replacer.tsx
index b5f8bc297..ba2b8253c 100644
--- a/src/components/markdown-renderer/replace-components/asciinema/asciinema-replacer.tsx
+++ b/src/components/markdown-renderer/replace-components/asciinema/asciinema-replacer.tsx
@@ -1,8 +1,11 @@
import { DomElement } from 'domhandler'
+import MarkdownIt from 'markdown-it'
+import markdownItRegex from 'markdown-it-regex'
import React from 'react'
-import { getAttributesFromHedgeDocTag } from '../utils'
import { ComponentReplacer } from '../ComponentReplacer'
+import { getAttributesFromHedgeDocTag } from '../utils'
import { AsciinemaFrame } from './asciinema-frame'
+import { replaceAsciinemaLink } from './replace-asciinema-link'
export class AsciinemaReplacer extends ComponentReplacer {
private counterMap: Map = new Map()
@@ -18,4 +21,8 @@ export class AsciinemaReplacer extends ComponentReplacer {
)
}
}
+
+ public static readonly markdownItPlugin: MarkdownIt.PluginSimple = (markdownIt) => {
+ markdownItRegex(markdownIt, replaceAsciinemaLink)
+ }
}
diff --git a/src/components/markdown-renderer/regex-plugins/replace-asciinema-link.ts b/src/components/markdown-renderer/replace-components/asciinema/replace-asciinema-link.ts
similarity index 88%
rename from src/components/markdown-renderer/regex-plugins/replace-asciinema-link.ts
rename to src/components/markdown-renderer/replace-components/asciinema/replace-asciinema-link.ts
index 5a5ea3ba3..3ebe05d55 100644
--- a/src/components/markdown-renderer/regex-plugins/replace-asciinema-link.ts
+++ b/src/components/markdown-renderer/replace-components/asciinema/replace-asciinema-link.ts
@@ -1,4 +1,4 @@
-import { RegexOptions } from '../../../external-types/markdown-it-regex/interface'
+import { RegexOptions } from '../../../../external-types/markdown-it-regex/interface'
const protocolRegex = /(?:http(?:s)?:\/\/)?/
const domainRegex = /(?:asciinema\.org\/a\/)/
diff --git a/src/components/markdown-renderer/replace-components/gist/gist-replacer.tsx b/src/components/markdown-renderer/replace-components/gist/gist-replacer.tsx
index b958eff08..75f46f669 100644
--- a/src/components/markdown-renderer/replace-components/gist/gist-replacer.tsx
+++ b/src/components/markdown-renderer/replace-components/gist/gist-replacer.tsx
@@ -1,5 +1,9 @@
import { DomElement } from 'domhandler'
+import MarkdownIt from 'markdown-it'
+import markdownItRegex from 'markdown-it-regex'
import React from 'react'
+import { replaceGistLink } from './replace-gist-link'
+import { replaceLegacyGistShortCode } from './replace-legacy-gist-short-code'
import { getAttributesFromHedgeDocTag } from '../utils'
import { ComponentReplacer } from '../ComponentReplacer'
import { OneClickEmbedding } from '../one-click-frame/one-click-embedding'
@@ -22,4 +26,9 @@ export class GistReplacer extends ComponentReplacer {
)
}
}
+
+ public static readonly markdownItPlugin: MarkdownIt.PluginSimple = (markdownIt) => {
+ markdownItRegex(markdownIt, replaceGistLink)
+ markdownItRegex(markdownIt, replaceLegacyGistShortCode)
+ }
}
diff --git a/src/components/markdown-renderer/regex-plugins/replace-gist-link.ts b/src/components/markdown-renderer/replace-components/gist/replace-gist-link.ts
similarity index 88%
rename from src/components/markdown-renderer/regex-plugins/replace-gist-link.ts
rename to src/components/markdown-renderer/replace-components/gist/replace-gist-link.ts
index 0a1a1e552..4addd34d6 100644
--- a/src/components/markdown-renderer/regex-plugins/replace-gist-link.ts
+++ b/src/components/markdown-renderer/replace-components/gist/replace-gist-link.ts
@@ -1,4 +1,4 @@
-import { RegexOptions } from '../../../external-types/markdown-it-regex/interface'
+import { RegexOptions } from '../../../../external-types/markdown-it-regex/interface'
const protocolRegex = /(?:http(?:s)?:\/\/)?/
const domainRegex = /(?:gist\.github\.com\/)/
diff --git a/src/components/markdown-renderer/regex-plugins/replace-legacy-gist-short-code.ts b/src/components/markdown-renderer/replace-components/gist/replace-legacy-gist-short-code.ts
similarity index 80%
rename from src/components/markdown-renderer/regex-plugins/replace-legacy-gist-short-code.ts
rename to src/components/markdown-renderer/replace-components/gist/replace-legacy-gist-short-code.ts
index c76ec666d..49d6dd320 100644
--- a/src/components/markdown-renderer/regex-plugins/replace-legacy-gist-short-code.ts
+++ b/src/components/markdown-renderer/replace-components/gist/replace-legacy-gist-short-code.ts
@@ -1,4 +1,4 @@
-import { RegexOptions } from '../../../external-types/markdown-it-regex/interface'
+import { RegexOptions } from '../../../../external-types/markdown-it-regex/interface'
const finalRegex = /^{%gist (\w+\/\w+) ?%}$/
diff --git a/src/components/markdown-renderer/replace-components/katex/katex-replacer.tsx b/src/components/markdown-renderer/replace-components/katex/katex-replacer.tsx
index 6dcec4815..3b90ef87a 100644
--- a/src/components/markdown-renderer/replace-components/katex/katex-replacer.tsx
+++ b/src/components/markdown-renderer/replace-components/katex/katex-replacer.tsx
@@ -1,9 +1,11 @@
import { DomElement } from 'domhandler'
+import MarkdownIt from 'markdown-it'
+import mathJax from 'markdown-it-mathjax'
import React from 'react'
import { ComponentReplacer } from '../ComponentReplacer'
import './katex.scss'
-const getNodeIfKatexBlock = (node: DomElement): (DomElement|undefined) => {
+const getNodeIfKatexBlock = (node: DomElement): (DomElement | undefined) => {
if (node.name !== 'p' || !node.children || node.children.length === 0) {
return
}
@@ -12,7 +14,7 @@ const getNodeIfKatexBlock = (node: DomElement): (DomElement|undefined) => {
})
}
-const getNodeIfInlineKatex = (node: DomElement): (DomElement|undefined) => {
+const getNodeIfInlineKatex = (node: DomElement): (DomElement | undefined) => {
return (node.name === 'app-katex' && node.attribs?.inline !== undefined) ? node : undefined
}
@@ -27,4 +29,13 @@ export class KatexReplacer extends ComponentReplacer {
return
}
}
+
+ public static readonly markdownItPlugin: MarkdownIt.PluginSimple = mathJax({
+ beforeMath: '',
+ afterMath: '',
+ beforeInlineMath: '',
+ afterInlineMath: '',
+ beforeDisplayMath: '',
+ afterDisplayMath: ''
+ })
}
diff --git a/src/components/markdown-renderer/replace-components/linemarker/line-number-marker.ts b/src/components/markdown-renderer/replace-components/linemarker/line-number-marker.ts
new file mode 100644
index 000000000..2f560317e
--- /dev/null
+++ b/src/components/markdown-renderer/replace-components/linemarker/line-number-marker.ts
@@ -0,0 +1,72 @@
+import MarkdownIt from 'markdown-it/lib'
+import Token from 'markdown-it/lib/token'
+
+export interface LineMarkers {
+ startLine: number
+ endLine: number
+}
+
+export type LineNumberMarkerOptions = (lineMarkers: LineMarkers[]) => void;
+
+/**
+ * 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 lineNumberMarker: MarkdownIt.PluginWithOptions = (md: MarkdownIt, options) => {
+ // add app_linemarker token before each opening or self-closing level-0 tag
+ md.core.ruler.push('line_number_marker', (state) => {
+ const lineMarkers: LineMarkers[] = []
+ tagTokens(state.tokens, lineMarkers)
+ if (options) {
+ options(lineMarkers)
+ }
+ return true
+ })
+
+ md.renderer.rules.app_linemarker = (tokens: Token[], index: number): string => {
+ 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 ``
+ }
+
+ const insertNewLineMarker = (startLineNumber: number, endLineNumber: number, tokenPosition: number, level: number, tokens: Token[]) => {
+ const startToken = new Token('app_linemarker', 'app-linemarker', 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) {
+ 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, endLine: endLineNumber })
+ }
+
+ insertNewLineMarker(startLineNumber, endLineNumber, tokenPosition, token.level, tokens)
+ tokenPosition += 1
+
+ if (token.children) {
+ tagTokens(token.children, lineMarkers)
+ }
+ }
+ }
+}
diff --git a/src/components/markdown-renderer/replace-components/pdf/pdf-replacer.tsx b/src/components/markdown-renderer/replace-components/pdf/pdf-replacer.tsx
index d83b79ce5..75f5485c1 100644
--- a/src/components/markdown-renderer/replace-components/pdf/pdf-replacer.tsx
+++ b/src/components/markdown-renderer/replace-components/pdf/pdf-replacer.tsx
@@ -1,5 +1,8 @@
import { DomElement } from 'domhandler'
+import MarkdownIt from 'markdown-it'
+import markdownItRegex from 'markdown-it-regex'
import React from 'react'
+import { replacePdfShortCode } from './replace-pdf-short-code'
import { getAttributesFromHedgeDocTag } from '../utils'
import { ComponentReplacer } from '../ComponentReplacer'
import { PdfFrame } from './pdf-frame'
@@ -16,4 +19,8 @@ export class PdfReplacer extends ComponentReplacer {
return
}
}
+
+ public static readonly markdownItPlugin: MarkdownIt.PluginSimple = (markdownIt) => {
+ markdownItRegex(markdownIt, replacePdfShortCode)
+ }
}
diff --git a/src/components/markdown-renderer/regex-plugins/replace-pdf-short-code.ts b/src/components/markdown-renderer/replace-components/pdf/replace-pdf-short-code.ts
similarity index 78%
rename from src/components/markdown-renderer/regex-plugins/replace-pdf-short-code.ts
rename to src/components/markdown-renderer/replace-components/pdf/replace-pdf-short-code.ts
index a851feb66..4fa010ed2 100644
--- a/src/components/markdown-renderer/regex-plugins/replace-pdf-short-code.ts
+++ b/src/components/markdown-renderer/replace-components/pdf/replace-pdf-short-code.ts
@@ -1,4 +1,4 @@
-import { RegexOptions } from '../../../external-types/markdown-it-regex/interface'
+import { RegexOptions } from '../../../../external-types/markdown-it-regex/interface'
export const replacePdfShortCode: RegexOptions = {
name: 'pdf-short-code',
diff --git a/src/components/markdown-renderer/regex-plugins/replace-legacy-vimeo-short-code.ts b/src/components/markdown-renderer/replace-components/vimeo/replace-legacy-vimeo-short-code.ts
similarity index 79%
rename from src/components/markdown-renderer/regex-plugins/replace-legacy-vimeo-short-code.ts
rename to src/components/markdown-renderer/replace-components/vimeo/replace-legacy-vimeo-short-code.ts
index 9c069d6b8..9b6aaddd1 100644
--- a/src/components/markdown-renderer/regex-plugins/replace-legacy-vimeo-short-code.ts
+++ b/src/components/markdown-renderer/replace-components/vimeo/replace-legacy-vimeo-short-code.ts
@@ -1,4 +1,4 @@
-import { RegexOptions } from '../../../external-types/markdown-it-regex/interface'
+import { RegexOptions } from '../../../../external-types/markdown-it-regex/interface'
export const replaceLegacyVimeoShortCode: RegexOptions = {
name: 'legacy-vimeo-short-code',
diff --git a/src/components/markdown-renderer/regex-plugins/replace-vimeo-link.ts b/src/components/markdown-renderer/replace-components/vimeo/replace-vimeo-link.ts
similarity index 89%
rename from src/components/markdown-renderer/regex-plugins/replace-vimeo-link.ts
rename to src/components/markdown-renderer/replace-components/vimeo/replace-vimeo-link.ts
index db363d294..ede009f71 100644
--- a/src/components/markdown-renderer/regex-plugins/replace-vimeo-link.ts
+++ b/src/components/markdown-renderer/replace-components/vimeo/replace-vimeo-link.ts
@@ -1,4 +1,4 @@
-import { RegexOptions } from '../../../external-types/markdown-it-regex/interface'
+import { RegexOptions } from '../../../../external-types/markdown-it-regex/interface'
const protocolRegex = /(?:http(?:s)?:\/\/)?/
const domainRegex = /(?:player\.)?(?:vimeo\.com\/)(?:(?:channels|album|ondemand|groups)\/\w+\/)?(?:video\/)?/
diff --git a/src/components/markdown-renderer/replace-components/vimeo/vimeo-replacer.tsx b/src/components/markdown-renderer/replace-components/vimeo/vimeo-replacer.tsx
index 154d430ce..0734b1d66 100644
--- a/src/components/markdown-renderer/replace-components/vimeo/vimeo-replacer.tsx
+++ b/src/components/markdown-renderer/replace-components/vimeo/vimeo-replacer.tsx
@@ -1,7 +1,11 @@
import { DomElement } from 'domhandler'
+import MarkdownIt from 'markdown-it'
+import markdownItRegex from 'markdown-it-regex'
import React from 'react'
-import { getAttributesFromHedgeDocTag } from '../utils'
import { ComponentReplacer } from '../ComponentReplacer'
+import { getAttributesFromHedgeDocTag } from '../utils'
+import { replaceLegacyVimeoShortCode } from './replace-legacy-vimeo-short-code'
+import { replaceVimeoLink } from './replace-vimeo-link'
import { VimeoFrame } from './vimeo-frame'
export class VimeoReplacer extends ComponentReplacer {
@@ -16,4 +20,9 @@ export class VimeoReplacer extends ComponentReplacer {
return
}
}
+
+ public static readonly markdownItPlugin: MarkdownIt.PluginSimple = (markdownIt) => {
+ markdownItRegex(markdownIt, replaceVimeoLink)
+ markdownItRegex(markdownIt, replaceLegacyVimeoShortCode)
+ }
}
diff --git a/src/components/markdown-renderer/regex-plugins/replace-legacy-youtube-short-code.ts b/src/components/markdown-renderer/replace-components/youtube/replace-legacy-youtube-short-code.ts
similarity index 80%
rename from src/components/markdown-renderer/regex-plugins/replace-legacy-youtube-short-code.ts
rename to src/components/markdown-renderer/replace-components/youtube/replace-legacy-youtube-short-code.ts
index 87aa345b1..e0f0d1e99 100644
--- a/src/components/markdown-renderer/regex-plugins/replace-legacy-youtube-short-code.ts
+++ b/src/components/markdown-renderer/replace-components/youtube/replace-legacy-youtube-short-code.ts
@@ -1,4 +1,4 @@
-import { RegexOptions } from '../../../external-types/markdown-it-regex/interface'
+import { RegexOptions } from '../../../../external-types/markdown-it-regex/interface'
export const replaceLegacyYoutubeShortCode: RegexOptions = {
name: 'legacy-youtube-short-code',
diff --git a/src/components/markdown-renderer/regex-plugins/replace-youtube-link.ts b/src/components/markdown-renderer/replace-components/youtube/replace-youtube-link.ts
similarity index 90%
rename from src/components/markdown-renderer/regex-plugins/replace-youtube-link.ts
rename to src/components/markdown-renderer/replace-components/youtube/replace-youtube-link.ts
index 1b509e2da..b80de0953 100644
--- a/src/components/markdown-renderer/regex-plugins/replace-youtube-link.ts
+++ b/src/components/markdown-renderer/replace-components/youtube/replace-youtube-link.ts
@@ -1,4 +1,4 @@
-import { RegexOptions } from '../../../external-types/markdown-it-regex/interface'
+import { RegexOptions } from '../../../../external-types/markdown-it-regex/interface'
const protocolRegex = /(?:http(?:s)?:\/\/)?/
const subdomainRegex = /(?:www.)?/
diff --git a/src/components/markdown-renderer/replace-components/youtube/youtube-replacer.tsx b/src/components/markdown-renderer/replace-components/youtube/youtube-replacer.tsx
index 4528f05e3..16f8d847f 100644
--- a/src/components/markdown-renderer/replace-components/youtube/youtube-replacer.tsx
+++ b/src/components/markdown-renderer/replace-components/youtube/youtube-replacer.tsx
@@ -1,7 +1,11 @@
import { DomElement } from 'domhandler'
+import MarkdownIt from 'markdown-it'
+import markdownItRegex from 'markdown-it-regex'
import React from 'react'
-import { getAttributesFromHedgeDocTag } from '../utils'
import { ComponentReplacer } from '../ComponentReplacer'
+import { getAttributesFromHedgeDocTag } from '../utils'
+import { replaceLegacyYoutubeShortCode } from './replace-legacy-youtube-short-code'
+import { replaceYouTubeLink } from './replace-youtube-link'
import { YouTubeFrame } from './youtube-frame'
export class YoutubeReplacer extends ComponentReplacer {
@@ -16,4 +20,9 @@ export class YoutubeReplacer extends ComponentReplacer {
return
}
}
+
+ public static readonly markdownItPlugin: MarkdownIt.PluginSimple = (markdownIt) => {
+ markdownItRegex(markdownIt, replaceYouTubeLink)
+ markdownItRegex(markdownIt, replaceLegacyYoutubeShortCode)
+ }
}
diff --git a/src/components/markdown-renderer/utils/calculate-line-marker-positions.ts b/src/components/markdown-renderer/utils/calculate-line-marker-positions.ts
index ae77a5d15..50fba00bb 100644
--- a/src/components/markdown-renderer/utils/calculate-line-marker-positions.ts
+++ b/src/components/markdown-renderer/utils/calculate-line-marker-positions.ts
@@ -2,7 +2,7 @@ import equal from 'fast-deep-equal'
import { RefObject, useCallback, useEffect, useRef } from 'react'
import useResizeObserver from 'use-resize-observer'
import { LineMarkerPosition } from '../types'
-import { LineMarkers } from '../markdown-it-plugins/line-number-marker'
+import { LineMarkers } from '../replace-components/linemarker/line-number-marker'
export const calculateLineMarkerPositions = (documentElement: HTMLDivElement, currentLineMarkers: LineMarkers[], offset?: number): LineMarkerPosition[] => {
const lineMarkers = currentLineMarkers
diff --git a/src/external-types/markdown-it-front-matter/index.d.ts b/src/external-types/markdown-it-front-matter/index.d.ts
index ab9c8f145..e022c97db 100644
--- a/src/external-types/markdown-it-front-matter/index.d.ts
+++ b/src/external-types/markdown-it-front-matter/index.d.ts
@@ -1,5 +1,7 @@
+
declare module 'markdown-it-front-matter' {
import MarkdownIt from 'markdown-it/lib'
- const markdownItFrontMatter: MarkdownIt.PluginSimple
+ export type FrontMatterPluginOptions = (rawMeta: string) => void
+ const markdownItFrontMatter: MarkdownIt.PluginWithOptions
export = markdownItFrontMatter
}
|