mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-17 16:44:49 -04:00
Restructure replacers (#266)
* Restructure replacers Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de>
This commit is contained in:
parent
eb56da7871
commit
b74bb8e71d
16 changed files with 250 additions and 219 deletions
|
@ -34,36 +34,21 @@ import { replaceQuoteExtraColor } from './regex-plugins/replace-quote-extra-colo
|
||||||
import { replaceQuoteExtraTime } from './regex-plugins/replace-quote-extra-time'
|
import { replaceQuoteExtraTime } from './regex-plugins/replace-quote-extra-time'
|
||||||
import { replaceVimeoLink } from './regex-plugins/replace-vimeo-link'
|
import { replaceVimeoLink } from './regex-plugins/replace-vimeo-link'
|
||||||
import { replaceYouTubeLink } from './regex-plugins/replace-youtube-link'
|
import { replaceYouTubeLink } from './regex-plugins/replace-youtube-link'
|
||||||
import { getGistReplacement } from './replace-components/gist/gist-frame'
|
import { ComponentReplacer, SubNodeConverter } from './replace-components/ComponentReplacer'
|
||||||
import { getHighlightedFence } from './replace-components/highlighted-fence/highlighted-fence'
|
import { GistReplacer } from './replace-components/gist/gist-replacer'
|
||||||
import { getMathJaxReplacement } from './replace-components/mathjax/mathjax-replacer'
|
import { HighlightedCodeReplacer } from './replace-components/highlighted-fence/highlighted-fence-replacer'
|
||||||
import { getPDFReplacement } from './replace-components/pdf/pdf-frame'
|
import { MathjaxReplacer } from './replace-components/mathjax/mathjax-replacer'
|
||||||
import { getQuoteOptionsReplacement } from './replace-components/quote-options/quote-options'
|
import { PdfReplacer } from './replace-components/pdf/pdf-replacer'
|
||||||
import { getTOCReplacement } from './replace-components/toc/toc-replacer'
|
import { QuoteOptionsReplacer } from './replace-components/quote-options/quote-options-replacer'
|
||||||
import { getVimeoReplacement } from './replace-components/vimeo/vimeo-frame'
|
import { TocReplacer } from './replace-components/toc/toc-replacer'
|
||||||
import { getYouTubeReplacement } from './replace-components/youtube/youtube-frame'
|
import { VimeoReplacer } from './replace-components/vimeo/vimeo-replacer'
|
||||||
|
import { YoutubeReplacer } from './replace-components/youtube/youtube-replacer'
|
||||||
|
|
||||||
export interface MarkdownPreviewProps {
|
export interface MarkdownPreviewProps {
|
||||||
content: string
|
content: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SubNodeConverter = (node: DomElement, index: number) => ReactElement
|
const createMarkdownIt = ():MarkdownIt => {
|
||||||
export type ComponentReplacer = (node: DomElement, index: number, counterMap: Map<string, number>, nodeConverter: SubNodeConverter) => (ReactElement | undefined);
|
|
||||||
type ComponentReplacer2Identifier2CounterMap = Map<ComponentReplacer, Map<string, number>>
|
|
||||||
|
|
||||||
const allComponentReplacers: ComponentReplacer[] = [getYouTubeReplacement, getVimeoReplacement, getGistReplacement, getPDFReplacement, getTOCReplacement, getHighlightedFence, getQuoteOptionsReplacement, getMathJaxReplacement]
|
|
||||||
|
|
||||||
const tryToReplaceNode = (node: DomElement, index:number, componentReplacer2Identifier2CounterMap: ComponentReplacer2Identifier2CounterMap, nodeConverter: SubNodeConverter) => {
|
|
||||||
return allComponentReplacers
|
|
||||||
.map((componentReplacer) => {
|
|
||||||
const identifier2CounterMap = componentReplacer2Identifier2CounterMap.get(componentReplacer) || new Map<string, number>()
|
|
||||||
return componentReplacer(node, index, identifier2CounterMap, nodeConverter)
|
|
||||||
})
|
|
||||||
.find((replacement) => !!replacement)
|
|
||||||
}
|
|
||||||
|
|
||||||
const MarkdownRenderer: React.FC<MarkdownPreviewProps> = ({ content }) => {
|
|
||||||
const markdownIt = useMemo(() => {
|
|
||||||
const md = new MarkdownIt('default', {
|
const md = new MarkdownIt('default', {
|
||||||
html: true,
|
html: true,
|
||||||
breaks: true,
|
breaks: true,
|
||||||
|
@ -119,15 +104,32 @@ const MarkdownRenderer: React.FC<MarkdownPreviewProps> = ({ content }) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
return md
|
return md
|
||||||
}, [])
|
}
|
||||||
|
|
||||||
|
const tryToReplaceNode = (node: DomElement, index: number, allReplacers: ComponentReplacer[], nodeConverter: SubNodeConverter) => {
|
||||||
|
return allReplacers
|
||||||
|
.map((componentReplacer) => componentReplacer.getReplacement(node, index, nodeConverter))
|
||||||
|
.find((replacement) => !!replacement)
|
||||||
|
}
|
||||||
|
|
||||||
|
const MarkdownRenderer: React.FC<MarkdownPreviewProps> = ({ content }) => {
|
||||||
|
const markdownIt = useMemo(createMarkdownIt, [])
|
||||||
|
|
||||||
const result: ReactElement[] = useMemo(() => {
|
const result: ReactElement[] = useMemo(() => {
|
||||||
const componentReplacer2Identifier2CounterMap = new Map<ComponentReplacer, Map<string, number>>()
|
const allReplacers: ComponentReplacer[] = [
|
||||||
|
new GistReplacer(),
|
||||||
|
new YoutubeReplacer(),
|
||||||
|
new VimeoReplacer(),
|
||||||
|
new PdfReplacer(),
|
||||||
|
new TocReplacer(),
|
||||||
|
new HighlightedCodeReplacer(),
|
||||||
|
new QuoteOptionsReplacer(),
|
||||||
|
new MathjaxReplacer()
|
||||||
|
]
|
||||||
const html: string = markdownIt.render(content)
|
const html: string = markdownIt.render(content)
|
||||||
const transform: Transform = (node, index) => {
|
const transform: Transform = (node, index) => {
|
||||||
const maybeReplacement = tryToReplaceNode(node, index, componentReplacer2Identifier2CounterMap,
|
const subNodeConverter = (subNode: DomElement, subIndex: number) => convertNodeToElement(subNode, subIndex, transform)
|
||||||
(subNode, subIndex) => convertNodeToElement(subNode, subIndex, transform))
|
return tryToReplaceNode(node, index, allReplacers, subNodeConverter) || convertNodeToElement(node, index, transform)
|
||||||
return maybeReplacement || convertNodeToElement(node, index, transform)
|
|
||||||
}
|
}
|
||||||
return ReactHtmlParser(html, { transform: transform })
|
return ReactHtmlParser(html, { transform: transform })
|
||||||
}, [content, markdownIt])
|
}, [content, markdownIt])
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { DomElement } from 'domhandler'
|
||||||
|
import { ReactElement } from 'react'
|
||||||
|
|
||||||
|
export type SubNodeConverter = (node: DomElement, index: number) => ReactElement
|
||||||
|
|
||||||
|
export interface ComponentReplacer {
|
||||||
|
getReplacement: (node: DomElement, index:number, subNodeConverter: SubNodeConverter) => (ReactElement|undefined)
|
||||||
|
}
|
|
@ -1,9 +1,5 @@
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { ComponentReplacer } from '../../markdown-renderer'
|
|
||||||
import { getAttributesFromCodiMdTag } from '../codi-md-tag-utils'
|
|
||||||
import { OneClickEmbedding } from '../one-click-frame/one-click-embedding'
|
|
||||||
import './gist-frame.scss'
|
import './gist-frame.scss'
|
||||||
import preview from './gist-preview.png'
|
|
||||||
|
|
||||||
export interface GistFrameProps {
|
export interface GistFrameProps {
|
||||||
id: string
|
id: string
|
||||||
|
@ -14,20 +10,6 @@ interface resizeEvent {
|
||||||
id: string
|
id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const getElementReplacement:ComponentReplacer = (node, index:number, counterMap) => {
|
|
||||||
const attributes = getAttributesFromCodiMdTag(node, 'gist')
|
|
||||||
if (attributes && attributes.id) {
|
|
||||||
const gistId = attributes.id
|
|
||||||
const count = (counterMap.get(gistId) || 0) + 1
|
|
||||||
counterMap.set(gistId, count)
|
|
||||||
return (
|
|
||||||
<OneClickEmbedding previewContainerClassName={'gist-frame'} key={`gist_${gistId}_${count}`} loadingImageUrl={preview} hoverIcon={'github'} tooltip={'click to load gist'}>
|
|
||||||
<GistFrame id={gistId}/>
|
|
||||||
</OneClickEmbedding>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GistFrame: React.FC<GistFrameProps> = ({ id }) => {
|
export const GistFrame: React.FC<GistFrameProps> = ({ id }) => {
|
||||||
const iframeHtml = useMemo(() => {
|
const iframeHtml = useMemo(() => {
|
||||||
return (`
|
return (`
|
||||||
|
@ -85,5 +67,3 @@ export const GistFrame: React.FC<GistFrameProps> = ({ id }) => {
|
||||||
src={`data:text/html;base64,${btoa(iframeHtml)}`}/>
|
src={`data:text/html;base64,${btoa(iframeHtml)}`}/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { getElementReplacement as getGistReplacement }
|
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { DomElement } from 'domhandler'
|
||||||
|
import React from 'react'
|
||||||
|
import { getAttributesFromCodiMdTag } from '../codi-md-tag-utils'
|
||||||
|
import { ComponentReplacer } from '../ComponentReplacer'
|
||||||
|
import { OneClickEmbedding } from '../one-click-frame/one-click-embedding'
|
||||||
|
import { GistFrame } from './gist-frame'
|
||||||
|
import preview from './gist-preview.png'
|
||||||
|
|
||||||
|
export class GistReplacer implements ComponentReplacer {
|
||||||
|
private counterMap: Map<string, number> = new Map<string, number>()
|
||||||
|
|
||||||
|
getReplacement (node: DomElement): React.ReactElement | undefined {
|
||||||
|
const attributes = getAttributesFromCodiMdTag(node, 'gist')
|
||||||
|
if (attributes && attributes.id) {
|
||||||
|
const gistId = attributes.id
|
||||||
|
const count = (this.counterMap.get(gistId) || 0) + 1
|
||||||
|
this.counterMap.set(gistId, count)
|
||||||
|
return (
|
||||||
|
<OneClickEmbedding previewContainerClassName={'gist-frame'} key={`gist_${gistId}_${count}`} loadingImageUrl={preview} hoverIcon={'github'} tooltip={'click to load gist'}>
|
||||||
|
<GistFrame id={gistId}/>
|
||||||
|
</OneClickEmbedding>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { DomElement } from 'domhandler'
|
||||||
|
import React from 'react'
|
||||||
|
import { HighlightedCode } from '../../../../common/highlighted-code/highlighted-code'
|
||||||
|
import { ComponentReplacer, SubNodeConverter } from '../ComponentReplacer'
|
||||||
|
|
||||||
|
export class HighlightedCodeReplacer implements ComponentReplacer {
|
||||||
|
getReplacement (codeNode: DomElement, index: number, subNodeConverter: SubNodeConverter): React.ReactElement | undefined {
|
||||||
|
if (codeNode.name !== 'code' || !codeNode.attribs || !codeNode.attribs['data-highlight-language'] || !codeNode.children || !codeNode.children[0]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const language = codeNode.attribs['data-highlight-language']
|
||||||
|
const showGutter = codeNode.attribs['data-show-gutter'] !== undefined
|
||||||
|
const wrapLines = codeNode.attribs['data-wrap-lines'] !== undefined
|
||||||
|
|
||||||
|
return <HighlightedCode key={index} language={language} showGutter={showGutter} wrapLines={wrapLines} code={codeNode.children[0].data as string}/>
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,16 +0,0 @@
|
||||||
import { DomElement } from 'domhandler'
|
|
||||||
import React, { ReactElement } from 'react'
|
|
||||||
import { HighlightedCode } from '../../../../common/highlighted-code/highlighted-code'
|
|
||||||
|
|
||||||
const getElementReplacement = (codeNode: DomElement, index: number, counterMap: Map<string, number>): (ReactElement | undefined) => {
|
|
||||||
if (codeNode.name !== 'code' || !codeNode.attribs || !codeNode.attribs['data-highlight-language'] || !codeNode.children || !codeNode.children[0]) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const language = codeNode.attribs['data-highlight-language']
|
|
||||||
const showGutter = codeNode.attribs['data-show-gutter'] !== undefined
|
|
||||||
const wrapLines = codeNode.attribs['data-wrap-lines'] !== undefined
|
|
||||||
return <HighlightedCode key={index} language={language} showGutter={showGutter} wrapLines={wrapLines} code={codeNode.children[0].data as string}/>
|
|
||||||
}
|
|
||||||
|
|
||||||
export { getElementReplacement as getHighlightedFence }
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react'
|
|
||||||
import { DomElement } from 'domhandler'
|
import { DomElement } from 'domhandler'
|
||||||
import { ComponentReplacer } from '../../markdown-renderer'
|
import React from 'react'
|
||||||
import MathJax from 'react-mathjax'
|
import MathJax from 'react-mathjax'
|
||||||
|
import { ComponentReplacer, SubNodeConverter } from '../ComponentReplacer'
|
||||||
|
|
||||||
const getNodeIfMathJaxBlock = (node: DomElement): (DomElement|undefined) => {
|
const getNodeIfMathJaxBlock = (node: DomElement): (DomElement|undefined) => {
|
||||||
if (node.name !== 'p' || !node.children || node.children.length !== 1) {
|
if (node.name !== 'p' || !node.children || node.children.length !== 1) {
|
||||||
|
@ -15,13 +15,13 @@ const getNodeIfInlineMathJax = (node: DomElement): (DomElement|undefined) => {
|
||||||
return (node.name === 'codimd-mathjax' && node.attribs?.inline !== undefined) ? node : undefined
|
return (node.name === 'codimd-mathjax' && node.attribs?.inline !== undefined) ? node : undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const getElementReplacement: ComponentReplacer = (node, index: number, counterMap) => {
|
export class MathjaxReplacer implements ComponentReplacer {
|
||||||
|
getReplacement (node: DomElement, index: number, subNodeConverter: SubNodeConverter): React.ReactElement | undefined {
|
||||||
const mathJax = getNodeIfMathJaxBlock(node) || getNodeIfInlineMathJax(node)
|
const mathJax = getNodeIfMathJaxBlock(node) || getNodeIfInlineMathJax(node)
|
||||||
if (mathJax?.children && mathJax.children[0]) {
|
if (mathJax?.children && mathJax.children[0]) {
|
||||||
const mathJaxContent = mathJax.children[0]?.data as string
|
const mathJaxContent = mathJax.children[0]?.data as string
|
||||||
const isInline = (mathJax.attribs?.inline) !== undefined
|
const isInline = (mathJax.attribs?.inline) !== undefined
|
||||||
return <MathJax.Node key={index} inline={isInline} formula={mathJaxContent}/>
|
return <MathJax.Node key={index} inline={isInline} formula={mathJaxContent}/>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { getElementReplacement as getMathJaxReplacement }
|
|
||||||
|
|
|
@ -1,20 +1,8 @@
|
||||||
import { DomElement } from 'domhandler'
|
import React from 'react'
|
||||||
import React, { ReactElement } from 'react'
|
|
||||||
import { ExternalLink } from '../../../../common/links/external-link'
|
import { ExternalLink } from '../../../../common/links/external-link'
|
||||||
import { getAttributesFromCodiMdTag } from '../codi-md-tag-utils'
|
|
||||||
import { OneClickEmbedding } from '../one-click-frame/one-click-embedding'
|
import { OneClickEmbedding } from '../one-click-frame/one-click-embedding'
|
||||||
import './pdf-frame.scss'
|
import './pdf-frame.scss'
|
||||||
|
|
||||||
const getElementReplacement = (node: DomElement, index:number, counterMap: Map<string, number>): (ReactElement | undefined) => {
|
|
||||||
const attributes = getAttributesFromCodiMdTag(node, 'pdf')
|
|
||||||
if (attributes && attributes.url) {
|
|
||||||
const pdfUrl = attributes.url
|
|
||||||
const count = (counterMap.get(pdfUrl) || 0) + 1
|
|
||||||
counterMap.set(pdfUrl, count)
|
|
||||||
return <PdfFrame key={`pdf_${pdfUrl}_${count}`} url={pdfUrl}/>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PdfFrameProps {
|
export interface PdfFrameProps {
|
||||||
url: string
|
url: string
|
||||||
}
|
}
|
||||||
|
@ -30,5 +18,3 @@ export const PdfFrame: React.FC<PdfFrameProps> = ({ url }) => {
|
||||||
</OneClickEmbedding>
|
</OneClickEmbedding>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { getElementReplacement as getPDFReplacement }
|
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { DomElement } from 'domhandler'
|
||||||
|
import React from 'react'
|
||||||
|
import { getAttributesFromCodiMdTag } from '../codi-md-tag-utils'
|
||||||
|
import { ComponentReplacer, SubNodeConverter } from '../ComponentReplacer'
|
||||||
|
import { PdfFrame } from './pdf-frame'
|
||||||
|
|
||||||
|
export class PdfReplacer implements ComponentReplacer {
|
||||||
|
private counterMap: Map<string, number> = new Map<string, number>()
|
||||||
|
|
||||||
|
getReplacement (node: DomElement, index: number, subNodeConverter: SubNodeConverter): React.ReactElement | undefined {
|
||||||
|
const attributes = getAttributesFromCodiMdTag(node, 'pdf')
|
||||||
|
if (attributes && attributes.url) {
|
||||||
|
const pdfUrl = attributes.url
|
||||||
|
const count = (this.counterMap.get(pdfUrl) || 0) + 1
|
||||||
|
this.counterMap.set(pdfUrl, count)
|
||||||
|
return <PdfFrame key={`pdf_${pdfUrl}_${count}`} url={pdfUrl}/>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { DomElement } from 'domhandler'
|
||||||
|
import { ComponentReplacer, SubNodeConverter } from '../ComponentReplacer'
|
||||||
|
|
||||||
|
const isColorExtraElement = (node: DomElement | undefined): boolean => {
|
||||||
|
if (!node || !node.attribs || !node.attribs.class || !node.attribs['data-color']) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return (node.name === 'span' && node.attribs.class === 'quote-extra')
|
||||||
|
}
|
||||||
|
|
||||||
|
const findQuoteOptionsParent = (nodes: DomElement[]): DomElement | undefined => {
|
||||||
|
return nodes.find((child) => {
|
||||||
|
if (child.name !== 'p' || !child.children || child.children.length < 1) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return child.children.find(isColorExtraElement) !== undefined
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export class QuoteOptionsReplacer implements ComponentReplacer {
|
||||||
|
getReplacement (node: DomElement, index: number, subNodeConverter: SubNodeConverter): React.ReactElement | undefined {
|
||||||
|
if (node.name !== 'blockquote' || !node.children || node.children.length < 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const paragraph = findQuoteOptionsParent(node.children)
|
||||||
|
if (!paragraph) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const childElements = paragraph.children || []
|
||||||
|
const optionsTag = childElements.find(isColorExtraElement)
|
||||||
|
if (!optionsTag) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
paragraph.children = childElements.filter(elem => !isColorExtraElement(elem))
|
||||||
|
const attributes = optionsTag.attribs
|
||||||
|
if (!attributes || !attributes['data-color']) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
node.attribs = Object.assign(node.attribs || {}, { style: `border-left-color: ${attributes['data-color']};` })
|
||||||
|
return subNodeConverter(node, index)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,43 +0,0 @@
|
||||||
import { DomElement } from 'domhandler'
|
|
||||||
import { ReactElement } from 'react'
|
|
||||||
import { SubNodeConverter } from '../../markdown-renderer'
|
|
||||||
|
|
||||||
const isColorExtraElement = (node: DomElement | undefined): boolean => {
|
|
||||||
if (!node || !node.attribs || !node.attribs.class || !node.attribs['data-color']) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return (node.name === 'span' && node.attribs.class === 'quote-extra')
|
|
||||||
}
|
|
||||||
|
|
||||||
const findQuoteOptionsParent = (nodes: DomElement[]): DomElement | undefined => {
|
|
||||||
return nodes.find((child) => {
|
|
||||||
if (child.name !== 'p' || !child.children || child.children.length < 1) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return child.children.find(isColorExtraElement) !== undefined
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const getElementReplacement = (node: DomElement, index: number, counterMap: Map<string, number>, nodeConverter: SubNodeConverter): (ReactElement | undefined) => {
|
|
||||||
if (node.name !== 'blockquote' || !node.children || node.children.length < 1) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const paragraph = findQuoteOptionsParent(node.children)
|
|
||||||
if (!paragraph) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const childElements = paragraph.children || []
|
|
||||||
const optionsTag = childElements.find(isColorExtraElement)
|
|
||||||
if (!optionsTag) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
paragraph.children = childElements.filter(elem => !isColorExtraElement(elem))
|
|
||||||
const attributes = optionsTag.attribs
|
|
||||||
if (!attributes || !attributes['data-color']) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
node.attribs = Object.assign(node.attribs || {}, { style: `border-left-color: ${attributes['data-color']};` })
|
|
||||||
return nodeConverter(node, index)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { getElementReplacement as getQuoteOptionsReplacement }
|
|
|
@ -1,17 +1,17 @@
|
||||||
import { DomElement } from 'domhandler'
|
import { DomElement } from 'domhandler'
|
||||||
import { ReactElement } from 'react'
|
import { ComponentReplacer, SubNodeConverter } from '../ComponentReplacer'
|
||||||
import { SubNodeConverter } from '../../markdown-renderer'
|
|
||||||
|
|
||||||
const getElementReplacement = (node: DomElement, index: number, counterMap: Map<string, number>, nodeConverter: SubNodeConverter): (ReactElement | undefined) => {
|
export class TocReplacer implements ComponentReplacer {
|
||||||
if (node.name === 'p' && node.children && node.children.length === 1) {
|
getReplacement (node: DomElement, index: number, subNodeConverter: SubNodeConverter): React.ReactElement | undefined {
|
||||||
|
if (node.name !== 'p' || node.children?.length !== 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const possibleTocDiv = node.children[0]
|
const possibleTocDiv = node.children[0]
|
||||||
if (possibleTocDiv.name === 'div' && possibleTocDiv.attribs && possibleTocDiv.attribs.class &&
|
if (possibleTocDiv.name === 'div' && possibleTocDiv.attribs && possibleTocDiv.attribs.class &&
|
||||||
possibleTocDiv.attribs.class === 'table-of-contents' && possibleTocDiv.children && possibleTocDiv.children.length === 1) {
|
possibleTocDiv.attribs.class === 'table-of-contents' && possibleTocDiv.children && possibleTocDiv.children.length === 1) {
|
||||||
const listElement = possibleTocDiv.children[0]
|
const listElement = possibleTocDiv.children[0]
|
||||||
listElement.attribs = Object.assign(listElement.attribs || {}, { class: 'table-of-contents' })
|
listElement.attribs = Object.assign(listElement.attribs || {}, { class: 'table-of-contents' })
|
||||||
return nodeConverter(listElement, index)
|
return subNodeConverter(listElement, index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { getElementReplacement as getTOCReplacement }
|
|
||||||
|
|
|
@ -1,18 +1,6 @@
|
||||||
import React, { useCallback } from 'react'
|
import React, { useCallback } from 'react'
|
||||||
import { ComponentReplacer } from '../../markdown-renderer'
|
|
||||||
import { getAttributesFromCodiMdTag } from '../codi-md-tag-utils'
|
|
||||||
import { OneClickEmbedding } from '../one-click-frame/one-click-embedding'
|
import { OneClickEmbedding } from '../one-click-frame/one-click-embedding'
|
||||||
|
|
||||||
const getElementReplacement:ComponentReplacer = (node, index:number, counterMap) => {
|
|
||||||
const attributes = getAttributesFromCodiMdTag(node, 'vimeo')
|
|
||||||
if (attributes && attributes.id) {
|
|
||||||
const videoId = attributes.id
|
|
||||||
const count = (counterMap.get(videoId) || 0) + 1
|
|
||||||
counterMap.set(videoId, count)
|
|
||||||
return <VimeoFrame key={`vimeo_${videoId}_${count}`} id={videoId}/>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface VimeoApiResponse {
|
interface VimeoApiResponse {
|
||||||
// Vimeo uses strange names for their fields. ESLint doesn't like that.
|
// Vimeo uses strange names for their fields. ESLint doesn't like that.
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
|
@ -50,5 +38,3 @@ export const VimeoFrame: React.FC<VimeoFrameProps> = ({ id }) => {
|
||||||
</OneClickEmbedding>
|
</OneClickEmbedding>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { getElementReplacement as getVimeoReplacement }
|
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { DomElement } from 'domhandler'
|
||||||
|
import React from 'react'
|
||||||
|
import { getAttributesFromCodiMdTag } from '../codi-md-tag-utils'
|
||||||
|
import { ComponentReplacer } from '../ComponentReplacer'
|
||||||
|
import { VimeoFrame } from './vimeo-frame'
|
||||||
|
|
||||||
|
export class VimeoReplacer implements ComponentReplacer {
|
||||||
|
private counterMap: Map<string, number> = new Map<string, number>()
|
||||||
|
|
||||||
|
getReplacement (node: DomElement): React.ReactElement | undefined {
|
||||||
|
const attributes = getAttributesFromCodiMdTag(node, 'vimeo')
|
||||||
|
if (attributes && attributes.id) {
|
||||||
|
const videoId = attributes.id
|
||||||
|
const count = (this.counterMap.get(videoId) || 0) + 1
|
||||||
|
this.counterMap.set(videoId, count)
|
||||||
|
return <VimeoFrame key={`vimeo_${videoId}_${count}`} id={videoId}/>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,18 +1,6 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { ComponentReplacer } from '../../markdown-renderer'
|
|
||||||
import { getAttributesFromCodiMdTag } from '../codi-md-tag-utils'
|
|
||||||
import { OneClickEmbedding } from '../one-click-frame/one-click-embedding'
|
import { OneClickEmbedding } from '../one-click-frame/one-click-embedding'
|
||||||
|
|
||||||
const getElementReplacement: ComponentReplacer = (node, index:number, counterMap) => {
|
|
||||||
const attributes = getAttributesFromCodiMdTag(node, 'youtube')
|
|
||||||
if (attributes && attributes.id) {
|
|
||||||
const videoId = attributes.id
|
|
||||||
const count = (counterMap.get(videoId) || 0) + 1
|
|
||||||
counterMap.set(videoId, count)
|
|
||||||
return <YouTubeFrame key={`youtube_${videoId}_${count}`} id={videoId}/>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface YouTubeFrameProps {
|
export interface YouTubeFrameProps {
|
||||||
id: string
|
id: string
|
||||||
}
|
}
|
||||||
|
@ -28,5 +16,3 @@ export const YouTubeFrame: React.FC<YouTubeFrameProps> = ({ id }) => {
|
||||||
</OneClickEmbedding>
|
</OneClickEmbedding>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { getElementReplacement as getYouTubeReplacement }
|
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { DomElement } from 'domhandler'
|
||||||
|
import React from 'react'
|
||||||
|
import { getAttributesFromCodiMdTag } from '../codi-md-tag-utils'
|
||||||
|
import { ComponentReplacer } from '../ComponentReplacer'
|
||||||
|
import { YouTubeFrame } from './youtube-frame'
|
||||||
|
|
||||||
|
export class YoutubeReplacer implements ComponentReplacer {
|
||||||
|
private counterMap: Map<string, number> = new Map<string, number>()
|
||||||
|
|
||||||
|
getReplacement (node: DomElement): React.ReactElement | undefined {
|
||||||
|
const attributes = getAttributesFromCodiMdTag(node, 'youtube')
|
||||||
|
if (attributes && attributes.id) {
|
||||||
|
const videoId = attributes.id
|
||||||
|
const count = (this.counterMap.get(videoId) || 0) + 1
|
||||||
|
this.counterMap.set(videoId, count)
|
||||||
|
return <YouTubeFrame key={`youtube_${videoId}_${count}`} id={videoId}/>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue