mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-25 11:16:31 -05: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 { replaceVimeoLink } from './regex-plugins/replace-vimeo-link'
|
||||
import { replaceYouTubeLink } from './regex-plugins/replace-youtube-link'
|
||||
import { getGistReplacement } from './replace-components/gist/gist-frame'
|
||||
import { getHighlightedFence } from './replace-components/highlighted-fence/highlighted-fence'
|
||||
import { getMathJaxReplacement } from './replace-components/mathjax/mathjax-replacer'
|
||||
import { getPDFReplacement } from './replace-components/pdf/pdf-frame'
|
||||
import { getQuoteOptionsReplacement } from './replace-components/quote-options/quote-options'
|
||||
import { getTOCReplacement } from './replace-components/toc/toc-replacer'
|
||||
import { getVimeoReplacement } from './replace-components/vimeo/vimeo-frame'
|
||||
import { getYouTubeReplacement } from './replace-components/youtube/youtube-frame'
|
||||
import { ComponentReplacer, SubNodeConverter } from './replace-components/ComponentReplacer'
|
||||
import { GistReplacer } from './replace-components/gist/gist-replacer'
|
||||
import { HighlightedCodeReplacer } from './replace-components/highlighted-fence/highlighted-fence-replacer'
|
||||
import { MathjaxReplacer } from './replace-components/mathjax/mathjax-replacer'
|
||||
import { PdfReplacer } from './replace-components/pdf/pdf-replacer'
|
||||
import { QuoteOptionsReplacer } from './replace-components/quote-options/quote-options-replacer'
|
||||
import { TocReplacer } from './replace-components/toc/toc-replacer'
|
||||
import { VimeoReplacer } from './replace-components/vimeo/vimeo-replacer'
|
||||
import { YoutubeReplacer } from './replace-components/youtube/youtube-replacer'
|
||||
|
||||
export interface MarkdownPreviewProps {
|
||||
content: string
|
||||
}
|
||||
|
||||
export type SubNodeConverter = (node: DomElement, index: number) => ReactElement
|
||||
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 createMarkdownIt = ():MarkdownIt => {
|
||||
const md = new MarkdownIt('default', {
|
||||
html: true,
|
||||
breaks: true,
|
||||
|
@ -119,15 +104,32 @@ const MarkdownRenderer: React.FC<MarkdownPreviewProps> = ({ content }) => {
|
|||
})
|
||||
|
||||
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 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 transform: Transform = (node, index) => {
|
||||
const maybeReplacement = tryToReplaceNode(node, index, componentReplacer2Identifier2CounterMap,
|
||||
(subNode, subIndex) => convertNodeToElement(subNode, subIndex, transform))
|
||||
return maybeReplacement || convertNodeToElement(node, index, transform)
|
||||
const subNodeConverter = (subNode: DomElement, subIndex: number) => convertNodeToElement(subNode, subIndex, transform)
|
||||
return tryToReplaceNode(node, index, allReplacers, subNodeConverter) || convertNodeToElement(node, index, transform)
|
||||
}
|
||||
return ReactHtmlParser(html, { transform: transform })
|
||||
}, [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 { 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 preview from './gist-preview.png'
|
||||
|
||||
export interface GistFrameProps {
|
||||
id: string
|
||||
|
@ -14,20 +10,6 @@ interface resizeEvent {
|
|||
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 }) => {
|
||||
const iframeHtml = useMemo(() => {
|
||||
return (`
|
||||
|
@ -85,5 +67,3 @@ export const GistFrame: React.FC<GistFrameProps> = ({ id }) => {
|
|||
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 { ComponentReplacer } from '../../markdown-renderer'
|
||||
import React from 'react'
|
||||
import MathJax from 'react-mathjax'
|
||||
import { ComponentReplacer, SubNodeConverter } from '../ComponentReplacer'
|
||||
|
||||
const getNodeIfMathJaxBlock = (node: DomElement): (DomElement|undefined) => {
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
if (mathJax?.children && mathJax.children[0]) {
|
||||
const mathJaxContent = mathJax.children[0]?.data as string
|
||||
const isInline = (mathJax.attribs?.inline) !== undefined
|
||||
return <MathJax.Node key={index} inline={isInline} formula={mathJaxContent}/>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { getElementReplacement as getMathJaxReplacement }
|
||||
|
|
|
@ -1,20 +1,8 @@
|
|||
import { DomElement } from 'domhandler'
|
||||
import React, { ReactElement } from 'react'
|
||||
import React from 'react'
|
||||
import { ExternalLink } from '../../../../common/links/external-link'
|
||||
import { getAttributesFromCodiMdTag } from '../codi-md-tag-utils'
|
||||
import { OneClickEmbedding } from '../one-click-frame/one-click-embedding'
|
||||
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 {
|
||||
url: string
|
||||
}
|
||||
|
@ -30,5 +18,3 @@ export const PdfFrame: React.FC<PdfFrameProps> = ({ url }) => {
|
|||
</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 { ReactElement } from 'react'
|
||||
import { SubNodeConverter } from '../../markdown-renderer'
|
||||
import { ComponentReplacer, SubNodeConverter } from '../ComponentReplacer'
|
||||
|
||||
const getElementReplacement = (node: DomElement, index: number, counterMap: Map<string, number>, nodeConverter: SubNodeConverter): (ReactElement | undefined) => {
|
||||
if (node.name === 'p' && node.children && node.children.length === 1) {
|
||||
export class TocReplacer implements ComponentReplacer {
|
||||
getReplacement (node: DomElement, index: number, subNodeConverter: SubNodeConverter): React.ReactElement | undefined {
|
||||
if (node.name !== 'p' || node.children?.length !== 1) {
|
||||
return
|
||||
}
|
||||
const possibleTocDiv = node.children[0]
|
||||
if (possibleTocDiv.name === 'div' && possibleTocDiv.attribs && possibleTocDiv.attribs.class &&
|
||||
possibleTocDiv.attribs.class === 'table-of-contents' && possibleTocDiv.children && possibleTocDiv.children.length === 1) {
|
||||
const listElement = possibleTocDiv.children[0]
|
||||
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 { ComponentReplacer } from '../../markdown-renderer'
|
||||
import { getAttributesFromCodiMdTag } from '../codi-md-tag-utils'
|
||||
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 {
|
||||
// Vimeo uses strange names for their fields. ESLint doesn't like that.
|
||||
// eslint-disable-next-line camelcase
|
||||
|
@ -50,5 +38,3 @@ export const VimeoFrame: React.FC<VimeoFrameProps> = ({ id }) => {
|
|||
</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 { ComponentReplacer } from '../../markdown-renderer'
|
||||
import { getAttributesFromCodiMdTag } from '../codi-md-tag-utils'
|
||||
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 {
|
||||
id: string
|
||||
}
|
||||
|
@ -28,5 +16,3 @@ export const YouTubeFrame: React.FC<YouTubeFrameProps> = ({ id }) => {
|
|||
</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…
Reference in a new issue