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,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 = () => {
<BasicMarkdownRenderer
content={code}
wide={false}
onConfigureMarkdownIt={markdownItPlugins}
markdownIt={markdownIt}
/>
</td>
<td className={'markdown-body'}>

View file

@ -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<HTMLDivElement>
onBeforeRendering?: () => void
}
@ -36,44 +24,12 @@ export const BasicMarkdownRenderer: React.FC<BasicMarkdownRendererProps & Additi
content,
wide,
componentReplacers,
onConfigureMarkdownIt,
markdownIt,
documentReference,
onBeforeRendering
}) => {
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<LineKeys[]>()
const lastUsedLineId = useRef<number>(0)
@ -87,7 +43,7 @@ export const BasicMarkdownRenderer: React.FC<BasicMarkdownRendererProps & Additi
const { lines: newLines, lastUsedLineId: newLastUsedLineId } = calculateNewLineNumberMapping(contentLines, oldMarkdownLineKeys.current ?? [], lastUsedLineId.current)
oldMarkdownLineKeys.current = newLines
lastUsedLineId.current = newLastUsedLineId
const transformer = componentReplacers ? buildTransformer(newLines, componentReplacers) : undefined
const transformer = componentReplacers ? buildTransformer(newLines, componentReplacers()) : undefined
return ReactHtmlParser(html, { transform: transformer })
}, [onBeforeRendering, content, maxLength, markdownIt, componentReplacers])

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

View file

@ -0,0 +1,24 @@
import React, { useCallback, useEffect } from 'react'
export const useExtractFirstHeadline = (documentElement: React.RefObject<HTMLDivElement>, 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])
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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: '<i class="fa fa-link"></i>'
})
}

View file

@ -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<LineNumberMarkerOptions> = () => {
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 `<app-linemarker data-start-line='${startLineNumber}' data-end-line='${endLineNumber}'></app-linemarker>`
}
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)
}
}
}
}
}

View file

@ -1,8 +1,10 @@
import MarkdownIt from 'markdown-it/lib'
export const MarkdownItParserDebugger: MarkdownIt.PluginSimple = (md: MarkdownIt) => {
if (process.env.NODE_ENV !== 'production') {
md.core.ruler.push('test', (state) => {
console.log(state)
return true
})
}
}

View file

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

View file

@ -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 `<span class="quote-extra"><i class="fa fa-clock-o mx-1"></i> ${match}</span>`
}
}
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 `<span class="quote-extra" data-color='${match}' style='color: ${match}'><i class="fa fa-tag"></i></span>`
}
}
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 `<span class="quote-extra"><i class="fa fa-user mx-1"></i> ${match}</span>`
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -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 `<span class="quote-extra"><i class="fa fa-user mx-1"></i> ${match}</span>`
}
}

View file

@ -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 `<span class="quote-extra"><i class="fa fa-clock-o mx-1"></i> ${match}</span>`
}
}

View file

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

View file

@ -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<string, number> = new Map<string, number>()
@ -18,4 +21,8 @@ export class AsciinemaReplacer extends ComponentReplacer {
)
}
}
public static readonly markdownItPlugin: MarkdownIt.PluginSimple = (markdownIt) => {
markdownItRegex(markdownIt, replaceAsciinemaLink)
}
}

View file

@ -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\/)/

View file

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

View file

@ -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\/)/

View file

@ -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+) ?%}$/

View file

@ -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 <KaTeX block={!isInline} math={mathJaxContent} errorColor={'#cc0000'}/>
}
}
public static readonly markdownItPlugin: MarkdownIt.PluginSimple = mathJax({
beforeMath: '<app-katex>',
afterMath: '</app-katex>',
beforeInlineMath: '<app-katex inline>',
afterInlineMath: '</app-katex>',
beforeDisplayMath: '<app-katex>',
afterDisplayMath: '</app-katex>'
})
}

View file

@ -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<LineNumberMarkerOptions> = (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 `<app-linemarker data-start-line='${startLineNumber}' data-end-line='${endLineNumber}'></app-linemarker>`
}
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)
}
}
}
}

View file

@ -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 <PdfFrame url={pdfUrl}/>
}
}
public static readonly markdownItPlugin: MarkdownIt.PluginSimple = (markdownIt) => {
markdownItRegex(markdownIt, replacePdfShortCode)
}
}

View file

@ -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',

View file

@ -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',

View file

@ -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\/)?/

View file

@ -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 <VimeoFrame id={videoId}/>
}
}
public static readonly markdownItPlugin: MarkdownIt.PluginSimple = (markdownIt) => {
markdownItRegex(markdownIt, replaceVimeoLink)
markdownItRegex(markdownIt, replaceLegacyVimeoShortCode)
}
}

View file

@ -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',

View file

@ -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.)?/

View file

@ -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 <YouTubeFrame id={videoId}/>
}
}
public static readonly markdownItPlugin: MarkdownIt.PluginSimple = (markdownIt) => {
markdownItRegex(markdownIt, replaceYouTubeLink)
markdownItRegex(markdownIt, replaceLegacyYoutubeShortCode)
}
}

View file

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

View file

@ -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<FrontMatterPluginOptions>
export = markdownItFrontMatter
}