mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-19 17:55:17 -04:00
Replace react-html-parser with html-to-react (#1327)
* Replace react-html-parser with html-to-react Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
b13c1ce8a0
commit
82472227f9
31 changed files with 329 additions and 167 deletions
|
@ -4,20 +4,46 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { DomElement } from 'domhandler'
|
||||
import { Element, isText, NodeWithChildren } from 'domhandler'
|
||||
import MarkdownIt from 'markdown-it'
|
||||
import { ReactElement } from 'react'
|
||||
|
||||
export type SubNodeTransform = (node: DomElement, subIndex: number) => ReactElement | void | null
|
||||
export type ValidReactDomElement = ReactElement | string | null
|
||||
|
||||
export type NativeRenderer = () => ReactElement
|
||||
export type SubNodeTransform = (node: Element, subKey: number | string) => ValidReactDomElement | void
|
||||
|
||||
export type NativeRenderer = () => ValidReactDomElement
|
||||
|
||||
export type MarkdownItPlugin = MarkdownIt.PluginSimple | MarkdownIt.PluginWithOptions | MarkdownIt.PluginWithParams
|
||||
|
||||
/**
|
||||
* Base class for all component replacers.
|
||||
* Component replacers detect structures in the HTML DOM from markdown it
|
||||
* and replace them with some special react components.
|
||||
*/
|
||||
export abstract class ComponentReplacer {
|
||||
/**
|
||||
* Extracts the content of the first text child node.
|
||||
*
|
||||
* @param node the node with the text node child
|
||||
* @return the string content
|
||||
*/
|
||||
protected static extractTextChildContent(node: NodeWithChildren): string {
|
||||
const childrenTextNode = node.children[0]
|
||||
return isText(childrenTextNode) ? childrenTextNode.data : ''
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current node should be altered or replaced and does if needed.
|
||||
*
|
||||
* @param node The current html dom node
|
||||
* @param subNodeTransform should be used to convert child elements of the current node
|
||||
* @param nativeRenderer renders the current node as it is without any replacement.
|
||||
* @return the replacement for the current node or undefined if the current replacer replacer hasn't done anything.
|
||||
*/
|
||||
public abstract getReplacement(
|
||||
node: DomElement,
|
||||
node: Element,
|
||||
subNodeTransform: SubNodeTransform,
|
||||
nativeRenderer: NativeRenderer
|
||||
): ReactElement | null | undefined
|
||||
): ValidReactDomElement | undefined
|
||||
}
|
||||
|
|
|
@ -4,13 +4,16 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { DomElement } from 'domhandler'
|
||||
import { Element } from 'domhandler'
|
||||
import React from 'react'
|
||||
import { ComponentReplacer } from '../ComponentReplacer'
|
||||
import { AbcFrame } from './abc-frame'
|
||||
|
||||
export class AbcReplacer implements ComponentReplacer {
|
||||
getReplacement(codeNode: DomElement): React.ReactElement | undefined {
|
||||
/**
|
||||
* Detects code blocks with "abc" as language and renders them as ABC.js
|
||||
*/
|
||||
export class AbcReplacer extends ComponentReplacer {
|
||||
getReplacement(codeNode: Element): React.ReactElement | undefined {
|
||||
if (
|
||||
codeNode.name !== 'code' ||
|
||||
!codeNode.attribs ||
|
||||
|
@ -22,7 +25,7 @@ export class AbcReplacer implements ComponentReplacer {
|
|||
return
|
||||
}
|
||||
|
||||
const code = codeNode.children[0].data as string
|
||||
const code = ComponentReplacer.extractTextChildContent(codeNode)
|
||||
|
||||
return <AbcFrame code={code} />
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { DomElement } from 'domhandler'
|
||||
import { Element } from 'domhandler'
|
||||
import MarkdownIt from 'markdown-it'
|
||||
import markdownItRegex from 'markdown-it-regex'
|
||||
import React from 'react'
|
||||
|
@ -13,12 +13,15 @@ import { getAttributesFromHedgeDocTag } from '../utils'
|
|||
import { AsciinemaFrame } from './asciinema-frame'
|
||||
import { replaceAsciinemaLink } from './replace-asciinema-link'
|
||||
|
||||
/**
|
||||
* Detects code blocks with "asciinema" as language and renders them Asciinema frame
|
||||
*/
|
||||
export class AsciinemaReplacer extends ComponentReplacer {
|
||||
public static readonly markdownItPlugin: MarkdownIt.PluginSimple = (markdownIt) => {
|
||||
markdownItRegex(markdownIt, replaceAsciinemaLink)
|
||||
}
|
||||
|
||||
public getReplacement(node: DomElement): React.ReactElement | undefined {
|
||||
public getReplacement(node: Element): React.ReactElement | undefined {
|
||||
const attributes = getAttributesFromHedgeDocTag(node, 'asciinema')
|
||||
if (attributes && attributes.id) {
|
||||
const asciinemaId = attributes.id
|
||||
|
|
|
@ -4,45 +4,60 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { DomElement } from 'domhandler'
|
||||
import { ReactElement } from 'react'
|
||||
import { ComponentReplacer, NativeRenderer, SubNodeTransform } from '../ComponentReplacer'
|
||||
import { Element, isTag } from 'domhandler'
|
||||
import { ComponentReplacer, NativeRenderer, SubNodeTransform, ValidReactDomElement } from '../ComponentReplacer'
|
||||
|
||||
const isColorExtraElement = (node: DomElement | undefined): boolean => {
|
||||
/**
|
||||
* Checks if the given node is a blockquote color definition
|
||||
*
|
||||
* @param node The node to check
|
||||
* @return true if the checked node is a blockquote color definition
|
||||
*/
|
||||
const isBlockquoteColorDefinition = (node: Element | 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 => {
|
||||
/**
|
||||
* Checks if any of the given nodes is the parent element of a color extra element.
|
||||
*
|
||||
* @param nodes The array of nodes to check
|
||||
* @return the found element or undefined if no element was found
|
||||
*/
|
||||
const findBlockquoteColorParentElement = (nodes: Element[]): Element | undefined => {
|
||||
return nodes.find((child) => {
|
||||
if (child.name !== 'p' || !child.children || child.children.length < 1) {
|
||||
return false
|
||||
}
|
||||
return child.children.find(isColorExtraElement) !== undefined
|
||||
return child.children.filter(isTag).find(isBlockquoteColorDefinition) !== undefined
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects blockquotes and checks if they contain a color tag.
|
||||
* If a color tag was found then the color will be applied to the node as border.
|
||||
*/
|
||||
export class ColoredBlockquoteReplacer extends ComponentReplacer {
|
||||
public getReplacement(
|
||||
node: DomElement,
|
||||
node: Element,
|
||||
subNodeTransform: SubNodeTransform,
|
||||
nativeRenderer: NativeRenderer
|
||||
): ReactElement | undefined {
|
||||
): ValidReactDomElement | undefined {
|
||||
if (node.name !== 'blockquote' || !node.children || node.children.length < 1) {
|
||||
return
|
||||
}
|
||||
const paragraph = findQuoteOptionsParent(node.children)
|
||||
const paragraph = findBlockquoteColorParentElement(node.children.filter(isTag))
|
||||
if (!paragraph) {
|
||||
return
|
||||
}
|
||||
const childElements = paragraph.children || []
|
||||
const optionsTag = childElements.find(isColorExtraElement)
|
||||
const optionsTag = childElements.filter(isTag).find(isBlockquoteColorDefinition)
|
||||
if (!optionsTag) {
|
||||
return
|
||||
}
|
||||
paragraph.children = childElements.filter((elem) => !isColorExtraElement(elem))
|
||||
paragraph.children = childElements.filter((elem) => !isTag(elem) || !isBlockquoteColorDefinition(elem))
|
||||
const attributes = optionsTag.attribs
|
||||
if (!attributes || !attributes['data-color']) {
|
||||
return
|
||||
|
|
|
@ -4,13 +4,16 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { DomElement } from 'domhandler'
|
||||
import { Element } from 'domhandler'
|
||||
import React from 'react'
|
||||
import { ComponentReplacer } from '../ComponentReplacer'
|
||||
import { CsvTable } from './csv-table'
|
||||
|
||||
/**
|
||||
* Detects code blocks with "csv" as language and renders them as table.
|
||||
*/
|
||||
export class CsvReplacer extends ComponentReplacer {
|
||||
public getReplacement(codeNode: DomElement): React.ReactElement | undefined {
|
||||
public getReplacement(codeNode: Element): React.ReactElement | undefined {
|
||||
if (
|
||||
codeNode.name !== 'code' ||
|
||||
!codeNode.attribs ||
|
||||
|
@ -22,7 +25,7 @@ export class CsvReplacer extends ComponentReplacer {
|
|||
return
|
||||
}
|
||||
|
||||
const code = codeNode.children[0].data as string
|
||||
const code = ComponentReplacer.extractTextChildContent(codeNode)
|
||||
|
||||
const extraData = codeNode.attribs['data-extra']
|
||||
const extraRegex = /\s*(delimiter=([^\s]*))?\s*(header)?/
|
||||
|
|
|
@ -4,13 +4,16 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { DomElement } from 'domhandler'
|
||||
import { Element } from 'domhandler'
|
||||
import React from 'react'
|
||||
import { ComponentReplacer } from '../ComponentReplacer'
|
||||
import { FlowChart } from './flowchart/flowchart'
|
||||
|
||||
/**
|
||||
* Detects code blocks with "flow" as language and renders them as flow chart.
|
||||
*/
|
||||
export class FlowchartReplacer extends ComponentReplacer {
|
||||
public getReplacement(codeNode: DomElement): React.ReactElement | undefined {
|
||||
public getReplacement(codeNode: Element): React.ReactElement | undefined {
|
||||
if (
|
||||
codeNode.name !== 'code' ||
|
||||
!codeNode.attribs ||
|
||||
|
@ -22,7 +25,7 @@ export class FlowchartReplacer extends ComponentReplacer {
|
|||
return
|
||||
}
|
||||
|
||||
const code = codeNode.children[0].data as string
|
||||
const code = ComponentReplacer.extractTextChildContent(codeNode)
|
||||
|
||||
return <FlowChart code={code} />
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { DomElement } from 'domhandler'
|
||||
import { Element } from 'domhandler'
|
||||
import MarkdownIt from 'markdown-it'
|
||||
import markdownItRegex from 'markdown-it-regex'
|
||||
import React from 'react'
|
||||
|
@ -16,13 +16,16 @@ import preview from './gist-preview.png'
|
|||
import { replaceGistLink } from './replace-gist-link'
|
||||
import { replaceLegacyGistShortCode } from './replace-legacy-gist-short-code'
|
||||
|
||||
/**
|
||||
* Detects "app-gist" tags and renders them as gist frames.
|
||||
*/
|
||||
export class GistReplacer extends ComponentReplacer {
|
||||
public static readonly markdownItPlugin: MarkdownIt.PluginSimple = (markdownIt) => {
|
||||
markdownItRegex(markdownIt, replaceGistLink)
|
||||
markdownItRegex(markdownIt, replaceLegacyGistShortCode)
|
||||
}
|
||||
|
||||
public getReplacement(node: DomElement): React.ReactElement | undefined {
|
||||
public getReplacement(node: Element): React.ReactElement | undefined {
|
||||
const attributes = getAttributesFromHedgeDocTag(node, 'gist')
|
||||
if (attributes && attributes.id) {
|
||||
const gistId = attributes.id
|
||||
|
|
|
@ -4,13 +4,16 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { DomElement } from 'domhandler'
|
||||
import { Element } from 'domhandler'
|
||||
import React from 'react'
|
||||
import { ComponentReplacer } from '../ComponentReplacer'
|
||||
import { GraphvizFrame } from './graphviz-frame'
|
||||
|
||||
export class GraphvizReplacer implements ComponentReplacer {
|
||||
getReplacement(codeNode: DomElement): React.ReactElement | undefined {
|
||||
/**
|
||||
* Detects code blocks with "graphviz" as language and renders them as graphviz graph.
|
||||
*/
|
||||
export class GraphvizReplacer extends ComponentReplacer {
|
||||
getReplacement(codeNode: Element): React.ReactElement | undefined {
|
||||
if (
|
||||
codeNode.name !== 'code' ||
|
||||
!codeNode.attribs ||
|
||||
|
@ -22,7 +25,7 @@ export class GraphvizReplacer implements ComponentReplacer {
|
|||
return
|
||||
}
|
||||
|
||||
const code = codeNode.children[0].data as string
|
||||
const code = ComponentReplacer.extractTextChildContent(codeNode)
|
||||
|
||||
return <GraphvizFrame code={code} />
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import React, { Fragment, ReactElement, useEffect, useState } from 'react'
|
||||
import ReactHtmlParser from 'react-html-parser'
|
||||
import convertHtmlToReact from '@hedgedoc/html-to-react'
|
||||
import { CopyToClipboardButton } from '../../../../common/copyable/copy-to-clipboard-button/copy-to-clipboard-button'
|
||||
import '../../../utils/button-inside.scss'
|
||||
import './highlighted-code.scss'
|
||||
|
@ -29,11 +29,11 @@ const escapeHtml = (unsafe: string): string => {
|
|||
.replaceAll(/'/g, ''')
|
||||
}
|
||||
|
||||
const replaceCode = (code: string): ReactElement[][] => {
|
||||
const replaceCode = (code: string): (ReactElement | null | string)[][] => {
|
||||
return code
|
||||
.split('\n')
|
||||
.filter((line) => !!line)
|
||||
.map((line) => ReactHtmlParser(line))
|
||||
.map((line) => convertHtmlToReact(line, {}))
|
||||
}
|
||||
|
||||
export const HighlightedCode: React.FC<HighlightedCodeProps> = ({ code, language, startLineNumber, wrapLines }) => {
|
||||
|
|
|
@ -4,15 +4,18 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { DomElement } from 'domhandler'
|
||||
import { Element } from 'domhandler'
|
||||
import React from 'react'
|
||||
import { ComponentReplacer } from '../ComponentReplacer'
|
||||
import { HighlightedCode } from './highlighted-code/highlighted-code'
|
||||
|
||||
/**
|
||||
* Detects code blocks and renders them as highlighted code blocks
|
||||
*/
|
||||
export class HighlightedCodeReplacer extends ComponentReplacer {
|
||||
private lastLineNumber = 0
|
||||
|
||||
public getReplacement(codeNode: DomElement): React.ReactElement | undefined {
|
||||
public getReplacement(codeNode: Element): React.ReactElement | undefined {
|
||||
if (
|
||||
codeNode.name !== 'code' ||
|
||||
!codeNode.attribs ||
|
||||
|
@ -39,7 +42,7 @@ export class HighlightedCodeReplacer extends ComponentReplacer {
|
|||
|
||||
const startLineNumber =
|
||||
startLineNumberAttribute === '+' ? this.lastLineNumber : parseInt(startLineNumberAttribute) || 1
|
||||
const code = codeNode.children[0].data as string
|
||||
const code = ComponentReplacer.extractTextChildContent(codeNode)
|
||||
|
||||
if (showLineNumbers) {
|
||||
this.lastLineNumber = startLineNumber + code.split('\n').filter((line) => !!line).length
|
||||
|
|
|
@ -4,13 +4,16 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { DomElement } from 'domhandler'
|
||||
import { Element } from 'domhandler'
|
||||
import React from 'react'
|
||||
import { ComponentReplacer } from '../ComponentReplacer'
|
||||
import { ProxyImageFrame } from './proxy-image-frame'
|
||||
|
||||
export type ImageClickHandler = (event: React.MouseEvent<HTMLImageElement, MouseEvent>) => void
|
||||
|
||||
/**
|
||||
* Detects image tags and loads them via image proxy if configured.
|
||||
*/
|
||||
export class ImageReplacer extends ComponentReplacer {
|
||||
private readonly clickHandler?: ImageClickHandler
|
||||
|
||||
|
@ -19,7 +22,7 @@ export class ImageReplacer extends ComponentReplacer {
|
|||
this.clickHandler = clickHandler
|
||||
}
|
||||
|
||||
public getReplacement(node: DomElement): React.ReactElement | undefined {
|
||||
public getReplacement(node: Element): React.ReactElement | undefined {
|
||||
if (node.name === 'img' && node.attribs) {
|
||||
return (
|
||||
<ProxyImageFrame
|
||||
|
|
|
@ -4,28 +4,43 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { DomElement } from 'domhandler'
|
||||
import { Element, isTag } from 'domhandler'
|
||||
import MarkdownIt from 'markdown-it'
|
||||
import mathJax from 'markdown-it-mathjax'
|
||||
import React from 'react'
|
||||
import { ComponentReplacer } from '../ComponentReplacer'
|
||||
import './katex.scss'
|
||||
|
||||
const getNodeIfKatexBlock = (node: DomElement): DomElement | undefined => {
|
||||
/**
|
||||
* Checks if the given node is a KaTeX block.
|
||||
*
|
||||
* @param node the node to check
|
||||
* @return The given node if it is a KaTeX block element, undefined otherwise.
|
||||
*/
|
||||
const getNodeIfKatexBlock = (node: Element): Element | undefined => {
|
||||
if (node.name !== 'p' || !node.children || node.children.length === 0) {
|
||||
return
|
||||
}
|
||||
return node.children.find((subnode) => {
|
||||
return node.children.filter(isTag).find((subnode) => {
|
||||
return subnode.name === 'app-katex' && subnode.attribs?.inline === undefined
|
||||
})
|
||||
}
|
||||
|
||||
const getNodeIfInlineKatex = (node: DomElement): DomElement | undefined => {
|
||||
/**
|
||||
* Checks if the given node is a KaTeX inline element.
|
||||
*
|
||||
* @param node the node to check
|
||||
* @return The given node if it is a KaTeX inline element, undefined otherwise.
|
||||
*/
|
||||
const getNodeIfInlineKatex = (node: Element): Element | undefined => {
|
||||
return node.name === 'app-katex' && node.attribs?.inline !== undefined ? node : undefined
|
||||
}
|
||||
|
||||
const KaTeX = React.lazy(() => import(/* webpackChunkName: "katex" */ '@matejmazur/react-katex'))
|
||||
|
||||
/**
|
||||
* Detects LaTeX syntax and renders it with KaTeX.
|
||||
*/
|
||||
export class KatexReplacer extends ComponentReplacer {
|
||||
public static readonly markdownItPlugin: MarkdownIt.PluginSimple = mathJax({
|
||||
beforeMath: '<app-katex>',
|
||||
|
@ -36,10 +51,10 @@ export class KatexReplacer extends ComponentReplacer {
|
|||
afterDisplayMath: '</app-katex>'
|
||||
})
|
||||
|
||||
public getReplacement(node: DomElement): React.ReactElement | undefined {
|
||||
public getReplacement(node: Element): React.ReactElement | undefined {
|
||||
const katex = getNodeIfKatexBlock(node) || getNodeIfInlineKatex(node)
|
||||
if (katex?.children && katex.children[0]) {
|
||||
const mathJaxContent = katex.children[0]?.data as string
|
||||
const mathJaxContent = ComponentReplacer.extractTextChildContent(katex)
|
||||
const isInline = katex.attribs?.inline !== undefined
|
||||
return <KaTeX block={!isInline} math={mathJaxContent} errorColor={'#cc0000'} />
|
||||
}
|
||||
|
|
|
@ -4,11 +4,14 @@
|
|||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { DomElement } from 'domhandler'
|
||||
import { Element } from 'domhandler'
|
||||
import { ComponentReplacer } from '../ComponentReplacer'
|
||||
|
||||
/**
|
||||
* Detects line markers and suppresses them in the resulting DOM.
|
||||
*/
|
||||
export class LinemarkerReplacer extends ComponentReplacer {
|
||||
public getReplacement(codeNode: DomElement): null | undefined {
|
||||
public getReplacement(codeNode: Element): null | undefined {
|
||||
return codeNode.name === 'app-linemarker' ? null : undefined
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { DomElement } from 'domhandler'
|
||||
import React, { ReactElement } from 'react'
|
||||
import { ComponentReplacer, NativeRenderer, SubNodeTransform } from '../ComponentReplacer'
|
||||
import { Element } from 'domhandler'
|
||||
import React from 'react'
|
||||
import { ComponentReplacer, NativeRenderer, SubNodeTransform, ValidReactDomElement } from '../ComponentReplacer'
|
||||
|
||||
export const createJumpToMarkClickEventHandler = (id: string) => {
|
||||
return (event: React.MouseEvent<HTMLElement, MouseEvent>): void => {
|
||||
|
@ -14,16 +14,21 @@ export const createJumpToMarkClickEventHandler = (id: string) => {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects link tags and polishs them.
|
||||
* This replacer prevents data and javascript links,
|
||||
* extends relative links with the base url and creates working jump links.
|
||||
*/
|
||||
export class LinkReplacer extends ComponentReplacer {
|
||||
constructor(private baseUrl?: string) {
|
||||
super()
|
||||
}
|
||||
|
||||
public getReplacement(
|
||||
node: DomElement,
|
||||
node: Element,
|
||||
subNodeTransform: SubNodeTransform,
|
||||
nativeRenderer: NativeRenderer
|
||||
): ReactElement | null | undefined {
|
||||
): ValidReactDomElement | undefined {
|
||||
if (node.name !== 'a' || !node.attribs || !node.attribs.href) {
|
||||
return undefined
|
||||
}
|
||||
|
|
|
@ -4,13 +4,16 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { DomElement } from 'domhandler'
|
||||
import { Element } from 'domhandler'
|
||||
import React from 'react'
|
||||
import { ComponentReplacer } from '../ComponentReplacer'
|
||||
import { MarkmapFrame } from './markmap-frame'
|
||||
|
||||
export class MarkmapReplacer implements ComponentReplacer {
|
||||
getReplacement(codeNode: DomElement): React.ReactElement | undefined {
|
||||
/**
|
||||
* Detects code blocks with 'markmap' as language and renders them with Markmap.
|
||||
*/
|
||||
export class MarkmapReplacer extends ComponentReplacer {
|
||||
getReplacement(codeNode: Element): React.ReactElement | undefined {
|
||||
if (
|
||||
codeNode.name !== 'code' ||
|
||||
!codeNode.attribs ||
|
||||
|
@ -22,7 +25,7 @@ export class MarkmapReplacer implements ComponentReplacer {
|
|||
return
|
||||
}
|
||||
|
||||
const code = codeNode.children[0].data as string
|
||||
const code = ComponentReplacer.extractTextChildContent(codeNode)
|
||||
|
||||
return <MarkmapFrame code={code} />
|
||||
}
|
||||
|
|
|
@ -4,13 +4,16 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { DomElement } from 'domhandler'
|
||||
import { Element } from 'domhandler'
|
||||
import React from 'react'
|
||||
import { ComponentReplacer } from '../ComponentReplacer'
|
||||
import { MermaidChart } from './mermaid-chart'
|
||||
|
||||
export class MermaidReplacer implements ComponentReplacer {
|
||||
getReplacement(codeNode: DomElement): React.ReactElement | undefined {
|
||||
/**
|
||||
* Detects code blocks with 'mermaid' as language and renders them with mermaid.
|
||||
*/
|
||||
export class MermaidReplacer extends ComponentReplacer {
|
||||
getReplacement(codeNode: Element): React.ReactElement | undefined {
|
||||
if (
|
||||
codeNode.name !== 'code' ||
|
||||
!codeNode.attribs ||
|
||||
|
@ -22,7 +25,7 @@ export class MermaidReplacer implements ComponentReplacer {
|
|||
return
|
||||
}
|
||||
|
||||
const code = codeNode.children[0].data as string
|
||||
const code = ComponentReplacer.extractTextChildContent(codeNode)
|
||||
|
||||
return <MermaidChart code={code} />
|
||||
}
|
||||
|
|
|
@ -4,14 +4,18 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { DomElement } from 'domhandler'
|
||||
import { Element } from 'domhandler'
|
||||
import React, { Fragment } from 'react'
|
||||
import { ComponentReplacer } from '../ComponentReplacer'
|
||||
import { MermaidChart } from '../mermaid/mermaid-chart'
|
||||
import { DeprecationWarning } from './deprecation-warning'
|
||||
|
||||
export class SequenceDiagramReplacer implements ComponentReplacer {
|
||||
getReplacement(codeNode: DomElement): React.ReactElement | undefined {
|
||||
/**
|
||||
* Detects code blocks with 'sequence' as language and renders them as
|
||||
* sequence diagram with mermaid.
|
||||
*/
|
||||
export class SequenceDiagramReplacer extends ComponentReplacer {
|
||||
getReplacement(codeNode: Element): React.ReactElement | undefined {
|
||||
if (
|
||||
codeNode.name !== 'code' ||
|
||||
!codeNode.attribs ||
|
||||
|
@ -23,7 +27,7 @@ export class SequenceDiagramReplacer implements ComponentReplacer {
|
|||
return
|
||||
}
|
||||
|
||||
const code = codeNode.children[0].data as string
|
||||
const code = ComponentReplacer.extractTextChildContent(codeNode)
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
|
|
|
@ -4,12 +4,15 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { DomElement } from 'domhandler'
|
||||
import { Element } from 'domhandler'
|
||||
import React, { ReactElement } from 'react'
|
||||
import { ComponentReplacer } from '../ComponentReplacer'
|
||||
|
||||
export type TaskCheckedChangeHandler = (lineInMarkdown: number, checked: boolean) => void
|
||||
|
||||
/**
|
||||
* Detects task lists and renders them as checkboxes that execute a callback if clicked.
|
||||
*/
|
||||
export class TaskListReplacer extends ComponentReplacer {
|
||||
onTaskCheckedChange?: (lineInMarkdown: number, checked: boolean) => void
|
||||
|
||||
|
@ -25,7 +28,7 @@ export class TaskListReplacer extends ComponentReplacer {
|
|||
}
|
||||
}
|
||||
|
||||
public getReplacement(node: DomElement): ReactElement | undefined {
|
||||
public getReplacement(node: Element): ReactElement | undefined {
|
||||
if (node.attribs?.class !== 'task-list-item-checkbox') {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -4,12 +4,9 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { DomElement } from 'domhandler'
|
||||
import { Element } from 'domhandler'
|
||||
|
||||
export const getAttributesFromHedgeDocTag = (
|
||||
node: DomElement,
|
||||
tagName: string
|
||||
): { [s: string]: string } | undefined => {
|
||||
export const getAttributesFromHedgeDocTag = (node: Element, tagName: string): { [s: string]: string } | undefined => {
|
||||
if (node.name !== `app-${tagName}` || !node.attribs) {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -4,13 +4,16 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { DomElement } from 'domhandler'
|
||||
import { Element } from 'domhandler'
|
||||
import React from 'react'
|
||||
import { ComponentReplacer } from '../ComponentReplacer'
|
||||
import { VegaChart } from './vega-chart'
|
||||
|
||||
export class VegaReplacer implements ComponentReplacer {
|
||||
getReplacement(codeNode: DomElement): React.ReactElement | undefined {
|
||||
/**
|
||||
* Detects code blocks with 'vega-lite' as language and renders them with Vega.
|
||||
*/
|
||||
export class VegaReplacer extends ComponentReplacer {
|
||||
getReplacement(codeNode: Element): React.ReactElement | undefined {
|
||||
if (
|
||||
codeNode.name !== 'code' ||
|
||||
!codeNode.attribs ||
|
||||
|
@ -22,7 +25,7 @@ export class VegaReplacer implements ComponentReplacer {
|
|||
return
|
||||
}
|
||||
|
||||
const code = codeNode.children[0].data as string
|
||||
const code = ComponentReplacer.extractTextChildContent(codeNode)
|
||||
|
||||
return <VegaChart code={code} />
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { DomElement } from 'domhandler'
|
||||
import { Element } from 'domhandler'
|
||||
import MarkdownIt from 'markdown-it'
|
||||
import markdownItRegex from 'markdown-it-regex'
|
||||
import React from 'react'
|
||||
|
@ -14,13 +14,16 @@ import { replaceLegacyVimeoShortCode } from './replace-legacy-vimeo-short-code'
|
|||
import { replaceVimeoLink } from './replace-vimeo-link'
|
||||
import { VimeoFrame } from './vimeo-frame'
|
||||
|
||||
/**
|
||||
* Detects 'app-vimeo' tags and renders them as vimeo embedding.
|
||||
*/
|
||||
export class VimeoReplacer extends ComponentReplacer {
|
||||
public static readonly markdownItPlugin: MarkdownIt.PluginSimple = (markdownIt) => {
|
||||
markdownItRegex(markdownIt, replaceVimeoLink)
|
||||
markdownItRegex(markdownIt, replaceLegacyVimeoShortCode)
|
||||
}
|
||||
|
||||
public getReplacement(node: DomElement): React.ReactElement | undefined {
|
||||
public getReplacement(node: Element): React.ReactElement | undefined {
|
||||
const attributes = getAttributesFromHedgeDocTag(node, 'vimeo')
|
||||
if (attributes && attributes.id) {
|
||||
const videoId = attributes.id
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { DomElement } from 'domhandler'
|
||||
import { Element } from 'domhandler'
|
||||
import MarkdownIt from 'markdown-it'
|
||||
import markdownItRegex from 'markdown-it-regex'
|
||||
import React from 'react'
|
||||
|
@ -14,13 +14,16 @@ import { replaceLegacyYoutubeShortCode } from './replace-legacy-youtube-short-co
|
|||
import { replaceYouTubeLink } from './replace-youtube-link'
|
||||
import { YouTubeFrame } from './youtube-frame'
|
||||
|
||||
/**
|
||||
* Detects 'app-youtube' tags and renders them as youtube embedding.
|
||||
*/
|
||||
export class YoutubeReplacer extends ComponentReplacer {
|
||||
public static readonly markdownItPlugin: MarkdownIt.PluginSimple = (markdownIt) => {
|
||||
markdownItRegex(markdownIt, replaceYouTubeLink)
|
||||
markdownItRegex(markdownIt, replaceLegacyYoutubeShortCode)
|
||||
}
|
||||
|
||||
public getReplacement(node: DomElement): React.ReactElement | undefined {
|
||||
public getReplacement(node: Element): React.ReactElement | undefined {
|
||||
const attributes = getAttributesFromHedgeDocTag(node, 'youtube')
|
||||
if (attributes && attributes.id) {
|
||||
const videoId = attributes.id
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue