From b74bb8e71d40b9979b023dc5a9908bda7d7ec180 Mon Sep 17 00:00:00 2001 From: mrdrogdrog Date: Tue, 23 Jun 2020 23:04:51 +0200 Subject: [PATCH] Restructure replacers (#266) * Restructure replacers Signed-off-by: Tilman Vatteroth --- .../markdown-renderer/markdown-renderer.tsx | 160 +++++++++--------- .../replace-components/ComponentReplacer.ts | 8 + .../replace-components/gist/gist-frame.tsx | 20 --- .../replace-components/gist/gist-replacer.tsx | 25 +++ .../highlighted-fence-replacer.tsx | 18 ++ .../highlighted-fence/highlighted-fence.tsx | 16 -- .../mathjax/mathjax-replacer.tsx | 20 +-- .../replace-components/pdf/pdf-frame.tsx | 16 +- .../replace-components/pdf/pdf-replacer.tsx | 19 +++ .../quote-options/quote-options-replacer.tsx | 42 +++++ .../quote-options/quote-options.tsx | 43 ----- .../replace-components/toc/toc-replacer.tsx | 16 +- .../replace-components/vimeo/vimeo-frame.tsx | 14 -- .../vimeo/vimeo-replacer.tsx | 19 +++ .../youtube/youtube-frame.tsx | 14 -- .../youtube/youtube-replacer.tsx | 19 +++ 16 files changed, 250 insertions(+), 219 deletions(-) create mode 100644 src/components/editor/markdown-renderer/replace-components/ComponentReplacer.ts create mode 100644 src/components/editor/markdown-renderer/replace-components/gist/gist-replacer.tsx create mode 100644 src/components/editor/markdown-renderer/replace-components/highlighted-fence/highlighted-fence-replacer.tsx delete mode 100644 src/components/editor/markdown-renderer/replace-components/highlighted-fence/highlighted-fence.tsx create mode 100644 src/components/editor/markdown-renderer/replace-components/pdf/pdf-replacer.tsx create mode 100644 src/components/editor/markdown-renderer/replace-components/quote-options/quote-options-replacer.tsx delete mode 100644 src/components/editor/markdown-renderer/replace-components/quote-options/quote-options.tsx create mode 100644 src/components/editor/markdown-renderer/replace-components/vimeo/vimeo-replacer.tsx create mode 100644 src/components/editor/markdown-renderer/replace-components/youtube/youtube-replacer.tsx diff --git a/src/components/editor/markdown-renderer/markdown-renderer.tsx b/src/components/editor/markdown-renderer/markdown-renderer.tsx index c02cde773..f55e236d0 100644 --- a/src/components/editor/markdown-renderer/markdown-renderer.tsx +++ b/src/components/editor/markdown-renderer/markdown-renderer.tsx @@ -34,100 +34,102 @@ 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, nodeConverter: SubNodeConverter) => (ReactElement | undefined); -type ComponentReplacer2Identifier2CounterMap = Map> +const createMarkdownIt = ():MarkdownIt => { + const md = new MarkdownIt('default', { + html: true, + breaks: true, + langPrefix: '', + typographer: true + }) + md.use(taskList) + md.use(emoji) + md.use(abbreviation) + md.use(definitionList) + md.use(subscript) + md.use(superscript) + md.use(inserted) + md.use(marked) + md.use(footnote) + md.use(imsize) + // noinspection CheckTagEmptyBody + md.use(anchor, { + permalink: true, + permalinkBefore: true, + permalinkClass: 'heading-anchor text-dark', + permalinkSymbol: '' + }) + md.use(toc, { + includeLevel: [1, 2, 3], + markerPattern: /^\[TOC]$/i + }) + md.use(mathJax({ + beforeMath: '', + afterMath: '', + beforeInlineMath: '', + afterInlineMath: '', + beforeDisplayMath: '', + afterDisplayMath: '' + })) + 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, 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(MarkdownItParserDebugger) -const allComponentReplacers: ComponentReplacer[] = [getYouTubeReplacement, getVimeoReplacement, getGistReplacement, getPDFReplacement, getTOCReplacement, getHighlightedFence, getQuoteOptionsReplacement, getMathJaxReplacement] + validAlertLevels.forEach(level => { + md.use(markdownItContainer, level, { render: createRenderContainer(level) }) + }) -const tryToReplaceNode = (node: DomElement, index:number, componentReplacer2Identifier2CounterMap: ComponentReplacer2Identifier2CounterMap, nodeConverter: SubNodeConverter) => { - return allComponentReplacers - .map((componentReplacer) => { - const identifier2CounterMap = componentReplacer2Identifier2CounterMap.get(componentReplacer) || new Map() - return componentReplacer(node, index, identifier2CounterMap, nodeConverter) - }) + 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 = ({ content }) => { - const markdownIt = useMemo(() => { - const md = new MarkdownIt('default', { - html: true, - breaks: true, - langPrefix: '', - typographer: true - }) - md.use(taskList) - md.use(emoji) - md.use(abbreviation) - md.use(definitionList) - md.use(subscript) - md.use(superscript) - md.use(inserted) - md.use(marked) - md.use(footnote) - md.use(imsize) - // noinspection CheckTagEmptyBody - md.use(anchor, { - permalink: true, - permalinkBefore: true, - permalinkClass: 'heading-anchor text-dark', - permalinkSymbol: '' - }) - md.use(toc, { - includeLevel: [1, 2, 3], - markerPattern: /^\[TOC]$/i - }) - md.use(mathJax({ - beforeMath: '', - afterMath: '', - beforeInlineMath: '', - afterInlineMath: '', - beforeDisplayMath: '', - afterDisplayMath: '' - })) - 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, 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(MarkdownItParserDebugger) - - validAlertLevels.forEach(level => { - md.use(markdownItContainer, level, { render: createRenderContainer(level) }) - }) - - return md - }, []) + const markdownIt = useMemo(createMarkdownIt, []) const result: ReactElement[] = useMemo(() => { - const componentReplacer2Identifier2CounterMap = new Map>() + 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]) diff --git a/src/components/editor/markdown-renderer/replace-components/ComponentReplacer.ts b/src/components/editor/markdown-renderer/replace-components/ComponentReplacer.ts new file mode 100644 index 000000000..c7f226862 --- /dev/null +++ b/src/components/editor/markdown-renderer/replace-components/ComponentReplacer.ts @@ -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) +} diff --git a/src/components/editor/markdown-renderer/replace-components/gist/gist-frame.tsx b/src/components/editor/markdown-renderer/replace-components/gist/gist-frame.tsx index 6ac0360ee..2d0269152 100644 --- a/src/components/editor/markdown-renderer/replace-components/gist/gist-frame.tsx +++ b/src/components/editor/markdown-renderer/replace-components/gist/gist-frame.tsx @@ -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 ( - - - - ) - } -} - export const GistFrame: React.FC = ({ id }) => { const iframeHtml = useMemo(() => { return (` @@ -85,5 +67,3 @@ export const GistFrame: React.FC = ({ id }) => { src={`data:text/html;base64,${btoa(iframeHtml)}`}/> ) } - -export { getElementReplacement as getGistReplacement } diff --git a/src/components/editor/markdown-renderer/replace-components/gist/gist-replacer.tsx b/src/components/editor/markdown-renderer/replace-components/gist/gist-replacer.tsx new file mode 100644 index 000000000..5463c1758 --- /dev/null +++ b/src/components/editor/markdown-renderer/replace-components/gist/gist-replacer.tsx @@ -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 = new Map() + + 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 ( + + + + ) + } + } +} diff --git a/src/components/editor/markdown-renderer/replace-components/highlighted-fence/highlighted-fence-replacer.tsx b/src/components/editor/markdown-renderer/replace-components/highlighted-fence/highlighted-fence-replacer.tsx new file mode 100644 index 000000000..fb4a30ca9 --- /dev/null +++ b/src/components/editor/markdown-renderer/replace-components/highlighted-fence/highlighted-fence-replacer.tsx @@ -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 + } +} diff --git a/src/components/editor/markdown-renderer/replace-components/highlighted-fence/highlighted-fence.tsx b/src/components/editor/markdown-renderer/replace-components/highlighted-fence/highlighted-fence.tsx deleted file mode 100644 index 9e880b0ec..000000000 --- a/src/components/editor/markdown-renderer/replace-components/highlighted-fence/highlighted-fence.tsx +++ /dev/null @@ -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): (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 -} - -export { getElementReplacement as getHighlightedFence } diff --git a/src/components/editor/markdown-renderer/replace-components/mathjax/mathjax-replacer.tsx b/src/components/editor/markdown-renderer/replace-components/mathjax/mathjax-replacer.tsx index 6c466df99..cffcd777f 100644 --- a/src/components/editor/markdown-renderer/replace-components/mathjax/mathjax-replacer.tsx +++ b/src/components/editor/markdown-renderer/replace-components/mathjax/mathjax-replacer.tsx @@ -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) => { - 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 +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 + } } } - -export { getElementReplacement as getMathJaxReplacement } diff --git a/src/components/editor/markdown-renderer/replace-components/pdf/pdf-frame.tsx b/src/components/editor/markdown-renderer/replace-components/pdf/pdf-frame.tsx index 9b7f9a43f..6141f78c5 100644 --- a/src/components/editor/markdown-renderer/replace-components/pdf/pdf-frame.tsx +++ b/src/components/editor/markdown-renderer/replace-components/pdf/pdf-frame.tsx @@ -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): (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 - } -} - export interface PdfFrameProps { url: string } @@ -30,5 +18,3 @@ export const PdfFrame: React.FC = ({ url }) => { ) } - -export { getElementReplacement as getPDFReplacement } diff --git a/src/components/editor/markdown-renderer/replace-components/pdf/pdf-replacer.tsx b/src/components/editor/markdown-renderer/replace-components/pdf/pdf-replacer.tsx new file mode 100644 index 000000000..0f9dc00b4 --- /dev/null +++ b/src/components/editor/markdown-renderer/replace-components/pdf/pdf-replacer.tsx @@ -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 = new Map() + + 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 + } + } +} diff --git a/src/components/editor/markdown-renderer/replace-components/quote-options/quote-options-replacer.tsx b/src/components/editor/markdown-renderer/replace-components/quote-options/quote-options-replacer.tsx new file mode 100644 index 000000000..8876bfe39 --- /dev/null +++ b/src/components/editor/markdown-renderer/replace-components/quote-options/quote-options-replacer.tsx @@ -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) + } +} diff --git a/src/components/editor/markdown-renderer/replace-components/quote-options/quote-options.tsx b/src/components/editor/markdown-renderer/replace-components/quote-options/quote-options.tsx deleted file mode 100644 index 08db5436c..000000000 --- a/src/components/editor/markdown-renderer/replace-components/quote-options/quote-options.tsx +++ /dev/null @@ -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, 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 } diff --git a/src/components/editor/markdown-renderer/replace-components/toc/toc-replacer.tsx b/src/components/editor/markdown-renderer/replace-components/toc/toc-replacer.tsx index 7a0e12dd1..c05413632 100644 --- a/src/components/editor/markdown-renderer/replace-components/toc/toc-replacer.tsx +++ b/src/components/editor/markdown-renderer/replace-components/toc/toc-replacer.tsx @@ -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, 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) { + 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 } diff --git a/src/components/editor/markdown-renderer/replace-components/vimeo/vimeo-frame.tsx b/src/components/editor/markdown-renderer/replace-components/vimeo/vimeo-frame.tsx index 0d5f7a910..8dce7c8c6 100644 --- a/src/components/editor/markdown-renderer/replace-components/vimeo/vimeo-frame.tsx +++ b/src/components/editor/markdown-renderer/replace-components/vimeo/vimeo-frame.tsx @@ -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 - } -} - 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 = ({ id }) => { ) } - -export { getElementReplacement as getVimeoReplacement } diff --git a/src/components/editor/markdown-renderer/replace-components/vimeo/vimeo-replacer.tsx b/src/components/editor/markdown-renderer/replace-components/vimeo/vimeo-replacer.tsx new file mode 100644 index 000000000..887b9e461 --- /dev/null +++ b/src/components/editor/markdown-renderer/replace-components/vimeo/vimeo-replacer.tsx @@ -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 = new Map() + + 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 + } + } +} diff --git a/src/components/editor/markdown-renderer/replace-components/youtube/youtube-frame.tsx b/src/components/editor/markdown-renderer/replace-components/youtube/youtube-frame.tsx index aab82d174..1e2e78a9f 100644 --- a/src/components/editor/markdown-renderer/replace-components/youtube/youtube-frame.tsx +++ b/src/components/editor/markdown-renderer/replace-components/youtube/youtube-frame.tsx @@ -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 - } -} - export interface YouTubeFrameProps { id: string } @@ -28,5 +16,3 @@ export const YouTubeFrame: React.FC = ({ id }) => { ) } - -export { getElementReplacement as getYouTubeReplacement } diff --git a/src/components/editor/markdown-renderer/replace-components/youtube/youtube-replacer.tsx b/src/components/editor/markdown-renderer/replace-components/youtube/youtube-replacer.tsx new file mode 100644 index 000000000..07453c15d --- /dev/null +++ b/src/components/editor/markdown-renderer/replace-components/youtube/youtube-replacer.tsx @@ -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 = new Map() + + 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 + } + } +}