mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-23 02:06:29 -05:00
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:
parent
89968387c2
commit
0670cddb0b
42 changed files with 524 additions and 360 deletions
|
@ -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'}>
|
||||
|
|
|
@ -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])
|
||||
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
|
|
|
@ -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])
|
||||
}
|
|
@ -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])
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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) })
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
})
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
|
@ -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>'
|
||||
})
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]
|
|
@ -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>`
|
||||
}
|
||||
}
|
|
@ -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
|
||||
})
|
||||
}
|
|
@ -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
|
||||
})
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>`
|
||||
}
|
||||
}
|
|
@ -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>`
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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\/)/
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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\/)/
|
|
@ -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+) ?%}$/
|
||||
|
|
@ -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>'
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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',
|
|
@ -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',
|
|
@ -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\/)?/
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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',
|
|
@ -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.)?/
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue