mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-26 19:53:59 -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 React, { useMemo } from 'react'
|
||||||
import markdownItContainer from 'markdown-it-container'
|
|
||||||
import React, { useCallback } from 'react'
|
|
||||||
import { Table } from 'react-bootstrap'
|
import { Table } from 'react-bootstrap'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { BasicMarkdownRenderer } from '../../../markdown-renderer/basic-markdown-renderer'
|
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 { HighlightedCode } from '../../../markdown-renderer/replace-components/highlighted-fence/highlighted-code/highlighted-code'
|
||||||
import './cheatsheet.scss'
|
import './cheatsheet.scss'
|
||||||
|
|
||||||
|
@ -31,10 +30,10 @@ export const Cheatsheet: React.FC = () => {
|
||||||
`:::info\n${t('editor.help.cheatsheet.exampleAlert')}\n:::`
|
`:::info\n${t('editor.help.cheatsheet.exampleAlert')}\n:::`
|
||||||
]
|
]
|
||||||
|
|
||||||
const markdownItPlugins = useCallback((md: MarkdownIt) => {
|
const markdownIt = useMemo(() => {
|
||||||
validAlertLevels.forEach(level => {
|
return new BasicMarkdownItConfigurator()
|
||||||
md.use(markdownItContainer, level, { render: createRenderContainer(level) })
|
.pushConfig(alertContainer)
|
||||||
})
|
.buildConfiguredMarkdownIt()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -53,7 +52,7 @@ export const Cheatsheet: React.FC = () => {
|
||||||
<BasicMarkdownRenderer
|
<BasicMarkdownRenderer
|
||||||
content={code}
|
content={code}
|
||||||
wide={false}
|
wide={false}
|
||||||
onConfigureMarkdownIt={markdownItPlugins}
|
markdownIt={markdownIt}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td className={'markdown-body'}>
|
<td className={'markdown-body'}>
|
||||||
|
|
|
@ -1,13 +1,4 @@
|
||||||
import MarkdownIt from 'markdown-it'
|
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 React, { ReactElement, RefObject, useMemo, useRef } from 'react'
|
||||||
import { Alert } from 'react-bootstrap'
|
import { Alert } from 'react-bootstrap'
|
||||||
import ReactHtmlParser from 'react-html-parser'
|
import ReactHtmlParser from 'react-html-parser'
|
||||||
|
@ -15,9 +6,6 @@ import { Trans } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import { ApplicationState } from '../../redux'
|
import { ApplicationState } from '../../redux'
|
||||||
import { ShowIf } from '../common/show-if/show-if'
|
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 './markdown-renderer.scss'
|
||||||
import { ComponentReplacer } from './replace-components/ComponentReplacer'
|
import { ComponentReplacer } from './replace-components/ComponentReplacer'
|
||||||
import { AdditionalMarkdownRendererProps, LineKeys } from './types'
|
import { AdditionalMarkdownRendererProps, LineKeys } from './types'
|
||||||
|
@ -25,8 +13,8 @@ import { buildTransformer } from './utils/html-react-transformer'
|
||||||
import { calculateNewLineNumberMapping } from './utils/line-number-mapping'
|
import { calculateNewLineNumberMapping } from './utils/line-number-mapping'
|
||||||
|
|
||||||
export interface BasicMarkdownRendererProps {
|
export interface BasicMarkdownRendererProps {
|
||||||
componentReplacers?: ComponentReplacer[],
|
componentReplacers?: () => ComponentReplacer[],
|
||||||
onConfigureMarkdownIt?: (md: MarkdownIt) => void,
|
markdownIt: MarkdownIt,
|
||||||
documentReference?: RefObject<HTMLDivElement>
|
documentReference?: RefObject<HTMLDivElement>
|
||||||
onBeforeRendering?: () => void
|
onBeforeRendering?: () => void
|
||||||
}
|
}
|
||||||
|
@ -36,44 +24,12 @@ export const BasicMarkdownRenderer: React.FC<BasicMarkdownRendererProps & Additi
|
||||||
content,
|
content,
|
||||||
wide,
|
wide,
|
||||||
componentReplacers,
|
componentReplacers,
|
||||||
onConfigureMarkdownIt,
|
markdownIt,
|
||||||
documentReference,
|
documentReference,
|
||||||
onBeforeRendering
|
onBeforeRendering
|
||||||
}) => {
|
}) => {
|
||||||
const maxLength = useSelector((state: ApplicationState) => state.config.maxDocumentLength)
|
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 oldMarkdownLineKeys = useRef<LineKeys[]>()
|
||||||
const lastUsedLineId = useRef<number>(0)
|
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)
|
const { lines: newLines, lastUsedLineId: newLastUsedLineId } = calculateNewLineNumberMapping(contentLines, oldMarkdownLineKeys.current ?? [], lastUsedLineId.current)
|
||||||
oldMarkdownLineKeys.current = newLines
|
oldMarkdownLineKeys.current = newLines
|
||||||
lastUsedLineId.current = newLastUsedLineId
|
lastUsedLineId.current = newLastUsedLineId
|
||||||
const transformer = componentReplacers ? buildTransformer(newLines, componentReplacers) : undefined
|
const transformer = componentReplacers ? buildTransformer(newLines, componentReplacers()) : undefined
|
||||||
return ReactHtmlParser(html, { transform: transformer })
|
return ReactHtmlParser(html, { transform: transformer })
|
||||||
}, [onBeforeRendering, content, maxLength, markdownIt, componentReplacers])
|
}, [onBeforeRendering, content, maxLength, markdownIt, componentReplacers])
|
||||||
|
|
||||||
|
|
|
@ -1,65 +1,19 @@
|
||||||
import markdownItTaskLists from '@hedgedoc/markdown-it-task-lists'
|
import React, { useCallback, useMemo, useRef, useState } from 'react'
|
||||||
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 { Alert } from 'react-bootstrap'
|
import { Alert } from 'react-bootstrap'
|
||||||
import { Trans } from 'react-i18next'
|
import { Trans } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { TocAst } from '../../external-types/markdown-it-toc-done-right/interface'
|
import { TocAst } from '../../external-types/markdown-it-toc-done-right/interface'
|
||||||
import { ApplicationState } from '../../redux'
|
|
||||||
import { InternalLink } from '../common/links/internal-link'
|
import { InternalLink } from '../common/links/internal-link'
|
||||||
import { ShowIf } from '../common/show-if/show-if'
|
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 { RawYAMLMetadata, YAMLMetaData } from '../editor/yaml-metadata/yaml-metadata'
|
||||||
import { BasicMarkdownRenderer } from './basic-markdown-renderer'
|
import { BasicMarkdownRenderer } from './basic-markdown-renderer'
|
||||||
import { createRenderContainer, validAlertLevels } from './markdown-it-plugins/alert-container'
|
import { FullMarkdownItConfigurator } from './markdown-it-configurator/FullMarkdownItConfigurator'
|
||||||
import { highlightedCode } from './markdown-it-plugins/highlighted-code'
|
import { LineMarkers } from './replace-components/linemarker/line-number-marker'
|
||||||
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 { AdditionalMarkdownRendererProps, LineMarkerPosition } from './types'
|
import { AdditionalMarkdownRendererProps, LineMarkerPosition } from './types'
|
||||||
import { useCalculateLineMarkerPosition } from './utils/calculate-line-marker-positions'
|
import { useCalculateLineMarkerPosition } from './utils/calculate-line-marker-positions'
|
||||||
import { usePostMetaDataOnChange } from './utils/use-post-meta-data-on-change'
|
import { useReplacerInstanceListCreator } from './hooks/use-replacer-instance-list-creator'
|
||||||
import { usePostTocAstOnChange } from './utils/use-post-toc-ast-on-change'
|
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 {
|
export interface FullMarkdownRendererProps {
|
||||||
onFirstHeadingChange?: (firstHeading: string | undefined) => void
|
onFirstHeadingChange?: (firstHeading: string | undefined) => void
|
||||||
|
@ -79,150 +33,36 @@ export const FullMarkdownRenderer: React.FC<FullMarkdownRendererProps & Addition
|
||||||
className,
|
className,
|
||||||
wide
|
wide
|
||||||
}) => {
|
}) => {
|
||||||
const allReplacers = useMemo(() => {
|
const allReplacers = useReplacerInstanceListCreator(onTaskCheckedChange)
|
||||||
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 [yamlError, setYamlError] = useState(false)
|
const [yamlError, setYamlError] = useState(false)
|
||||||
|
|
||||||
const plantumlServer = useSelector((state: ApplicationState) => state.config.plantumlServer)
|
|
||||||
|
|
||||||
const rawMetaRef = useRef<RawYAMLMetadata>()
|
const rawMetaRef = useRef<RawYAMLMetadata>()
|
||||||
const firstHeadingRef = useRef<string>()
|
const firstHeadingRef = useRef<string>()
|
||||||
const documentElement = useRef<HTMLDivElement>(null)
|
const documentElement = useRef<HTMLDivElement>(null)
|
||||||
const currentLineMarkers = useRef<LineMarkers[]>()
|
const currentLineMarkers = useRef<LineMarkers[]>()
|
||||||
usePostMetaDataOnChange(rawMetaRef.current, firstHeadingRef.current, onMetaDataChange, onFirstHeadingChange)
|
usePostMetaDataOnChange(rawMetaRef.current, firstHeadingRef.current, onMetaDataChange, onFirstHeadingChange)
|
||||||
useCalculateLineMarkerPosition(documentElement, currentLineMarkers.current, onLineMarkerPositionChanged, documentElement.current?.offsetTop ?? 0)
|
useCalculateLineMarkerPosition(documentElement, currentLineMarkers.current, onLineMarkerPositionChanged, documentElement.current?.offsetTop ?? 0)
|
||||||
|
useExtractFirstHeadline(documentElement, content, onFirstHeadingChange)
|
||||||
|
|
||||||
const tocAst = useRef<TocAst>()
|
const tocAst = useRef<TocAst>()
|
||||||
usePostTocAstOnChange(tocAst, onTocChange)
|
usePostTocAstOnChange(tocAst, onTocChange)
|
||||||
|
|
||||||
const extractInnerText = useCallback((node: ChildNode) => {
|
const markdownIt = useMemo(() => {
|
||||||
let innerText = ''
|
return (new FullMarkdownItConfigurator(
|
||||||
if (node.childNodes && node.childNodes.length > 0) {
|
!!onMetaDataChange,
|
||||||
node.childNodes.forEach((child) => { innerText += extractInnerText(child) })
|
error => setYamlError(error),
|
||||||
} else if (node.nodeName === 'IMG') {
|
rawMeta => {
|
||||||
innerText += (node as HTMLImageElement).getAttribute('alt')
|
rawMetaRef.current = rawMeta
|
||||||
} 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
|
|
||||||
},
|
},
|
||||||
slugify: slugify
|
toc => {
|
||||||
})
|
tocAst.current = toc
|
||||||
validAlertLevels.forEach(level => {
|
},
|
||||||
md.use(markdownItContainer, level, { render: createRenderContainer(level) })
|
lineMarkers => {
|
||||||
})
|
|
||||||
md.use(lineNumberMarker(), {
|
|
||||||
postLineMarkers: (lineMarkers) => {
|
|
||||||
currentLineMarkers.current = lineMarkers
|
currentLineMarkers.current = lineMarkers
|
||||||
}
|
}
|
||||||
})
|
)).buildConfiguredMarkdownIt()
|
||||||
}, [onMetaDataChange, plantumlServer])
|
}, [onMetaDataChange])
|
||||||
|
|
||||||
const clearMetadata = useCallback(() => {
|
const clearMetadata = useCallback(() => {
|
||||||
rawMetaRef.current = undefined
|
rawMetaRef.current = undefined
|
||||||
|
@ -238,7 +78,7 @@ export const FullMarkdownRenderer: React.FC<FullMarkdownRendererProps & Addition
|
||||||
</Alert>
|
</Alert>
|
||||||
</ShowIf>
|
</ShowIf>
|
||||||
<BasicMarkdownRenderer className={className} wide={wide} content={content} componentReplacers={allReplacers}
|
<BasicMarkdownRenderer className={className} wide={wide} content={content} componentReplacers={allReplacers}
|
||||||
onConfigureMarkdownIt={configureMarkdownIt} documentReference={documentElement}
|
markdownIt={markdownIt} documentReference={documentElement}
|
||||||
onBeforeRendering={clearMetadata}/>
|
onBeforeRendering={clearMetadata}/>
|
||||||
</div>
|
</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 MarkdownIt from 'markdown-it'
|
||||||
|
import markdownItContainer from 'markdown-it-container'
|
||||||
import Renderer from 'markdown-it/lib/renderer'
|
import Renderer from 'markdown-it/lib/renderer'
|
||||||
import Token from 'markdown-it/lib/token'
|
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 RenderContainerReturn = (tokens: Token[], index: number, options: MarkdownIt.Options, env: unknown, self: Renderer) => void;
|
||||||
type ValidAlertLevels = ('warning' | 'danger' | 'success' | 'info')
|
type ValidAlertLevels = ('warning' | 'danger' | 'success' | 'info')
|
||||||
export const validAlertLevels: ValidAlertLevels[] = ['success', 'danger', 'info', 'warning']
|
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) => {
|
return (tokens: Token[], index: number, options: MarkdownIt.Options, env: unknown, self: Renderer) => {
|
||||||
tokens[index].attrJoin('role', 'alert')
|
tokens[index].attrJoin('role', 'alert')
|
||||||
tokens[index].attrJoin('class', 'alert')
|
tokens[index].attrJoin('class', 'alert')
|
||||||
|
@ -14,3 +16,9 @@ export const createRenderContainer = (level: ValidAlertLevels): RenderContainerR
|
||||||
return self.renderToken(tokens, index, options)
|
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'
|
import MarkdownIt from 'markdown-it/lib'
|
||||||
|
|
||||||
export const MarkdownItParserDebugger: MarkdownIt.PluginSimple = (md: MarkdownIt) => {
|
export const MarkdownItParserDebugger: MarkdownIt.PluginSimple = (md: MarkdownIt) => {
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
md.core.ruler.push('test', (state) => {
|
md.core.ruler.push('test', (state) => {
|
||||||
console.log(state)
|
console.log(state)
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,24 @@
|
||||||
|
import plantuml from 'markdown-it-plantuml'
|
||||||
import MarkdownIt, { Options } from 'markdown-it/lib'
|
import MarkdownIt, { Options } from 'markdown-it/lib'
|
||||||
import Renderer, { RenderRule } from 'markdown-it/lib/renderer'
|
import Renderer, { RenderRule } from 'markdown-it/lib/renderer'
|
||||||
import Token from 'markdown-it/lib/token'
|
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 || (() => '')
|
const defaultRenderer: RenderRule = md.renderer.rules.fence || (() => '')
|
||||||
md.renderer.rules.fence = (tokens: Token[], idx: number, options: Options, env, slf: Renderer) => {
|
md.renderer.rules.fence = (tokens: Token[], idx: number, options: Options, env, slf: Renderer) => {
|
||||||
const token = tokens[idx]
|
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'
|
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
|
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',
|
name: 'quote-extra-color',
|
||||||
regex: cssColorRegex,
|
regex: cssColorRegex,
|
||||||
replace: (match) => {
|
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>`
|
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'
|
import { RegexOptions } from '../../../external-types/markdown-it-regex/interface'
|
||||||
|
|
||||||
const finalRegex = /^{%slideshare (\w+\/[\w-]+) ?%}$/
|
const finalRegex = /^{%slideshare (\w+\/[\w-]+) ?%}$/
|
||||||
|
|
||||||
|
export const legacySlideshareShortCode: MarkdownIt.PluginSimple = (markdownIt) => {
|
||||||
|
markdownItRegex(markdownIt, replaceLegacySlideshareShortCode)
|
||||||
|
}
|
||||||
|
|
||||||
export const replaceLegacySlideshareShortCode: RegexOptions = {
|
export const replaceLegacySlideshareShortCode: RegexOptions = {
|
||||||
name: 'legacy-slideshare-short-code',
|
name: 'legacy-slideshare-short-code',
|
||||||
regex: finalRegex,
|
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'
|
import { RegexOptions } from '../../../external-types/markdown-it-regex/interface'
|
||||||
|
|
||||||
const finalRegex = /^{%speakerdeck (\w+\/[\w-]+) ?%}$/
|
const finalRegex = /^{%speakerdeck (\w+\/[\w-]+) ?%}$/
|
||||||
|
|
||||||
|
export const legacySpeakerdeckShortCode: MarkdownIt.PluginSimple = (markdownIt) => {
|
||||||
|
markdownItRegex(markdownIt, replaceLegacySpeakerdeckShortCode)
|
||||||
|
}
|
||||||
|
|
||||||
export const replaceLegacySpeakerdeckShortCode: RegexOptions = {
|
export const replaceLegacySpeakerdeckShortCode: RegexOptions = {
|
||||||
name: 'legacy-speakerdeck-short-code',
|
name: 'legacy-speakerdeck-short-code',
|
||||||
regex: finalRegex,
|
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 { DomElement } from 'domhandler'
|
||||||
|
import MarkdownIt from 'markdown-it'
|
||||||
import { ReactElement } from 'react'
|
import { ReactElement } from 'react'
|
||||||
|
|
||||||
export type SubNodeTransform = (node: DomElement, subIndex: number) => ReactElement | void | null
|
export type SubNodeTransform = (node: DomElement, subIndex: number) => ReactElement | void | null
|
||||||
|
|
||||||
export type NativeRenderer = (node: DomElement, key: number) => ReactElement
|
export type NativeRenderer = (node: DomElement, key: number) => ReactElement
|
||||||
|
|
||||||
|
export type MarkdownItPlugin = MarkdownIt.PluginSimple | MarkdownIt.PluginWithOptions | MarkdownIt.PluginWithParams
|
||||||
|
|
||||||
export abstract class ComponentReplacer {
|
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 { DomElement } from 'domhandler'
|
||||||
|
import MarkdownIt from 'markdown-it'
|
||||||
|
import markdownItRegex from 'markdown-it-regex'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { getAttributesFromHedgeDocTag } from '../utils'
|
|
||||||
import { ComponentReplacer } from '../ComponentReplacer'
|
import { ComponentReplacer } from '../ComponentReplacer'
|
||||||
|
import { getAttributesFromHedgeDocTag } from '../utils'
|
||||||
import { AsciinemaFrame } from './asciinema-frame'
|
import { AsciinemaFrame } from './asciinema-frame'
|
||||||
|
import { replaceAsciinemaLink } from './replace-asciinema-link'
|
||||||
|
|
||||||
export class AsciinemaReplacer extends ComponentReplacer {
|
export class AsciinemaReplacer extends ComponentReplacer {
|
||||||
private counterMap: Map<string, number> = new Map<string, number>()
|
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 protocolRegex = /(?:http(?:s)?:\/\/)?/
|
||||||
const domainRegex = /(?:asciinema\.org\/a\/)/
|
const domainRegex = /(?:asciinema\.org\/a\/)/
|
|
@ -1,5 +1,9 @@
|
||||||
import { DomElement } from 'domhandler'
|
import { DomElement } from 'domhandler'
|
||||||
|
import MarkdownIt from 'markdown-it'
|
||||||
|
import markdownItRegex from 'markdown-it-regex'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { replaceGistLink } from './replace-gist-link'
|
||||||
|
import { replaceLegacyGistShortCode } from './replace-legacy-gist-short-code'
|
||||||
import { getAttributesFromHedgeDocTag } from '../utils'
|
import { getAttributesFromHedgeDocTag } from '../utils'
|
||||||
import { ComponentReplacer } from '../ComponentReplacer'
|
import { ComponentReplacer } from '../ComponentReplacer'
|
||||||
import { OneClickEmbedding } from '../one-click-frame/one-click-embedding'
|
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 protocolRegex = /(?:http(?:s)?:\/\/)?/
|
||||||
const domainRegex = /(?:gist\.github\.com\/)/
|
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+) ?%}$/
|
const finalRegex = /^{%gist (\w+\/\w+) ?%}$/
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import { DomElement } from 'domhandler'
|
import { DomElement } from 'domhandler'
|
||||||
|
import MarkdownIt from 'markdown-it'
|
||||||
|
import mathJax from 'markdown-it-mathjax'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { ComponentReplacer } from '../ComponentReplacer'
|
import { ComponentReplacer } from '../ComponentReplacer'
|
||||||
import './katex.scss'
|
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) {
|
if (node.name !== 'p' || !node.children || node.children.length === 0) {
|
||||||
return
|
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
|
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'}/>
|
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 { DomElement } from 'domhandler'
|
||||||
|
import MarkdownIt from 'markdown-it'
|
||||||
|
import markdownItRegex from 'markdown-it-regex'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { replacePdfShortCode } from './replace-pdf-short-code'
|
||||||
import { getAttributesFromHedgeDocTag } from '../utils'
|
import { getAttributesFromHedgeDocTag } from '../utils'
|
||||||
import { ComponentReplacer } from '../ComponentReplacer'
|
import { ComponentReplacer } from '../ComponentReplacer'
|
||||||
import { PdfFrame } from './pdf-frame'
|
import { PdfFrame } from './pdf-frame'
|
||||||
|
@ -16,4 +19,8 @@ export class PdfReplacer extends ComponentReplacer {
|
||||||
return <PdfFrame url={pdfUrl}/>
|
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 = {
|
export const replacePdfShortCode: RegexOptions = {
|
||||||
name: 'pdf-short-code',
|
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 = {
|
export const replaceLegacyVimeoShortCode: RegexOptions = {
|
||||||
name: 'legacy-vimeo-short-code',
|
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 protocolRegex = /(?:http(?:s)?:\/\/)?/
|
||||||
const domainRegex = /(?:player\.)?(?:vimeo\.com\/)(?:(?:channels|album|ondemand|groups)\/\w+\/)?(?:video\/)?/
|
const domainRegex = /(?:player\.)?(?:vimeo\.com\/)(?:(?:channels|album|ondemand|groups)\/\w+\/)?(?:video\/)?/
|
|
@ -1,7 +1,11 @@
|
||||||
import { DomElement } from 'domhandler'
|
import { DomElement } from 'domhandler'
|
||||||
|
import MarkdownIt from 'markdown-it'
|
||||||
|
import markdownItRegex from 'markdown-it-regex'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { getAttributesFromHedgeDocTag } from '../utils'
|
|
||||||
import { ComponentReplacer } from '../ComponentReplacer'
|
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'
|
import { VimeoFrame } from './vimeo-frame'
|
||||||
|
|
||||||
export class VimeoReplacer extends ComponentReplacer {
|
export class VimeoReplacer extends ComponentReplacer {
|
||||||
|
@ -16,4 +20,9 @@ export class VimeoReplacer extends ComponentReplacer {
|
||||||
return <VimeoFrame id={videoId}/>
|
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 = {
|
export const replaceLegacyYoutubeShortCode: RegexOptions = {
|
||||||
name: 'legacy-youtube-short-code',
|
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 protocolRegex = /(?:http(?:s)?:\/\/)?/
|
||||||
const subdomainRegex = /(?:www.)?/
|
const subdomainRegex = /(?:www.)?/
|
|
@ -1,7 +1,11 @@
|
||||||
import { DomElement } from 'domhandler'
|
import { DomElement } from 'domhandler'
|
||||||
|
import MarkdownIt from 'markdown-it'
|
||||||
|
import markdownItRegex from 'markdown-it-regex'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { getAttributesFromHedgeDocTag } from '../utils'
|
|
||||||
import { ComponentReplacer } from '../ComponentReplacer'
|
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'
|
import { YouTubeFrame } from './youtube-frame'
|
||||||
|
|
||||||
export class YoutubeReplacer extends ComponentReplacer {
|
export class YoutubeReplacer extends ComponentReplacer {
|
||||||
|
@ -16,4 +20,9 @@ export class YoutubeReplacer extends ComponentReplacer {
|
||||||
return <YouTubeFrame id={videoId}/>
|
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 { RefObject, useCallback, useEffect, useRef } from 'react'
|
||||||
import useResizeObserver from 'use-resize-observer'
|
import useResizeObserver from 'use-resize-observer'
|
||||||
import { LineMarkerPosition } from '../types'
|
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[] => {
|
export const calculateLineMarkerPositions = (documentElement: HTMLDivElement, currentLineMarkers: LineMarkers[], offset?: number): LineMarkerPosition[] => {
|
||||||
const lineMarkers = currentLineMarkers
|
const lineMarkers = currentLineMarkers
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
|
||||||
declare module 'markdown-it-front-matter' {
|
declare module 'markdown-it-front-matter' {
|
||||||
import MarkdownIt from 'markdown-it/lib'
|
import MarkdownIt from 'markdown-it/lib'
|
||||||
const markdownItFrontMatter: MarkdownIt.PluginSimple
|
export type FrontMatterPluginOptions = (rawMeta: string) => void
|
||||||
|
const markdownItFrontMatter: MarkdownIt.PluginWithOptions<FrontMatterPluginOptions>
|
||||||
export = markdownItFrontMatter
|
export = markdownItFrontMatter
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue