mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-14 15:14:56 -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
|
@ -144,7 +144,7 @@ describe('Autocompletion', () => {
|
||||||
.should('have.text', '# ')
|
.should('have.text', '# ')
|
||||||
cy.getMarkdownBody()
|
cy.getMarkdownBody()
|
||||||
.find('h1 ')
|
.find('h1 ')
|
||||||
.should('have.text', ' ')
|
.should('have.text', '\n ')
|
||||||
})
|
})
|
||||||
it('via doubleclick', () => {
|
it('via doubleclick', () => {
|
||||||
cy.codemirrorFill('#')
|
cy.codemirrorFill('#')
|
||||||
|
@ -157,7 +157,7 @@ describe('Autocompletion', () => {
|
||||||
.should('have.text', '# ')
|
.should('have.text', '# ')
|
||||||
cy.getMarkdownBody()
|
cy.getMarkdownBody()
|
||||||
.find('h1')
|
.find('h1')
|
||||||
.should('have.text', ' ')
|
.should('have.text', '\n ')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
describe('emojis', () => {
|
describe('emojis', () => {
|
||||||
|
|
||||||
const HEDGEHOG_UNICODE_CHARACTER = '🦔'
|
const HEDGEHOG_UNICODE_CHARACTER = '\n🦔\n'
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.visitTestEditor()
|
cy.visitTestEditor()
|
||||||
|
|
|
@ -13,7 +13,8 @@ declare namespace Cypress {
|
||||||
}
|
}
|
||||||
|
|
||||||
Cypress.Commands.add('getMarkdownRenderer', () => {
|
Cypress.Commands.add('getMarkdownRenderer', () => {
|
||||||
return cy.get(`iframe[data-cy="documentIframe"]`)
|
return cy
|
||||||
|
.get(`iframe[data-cy="documentIframe"]`)
|
||||||
.should('be.visible')
|
.should('be.visible')
|
||||||
.its('0.contentDocument')
|
.its('0.contentDocument')
|
||||||
.should('exist')
|
.should('exist')
|
||||||
|
@ -23,6 +24,5 @@ Cypress.Commands.add('getMarkdownRenderer', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add('getMarkdownBody', () => {
|
Cypress.Commands.add('getMarkdownBody', () => {
|
||||||
return cy.getMarkdownRenderer()
|
return cy.getMarkdownRenderer().find('.markdown-body')
|
||||||
.find('.markdown-body')
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@craco/craco": "6.1.2",
|
"@craco/craco": "6.1.2",
|
||||||
"@fontsource/source-sans-pro": "4.4.5",
|
"@fontsource/source-sans-pro": "4.4.5",
|
||||||
|
"@hedgedoc/html-to-react": "1.0.0",
|
||||||
"@hedgedoc/markdown-it-task-lists": "1.0.0",
|
"@hedgedoc/markdown-it-task-lists": "1.0.0",
|
||||||
"@matejmazur/react-katex": "3.1.3",
|
"@matejmazur/react-katex": "3.1.3",
|
||||||
"@testing-library/jest-dom": "5.14.1",
|
"@testing-library/jest-dom": "5.14.1",
|
||||||
|
@ -13,7 +14,6 @@
|
||||||
"@types/codemirror": "5.60.0",
|
"@types/codemirror": "5.60.0",
|
||||||
"@types/d3-graphviz": "2.6.6",
|
"@types/d3-graphviz": "2.6.6",
|
||||||
"@types/diff": "5.0.0",
|
"@types/diff": "5.0.0",
|
||||||
"@types/domhandler": "2.4.1",
|
|
||||||
"@types/jest": "26.0.23",
|
"@types/jest": "26.0.23",
|
||||||
"@types/js-yaml": "4.0.1",
|
"@types/js-yaml": "4.0.1",
|
||||||
"@types/luxon": "1.27.0",
|
"@types/luxon": "1.27.0",
|
||||||
|
@ -26,7 +26,6 @@
|
||||||
"@types/react": "17.0.11",
|
"@types/react": "17.0.11",
|
||||||
"@types/react-bootstrap-typeahead": "5.1.5",
|
"@types/react-bootstrap-typeahead": "5.1.5",
|
||||||
"@types/react-dom": "17.0.7",
|
"@types/react-dom": "17.0.7",
|
||||||
"@types/react-html-parser": "2.0.1",
|
|
||||||
"@types/react-router": "5.1.15",
|
"@types/react-router": "5.1.15",
|
||||||
"@types/react-router-bootstrap": "0.24.5",
|
"@types/react-router-bootstrap": "0.24.5",
|
||||||
"@types/react-router-dom": "5.1.7",
|
"@types/react-router-dom": "5.1.7",
|
||||||
|
@ -87,7 +86,6 @@
|
||||||
"react-codemirror2": "7.2.1",
|
"react-codemirror2": "7.2.1",
|
||||||
"react-diff-viewer": "3.1.1",
|
"react-diff-viewer": "3.1.1",
|
||||||
"react-dom": "17.0.2",
|
"react-dom": "17.0.2",
|
||||||
"react-html-parser": "2.0.2",
|
|
||||||
"react-i18next": "11.11.0",
|
"react-i18next": "11.11.0",
|
||||||
"react-redux": "7.2.4",
|
"react-redux": "7.2.4",
|
||||||
"react-router": "5.2.0",
|
"react-router": "5.2.0",
|
||||||
|
|
|
@ -90,11 +90,15 @@ export const BasicMarkdownRenderer: React.FC<BasicMarkdownRendererProps & Additi
|
||||||
}, [onAfterRendering])
|
}, [onAfterRendering])
|
||||||
|
|
||||||
const baseReplacers = useComponentReplacers(onTaskCheckedChange, onImageClick, baseUrl)
|
const baseReplacers = useComponentReplacers(onTaskCheckedChange, onImageClick, baseUrl)
|
||||||
|
const replacers = useCallback(
|
||||||
|
() => baseReplacers().concat(additionalReplacers ? additionalReplacers() : []),
|
||||||
|
[additionalReplacers, baseReplacers]
|
||||||
|
)
|
||||||
|
|
||||||
const markdownReactDom = useConvertMarkdownToReactDom(
|
const markdownReactDom = useConvertMarkdownToReactDom(
|
||||||
trimmedContent,
|
trimmedContent,
|
||||||
markdownIt,
|
markdownIt,
|
||||||
baseReplacers,
|
replacers,
|
||||||
additionalReplacers,
|
|
||||||
clearFrontmatter,
|
clearFrontmatter,
|
||||||
checkYamlErrorState
|
checkYamlErrorState
|
||||||
)
|
)
|
||||||
|
|
|
@ -26,6 +26,15 @@ import { VegaReplacer } from '../replace-components/vega-lite/vega-replacer'
|
||||||
import { VimeoReplacer } from '../replace-components/vimeo/vimeo-replacer'
|
import { VimeoReplacer } from '../replace-components/vimeo/vimeo-replacer'
|
||||||
import { YoutubeReplacer } from '../replace-components/youtube/youtube-replacer'
|
import { YoutubeReplacer } from '../replace-components/youtube/youtube-replacer'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a function that creates a list of {@link ComponentReplacer component replacer} instances.
|
||||||
|
*
|
||||||
|
* @param onTaskCheckedChange A callback that gets executed if a task checkbox gets clicked
|
||||||
|
* @param onImageClick A callback that should be executed if an image gets clicked
|
||||||
|
* @param baseUrl The base url for relative links
|
||||||
|
*
|
||||||
|
* @return the created list
|
||||||
|
*/
|
||||||
export const useComponentReplacers = (
|
export const useComponentReplacers = (
|
||||||
onTaskCheckedChange?: TaskCheckedChangeHandler,
|
onTaskCheckedChange?: TaskCheckedChangeHandler,
|
||||||
onImageClick?: ImageClickHandler,
|
onImageClick?: ImageClickHandler,
|
||||||
|
|
|
@ -5,21 +5,30 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import MarkdownIt from 'markdown-it/lib'
|
import MarkdownIt from 'markdown-it/lib'
|
||||||
import { ReactElement, useMemo, useRef } from 'react'
|
import { useMemo, useRef } from 'react'
|
||||||
import ReactHtmlParser from 'react-html-parser'
|
import { ComponentReplacer, ValidReactDomElement } from '../replace-components/ComponentReplacer'
|
||||||
import { ComponentReplacer } from '../replace-components/ComponentReplacer'
|
|
||||||
import { LineKeys } from '../types'
|
import { LineKeys } from '../types'
|
||||||
import { buildTransformer } from '../utils/html-react-transformer'
|
import { buildTransformer } from '../utils/html-react-transformer'
|
||||||
import { calculateNewLineNumberMapping } from '../utils/line-number-mapping'
|
import { calculateNewLineNumberMapping } from '../utils/line-number-mapping'
|
||||||
|
import convertHtmlToReact from '@hedgedoc/html-to-react'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders markdown code into react elements
|
||||||
|
*
|
||||||
|
* @param markdownCode The markdown code that should be rendered
|
||||||
|
* @param markdownIt The configured {@link MarkdownIt markdown it} instance that should render the code
|
||||||
|
* @param replacers A function that provides a list of {@link ComponentReplacer component replacers}
|
||||||
|
* @param onBeforeRendering A callback that gets executed before the rendering
|
||||||
|
* @param onAfterRendering A callback that gets executed after the rendering
|
||||||
|
* @return The React DOM that represents the rendered markdown code
|
||||||
|
*/
|
||||||
export const useConvertMarkdownToReactDom = (
|
export const useConvertMarkdownToReactDom = (
|
||||||
markdownCode: string,
|
markdownCode: string,
|
||||||
markdownIt: MarkdownIt,
|
markdownIt: MarkdownIt,
|
||||||
baseReplacers: () => ComponentReplacer[],
|
replacers: () => ComponentReplacer[],
|
||||||
additionalReplacers?: () => ComponentReplacer[],
|
|
||||||
onBeforeRendering?: () => void,
|
onBeforeRendering?: () => void,
|
||||||
onAfterRendering?: () => void
|
onAfterRendering?: () => void
|
||||||
): ReactElement[] => {
|
): ValidReactDomElement[] => {
|
||||||
const oldMarkdownLineKeys = useRef<LineKeys[]>()
|
const oldMarkdownLineKeys = useRef<LineKeys[]>()
|
||||||
const lastUsedLineId = useRef<number>(0)
|
const lastUsedLineId = useRef<number>(0)
|
||||||
|
|
||||||
|
@ -37,12 +46,12 @@ export const useConvertMarkdownToReactDom = (
|
||||||
oldMarkdownLineKeys.current = newLines
|
oldMarkdownLineKeys.current = newLines
|
||||||
lastUsedLineId.current = newLastUsedLineId
|
lastUsedLineId.current = newLastUsedLineId
|
||||||
|
|
||||||
const replacers = baseReplacers().concat(additionalReplacers ? additionalReplacers() : [])
|
const currentReplacers = replacers()
|
||||||
const transformer = replacers.length > 0 ? buildTransformer(newLines, replacers) : undefined
|
const transformer = currentReplacers.length > 0 ? buildTransformer(newLines, currentReplacers) : undefined
|
||||||
const rendering = ReactHtmlParser(html, { transform: transformer })
|
const rendering = convertHtmlToReact(html, { transform: transformer })
|
||||||
if (onAfterRendering) {
|
if (onAfterRendering) {
|
||||||
onAfterRendering()
|
onAfterRendering()
|
||||||
}
|
}
|
||||||
return rendering
|
return rendering
|
||||||
}, [onBeforeRendering, markdownIt, markdownCode, baseReplacers, additionalReplacers, onAfterRendering])
|
}, [onBeforeRendering, markdownIt, markdownCode, replacers, onAfterRendering])
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,20 +4,46 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DomElement } from 'domhandler'
|
import { Element, isText, NodeWithChildren } from 'domhandler'
|
||||||
import MarkdownIt from 'markdown-it'
|
import MarkdownIt from 'markdown-it'
|
||||||
import { ReactElement } from 'react'
|
import { ReactElement } from 'react'
|
||||||
|
|
||||||
export type SubNodeTransform = (node: DomElement, subIndex: number) => ReactElement | void | null
|
export type 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
|
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 {
|
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(
|
public abstract getReplacement(
|
||||||
node: DomElement,
|
node: Element,
|
||||||
subNodeTransform: SubNodeTransform,
|
subNodeTransform: SubNodeTransform,
|
||||||
nativeRenderer: NativeRenderer
|
nativeRenderer: NativeRenderer
|
||||||
): ReactElement | null | undefined
|
): ValidReactDomElement | undefined
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,16 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DomElement } from 'domhandler'
|
import { Element } from 'domhandler'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { ComponentReplacer } from '../ComponentReplacer'
|
import { ComponentReplacer } from '../ComponentReplacer'
|
||||||
import { AbcFrame } from './abc-frame'
|
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 (
|
if (
|
||||||
codeNode.name !== 'code' ||
|
codeNode.name !== 'code' ||
|
||||||
!codeNode.attribs ||
|
!codeNode.attribs ||
|
||||||
|
@ -22,7 +25,7 @@ export class AbcReplacer implements ComponentReplacer {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const code = codeNode.children[0].data as string
|
const code = ComponentReplacer.extractTextChildContent(codeNode)
|
||||||
|
|
||||||
return <AbcFrame code={code} />
|
return <AbcFrame code={code} />
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DomElement } from 'domhandler'
|
import { Element } from 'domhandler'
|
||||||
import MarkdownIt from 'markdown-it'
|
import MarkdownIt from 'markdown-it'
|
||||||
import markdownItRegex from 'markdown-it-regex'
|
import markdownItRegex from 'markdown-it-regex'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
@ -13,12 +13,15 @@ import { getAttributesFromHedgeDocTag } from '../utils'
|
||||||
import { AsciinemaFrame } from './asciinema-frame'
|
import { AsciinemaFrame } from './asciinema-frame'
|
||||||
import { replaceAsciinemaLink } from './replace-asciinema-link'
|
import { replaceAsciinemaLink } from './replace-asciinema-link'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detects code blocks with "asciinema" as language and renders them Asciinema frame
|
||||||
|
*/
|
||||||
export class AsciinemaReplacer extends ComponentReplacer {
|
export class AsciinemaReplacer extends ComponentReplacer {
|
||||||
public static readonly markdownItPlugin: MarkdownIt.PluginSimple = (markdownIt) => {
|
public static readonly markdownItPlugin: MarkdownIt.PluginSimple = (markdownIt) => {
|
||||||
markdownItRegex(markdownIt, replaceAsciinemaLink)
|
markdownItRegex(markdownIt, replaceAsciinemaLink)
|
||||||
}
|
}
|
||||||
|
|
||||||
public getReplacement(node: DomElement): React.ReactElement | undefined {
|
public getReplacement(node: Element): React.ReactElement | undefined {
|
||||||
const attributes = getAttributesFromHedgeDocTag(node, 'asciinema')
|
const attributes = getAttributesFromHedgeDocTag(node, 'asciinema')
|
||||||
if (attributes && attributes.id) {
|
if (attributes && attributes.id) {
|
||||||
const asciinemaId = attributes.id
|
const asciinemaId = attributes.id
|
||||||
|
|
|
@ -4,45 +4,60 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DomElement } from 'domhandler'
|
import { Element, isTag } from 'domhandler'
|
||||||
import { ReactElement } from 'react'
|
import { ComponentReplacer, NativeRenderer, SubNodeTransform, ValidReactDomElement } from '../ComponentReplacer'
|
||||||
import { ComponentReplacer, NativeRenderer, SubNodeTransform } 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']) {
|
if (!node || !node.attribs || !node.attribs.class || !node.attribs['data-color']) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return node.name === 'span' && node.attribs.class === 'quote-extra'
|
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) => {
|
return nodes.find((child) => {
|
||||||
if (child.name !== 'p' || !child.children || child.children.length < 1) {
|
if (child.name !== 'p' || !child.children || child.children.length < 1) {
|
||||||
return false
|
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 {
|
export class ColoredBlockquoteReplacer extends ComponentReplacer {
|
||||||
public getReplacement(
|
public getReplacement(
|
||||||
node: DomElement,
|
node: Element,
|
||||||
subNodeTransform: SubNodeTransform,
|
subNodeTransform: SubNodeTransform,
|
||||||
nativeRenderer: NativeRenderer
|
nativeRenderer: NativeRenderer
|
||||||
): ReactElement | undefined {
|
): ValidReactDomElement | undefined {
|
||||||
if (node.name !== 'blockquote' || !node.children || node.children.length < 1) {
|
if (node.name !== 'blockquote' || !node.children || node.children.length < 1) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const paragraph = findQuoteOptionsParent(node.children)
|
const paragraph = findBlockquoteColorParentElement(node.children.filter(isTag))
|
||||||
if (!paragraph) {
|
if (!paragraph) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const childElements = paragraph.children || []
|
const childElements = paragraph.children || []
|
||||||
const optionsTag = childElements.find(isColorExtraElement)
|
const optionsTag = childElements.filter(isTag).find(isBlockquoteColorDefinition)
|
||||||
if (!optionsTag) {
|
if (!optionsTag) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
paragraph.children = childElements.filter((elem) => !isColorExtraElement(elem))
|
paragraph.children = childElements.filter((elem) => !isTag(elem) || !isBlockquoteColorDefinition(elem))
|
||||||
const attributes = optionsTag.attribs
|
const attributes = optionsTag.attribs
|
||||||
if (!attributes || !attributes['data-color']) {
|
if (!attributes || !attributes['data-color']) {
|
||||||
return
|
return
|
||||||
|
|
|
@ -4,13 +4,16 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DomElement } from 'domhandler'
|
import { Element } from 'domhandler'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { ComponentReplacer } from '../ComponentReplacer'
|
import { ComponentReplacer } from '../ComponentReplacer'
|
||||||
import { CsvTable } from './csv-table'
|
import { CsvTable } from './csv-table'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detects code blocks with "csv" as language and renders them as table.
|
||||||
|
*/
|
||||||
export class CsvReplacer extends ComponentReplacer {
|
export class CsvReplacer extends ComponentReplacer {
|
||||||
public getReplacement(codeNode: DomElement): React.ReactElement | undefined {
|
public getReplacement(codeNode: Element): React.ReactElement | undefined {
|
||||||
if (
|
if (
|
||||||
codeNode.name !== 'code' ||
|
codeNode.name !== 'code' ||
|
||||||
!codeNode.attribs ||
|
!codeNode.attribs ||
|
||||||
|
@ -22,7 +25,7 @@ export class CsvReplacer extends ComponentReplacer {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const code = codeNode.children[0].data as string
|
const code = ComponentReplacer.extractTextChildContent(codeNode)
|
||||||
|
|
||||||
const extraData = codeNode.attribs['data-extra']
|
const extraData = codeNode.attribs['data-extra']
|
||||||
const extraRegex = /\s*(delimiter=([^\s]*))?\s*(header)?/
|
const extraRegex = /\s*(delimiter=([^\s]*))?\s*(header)?/
|
||||||
|
|
|
@ -4,13 +4,16 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DomElement } from 'domhandler'
|
import { Element } from 'domhandler'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { ComponentReplacer } from '../ComponentReplacer'
|
import { ComponentReplacer } from '../ComponentReplacer'
|
||||||
import { FlowChart } from './flowchart/flowchart'
|
import { FlowChart } from './flowchart/flowchart'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detects code blocks with "flow" as language and renders them as flow chart.
|
||||||
|
*/
|
||||||
export class FlowchartReplacer extends ComponentReplacer {
|
export class FlowchartReplacer extends ComponentReplacer {
|
||||||
public getReplacement(codeNode: DomElement): React.ReactElement | undefined {
|
public getReplacement(codeNode: Element): React.ReactElement | undefined {
|
||||||
if (
|
if (
|
||||||
codeNode.name !== 'code' ||
|
codeNode.name !== 'code' ||
|
||||||
!codeNode.attribs ||
|
!codeNode.attribs ||
|
||||||
|
@ -22,7 +25,7 @@ export class FlowchartReplacer extends ComponentReplacer {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const code = codeNode.children[0].data as string
|
const code = ComponentReplacer.extractTextChildContent(codeNode)
|
||||||
|
|
||||||
return <FlowChart code={code} />
|
return <FlowChart code={code} />
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DomElement } from 'domhandler'
|
import { Element } from 'domhandler'
|
||||||
import MarkdownIt from 'markdown-it'
|
import MarkdownIt from 'markdown-it'
|
||||||
import markdownItRegex from 'markdown-it-regex'
|
import markdownItRegex from 'markdown-it-regex'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
@ -16,13 +16,16 @@ import preview from './gist-preview.png'
|
||||||
import { replaceGistLink } from './replace-gist-link'
|
import { replaceGistLink } from './replace-gist-link'
|
||||||
import { replaceLegacyGistShortCode } from './replace-legacy-gist-short-code'
|
import { replaceLegacyGistShortCode } from './replace-legacy-gist-short-code'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detects "app-gist" tags and renders them as gist frames.
|
||||||
|
*/
|
||||||
export class GistReplacer extends ComponentReplacer {
|
export class GistReplacer extends ComponentReplacer {
|
||||||
public static readonly markdownItPlugin: MarkdownIt.PluginSimple = (markdownIt) => {
|
public static readonly markdownItPlugin: MarkdownIt.PluginSimple = (markdownIt) => {
|
||||||
markdownItRegex(markdownIt, replaceGistLink)
|
markdownItRegex(markdownIt, replaceGistLink)
|
||||||
markdownItRegex(markdownIt, replaceLegacyGistShortCode)
|
markdownItRegex(markdownIt, replaceLegacyGistShortCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
public getReplacement(node: DomElement): React.ReactElement | undefined {
|
public getReplacement(node: Element): React.ReactElement | undefined {
|
||||||
const attributes = getAttributesFromHedgeDocTag(node, 'gist')
|
const attributes = getAttributesFromHedgeDocTag(node, 'gist')
|
||||||
if (attributes && attributes.id) {
|
if (attributes && attributes.id) {
|
||||||
const gistId = attributes.id
|
const gistId = attributes.id
|
||||||
|
|
|
@ -4,13 +4,16 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DomElement } from 'domhandler'
|
import { Element } from 'domhandler'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { ComponentReplacer } from '../ComponentReplacer'
|
import { ComponentReplacer } from '../ComponentReplacer'
|
||||||
import { GraphvizFrame } from './graphviz-frame'
|
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 (
|
if (
|
||||||
codeNode.name !== 'code' ||
|
codeNode.name !== 'code' ||
|
||||||
!codeNode.attribs ||
|
!codeNode.attribs ||
|
||||||
|
@ -22,7 +25,7 @@ export class GraphvizReplacer implements ComponentReplacer {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const code = codeNode.children[0].data as string
|
const code = ComponentReplacer.extractTextChildContent(codeNode)
|
||||||
|
|
||||||
return <GraphvizFrame code={code} />
|
return <GraphvizFrame code={code} />
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { Fragment, ReactElement, useEffect, useState } from 'react'
|
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 { CopyToClipboardButton } from '../../../../common/copyable/copy-to-clipboard-button/copy-to-clipboard-button'
|
||||||
import '../../../utils/button-inside.scss'
|
import '../../../utils/button-inside.scss'
|
||||||
import './highlighted-code.scss'
|
import './highlighted-code.scss'
|
||||||
|
@ -29,11 +29,11 @@ const escapeHtml = (unsafe: string): string => {
|
||||||
.replaceAll(/'/g, ''')
|
.replaceAll(/'/g, ''')
|
||||||
}
|
}
|
||||||
|
|
||||||
const replaceCode = (code: string): ReactElement[][] => {
|
const replaceCode = (code: string): (ReactElement | null | string)[][] => {
|
||||||
return code
|
return code
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.filter((line) => !!line)
|
.filter((line) => !!line)
|
||||||
.map((line) => ReactHtmlParser(line))
|
.map((line) => convertHtmlToReact(line, {}))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HighlightedCode: React.FC<HighlightedCodeProps> = ({ code, language, startLineNumber, wrapLines }) => {
|
export const HighlightedCode: React.FC<HighlightedCodeProps> = ({ code, language, startLineNumber, wrapLines }) => {
|
||||||
|
|
|
@ -4,15 +4,18 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DomElement } from 'domhandler'
|
import { Element } from 'domhandler'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { ComponentReplacer } from '../ComponentReplacer'
|
import { ComponentReplacer } from '../ComponentReplacer'
|
||||||
import { HighlightedCode } from './highlighted-code/highlighted-code'
|
import { HighlightedCode } from './highlighted-code/highlighted-code'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detects code blocks and renders them as highlighted code blocks
|
||||||
|
*/
|
||||||
export class HighlightedCodeReplacer extends ComponentReplacer {
|
export class HighlightedCodeReplacer extends ComponentReplacer {
|
||||||
private lastLineNumber = 0
|
private lastLineNumber = 0
|
||||||
|
|
||||||
public getReplacement(codeNode: DomElement): React.ReactElement | undefined {
|
public getReplacement(codeNode: Element): React.ReactElement | undefined {
|
||||||
if (
|
if (
|
||||||
codeNode.name !== 'code' ||
|
codeNode.name !== 'code' ||
|
||||||
!codeNode.attribs ||
|
!codeNode.attribs ||
|
||||||
|
@ -39,7 +42,7 @@ export class HighlightedCodeReplacer extends ComponentReplacer {
|
||||||
|
|
||||||
const startLineNumber =
|
const startLineNumber =
|
||||||
startLineNumberAttribute === '+' ? this.lastLineNumber : parseInt(startLineNumberAttribute) || 1
|
startLineNumberAttribute === '+' ? this.lastLineNumber : parseInt(startLineNumberAttribute) || 1
|
||||||
const code = codeNode.children[0].data as string
|
const code = ComponentReplacer.extractTextChildContent(codeNode)
|
||||||
|
|
||||||
if (showLineNumbers) {
|
if (showLineNumbers) {
|
||||||
this.lastLineNumber = startLineNumber + code.split('\n').filter((line) => !!line).length
|
this.lastLineNumber = startLineNumber + code.split('\n').filter((line) => !!line).length
|
||||||
|
|
|
@ -4,13 +4,16 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DomElement } from 'domhandler'
|
import { Element } from 'domhandler'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { ComponentReplacer } from '../ComponentReplacer'
|
import { ComponentReplacer } from '../ComponentReplacer'
|
||||||
import { ProxyImageFrame } from './proxy-image-frame'
|
import { ProxyImageFrame } from './proxy-image-frame'
|
||||||
|
|
||||||
export type ImageClickHandler = (event: React.MouseEvent<HTMLImageElement, MouseEvent>) => void
|
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 {
|
export class ImageReplacer extends ComponentReplacer {
|
||||||
private readonly clickHandler?: ImageClickHandler
|
private readonly clickHandler?: ImageClickHandler
|
||||||
|
|
||||||
|
@ -19,7 +22,7 @@ export class ImageReplacer extends ComponentReplacer {
|
||||||
this.clickHandler = clickHandler
|
this.clickHandler = clickHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
public getReplacement(node: DomElement): React.ReactElement | undefined {
|
public getReplacement(node: Element): React.ReactElement | undefined {
|
||||||
if (node.name === 'img' && node.attribs) {
|
if (node.name === 'img' && node.attribs) {
|
||||||
return (
|
return (
|
||||||
<ProxyImageFrame
|
<ProxyImageFrame
|
||||||
|
|
|
@ -4,28 +4,43 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DomElement } from 'domhandler'
|
import { Element, isTag } from 'domhandler'
|
||||||
import MarkdownIt from 'markdown-it'
|
import MarkdownIt from 'markdown-it'
|
||||||
import mathJax from 'markdown-it-mathjax'
|
import mathJax from 'markdown-it-mathjax'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { ComponentReplacer } from '../ComponentReplacer'
|
import { ComponentReplacer } from '../ComponentReplacer'
|
||||||
import './katex.scss'
|
import './katex.scss'
|
||||||
|
|
||||||
const getNodeIfKatexBlock = (node: DomElement): DomElement | undefined => {
|
/**
|
||||||
|
* 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) {
|
if (node.name !== 'p' || !node.children || node.children.length === 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return node.children.find((subnode) => {
|
return node.children.filter(isTag).find((subnode) => {
|
||||||
return subnode.name === 'app-katex' && subnode.attribs?.inline === undefined
|
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
|
return node.name === 'app-katex' && node.attribs?.inline !== undefined ? node : undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const KaTeX = React.lazy(() => import(/* webpackChunkName: "katex" */ '@matejmazur/react-katex'))
|
const KaTeX = React.lazy(() => import(/* webpackChunkName: "katex" */ '@matejmazur/react-katex'))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detects LaTeX syntax and renders it with KaTeX.
|
||||||
|
*/
|
||||||
export class KatexReplacer extends ComponentReplacer {
|
export class KatexReplacer extends ComponentReplacer {
|
||||||
public static readonly markdownItPlugin: MarkdownIt.PluginSimple = mathJax({
|
public static readonly markdownItPlugin: MarkdownIt.PluginSimple = mathJax({
|
||||||
beforeMath: '<app-katex>',
|
beforeMath: '<app-katex>',
|
||||||
|
@ -36,10 +51,10 @@ export class KatexReplacer extends ComponentReplacer {
|
||||||
afterDisplayMath: '</app-katex>'
|
afterDisplayMath: '</app-katex>'
|
||||||
})
|
})
|
||||||
|
|
||||||
public getReplacement(node: DomElement): React.ReactElement | undefined {
|
public getReplacement(node: Element): React.ReactElement | undefined {
|
||||||
const katex = getNodeIfKatexBlock(node) || getNodeIfInlineKatex(node)
|
const katex = getNodeIfKatexBlock(node) || getNodeIfInlineKatex(node)
|
||||||
if (katex?.children && katex.children[0]) {
|
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
|
const isInline = katex.attribs?.inline !== undefined
|
||||||
return <KaTeX block={!isInline} math={mathJaxContent} errorColor={'#cc0000'} />
|
return <KaTeX block={!isInline} math={mathJaxContent} errorColor={'#cc0000'} />
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,14 @@
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DomElement } from 'domhandler'
|
import { Element } from 'domhandler'
|
||||||
import { ComponentReplacer } from '../ComponentReplacer'
|
import { ComponentReplacer } from '../ComponentReplacer'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detects line markers and suppresses them in the resulting DOM.
|
||||||
|
*/
|
||||||
export class LinemarkerReplacer extends ComponentReplacer {
|
export class LinemarkerReplacer extends ComponentReplacer {
|
||||||
public getReplacement(codeNode: DomElement): null | undefined {
|
public getReplacement(codeNode: Element): null | undefined {
|
||||||
return codeNode.name === 'app-linemarker' ? null : undefined
|
return codeNode.name === 'app-linemarker' ? null : undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { DomElement } from 'domhandler'
|
import { Element } from 'domhandler'
|
||||||
import React, { ReactElement } from 'react'
|
import React from 'react'
|
||||||
import { ComponentReplacer, NativeRenderer, SubNodeTransform } from '../ComponentReplacer'
|
import { ComponentReplacer, NativeRenderer, SubNodeTransform, ValidReactDomElement } from '../ComponentReplacer'
|
||||||
|
|
||||||
export const createJumpToMarkClickEventHandler = (id: string) => {
|
export const createJumpToMarkClickEventHandler = (id: string) => {
|
||||||
return (event: React.MouseEvent<HTMLElement, MouseEvent>): void => {
|
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 {
|
export class LinkReplacer extends ComponentReplacer {
|
||||||
constructor(private baseUrl?: string) {
|
constructor(private baseUrl?: string) {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
public getReplacement(
|
public getReplacement(
|
||||||
node: DomElement,
|
node: Element,
|
||||||
subNodeTransform: SubNodeTransform,
|
subNodeTransform: SubNodeTransform,
|
||||||
nativeRenderer: NativeRenderer
|
nativeRenderer: NativeRenderer
|
||||||
): ReactElement | null | undefined {
|
): ValidReactDomElement | undefined {
|
||||||
if (node.name !== 'a' || !node.attribs || !node.attribs.href) {
|
if (node.name !== 'a' || !node.attribs || !node.attribs.href) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,16 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DomElement } from 'domhandler'
|
import { Element } from 'domhandler'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { ComponentReplacer } from '../ComponentReplacer'
|
import { ComponentReplacer } from '../ComponentReplacer'
|
||||||
import { MarkmapFrame } from './markmap-frame'
|
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 (
|
if (
|
||||||
codeNode.name !== 'code' ||
|
codeNode.name !== 'code' ||
|
||||||
!codeNode.attribs ||
|
!codeNode.attribs ||
|
||||||
|
@ -22,7 +25,7 @@ export class MarkmapReplacer implements ComponentReplacer {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const code = codeNode.children[0].data as string
|
const code = ComponentReplacer.extractTextChildContent(codeNode)
|
||||||
|
|
||||||
return <MarkmapFrame code={code} />
|
return <MarkmapFrame code={code} />
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,16 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DomElement } from 'domhandler'
|
import { Element } from 'domhandler'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { ComponentReplacer } from '../ComponentReplacer'
|
import { ComponentReplacer } from '../ComponentReplacer'
|
||||||
import { MermaidChart } from './mermaid-chart'
|
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 (
|
if (
|
||||||
codeNode.name !== 'code' ||
|
codeNode.name !== 'code' ||
|
||||||
!codeNode.attribs ||
|
!codeNode.attribs ||
|
||||||
|
@ -22,7 +25,7 @@ export class MermaidReplacer implements ComponentReplacer {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const code = codeNode.children[0].data as string
|
const code = ComponentReplacer.extractTextChildContent(codeNode)
|
||||||
|
|
||||||
return <MermaidChart code={code} />
|
return <MermaidChart code={code} />
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,14 +4,18 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DomElement } from 'domhandler'
|
import { Element } from 'domhandler'
|
||||||
import React, { Fragment } from 'react'
|
import React, { Fragment } from 'react'
|
||||||
import { ComponentReplacer } from '../ComponentReplacer'
|
import { ComponentReplacer } from '../ComponentReplacer'
|
||||||
import { MermaidChart } from '../mermaid/mermaid-chart'
|
import { MermaidChart } from '../mermaid/mermaid-chart'
|
||||||
import { DeprecationWarning } from './deprecation-warning'
|
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 (
|
if (
|
||||||
codeNode.name !== 'code' ||
|
codeNode.name !== 'code' ||
|
||||||
!codeNode.attribs ||
|
!codeNode.attribs ||
|
||||||
|
@ -23,7 +27,7 @@ export class SequenceDiagramReplacer implements ComponentReplacer {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const code = codeNode.children[0].data as string
|
const code = ComponentReplacer.extractTextChildContent(codeNode)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
|
|
|
@ -4,12 +4,15 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DomElement } from 'domhandler'
|
import { Element } from 'domhandler'
|
||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import { ComponentReplacer } from '../ComponentReplacer'
|
import { ComponentReplacer } from '../ComponentReplacer'
|
||||||
|
|
||||||
export type TaskCheckedChangeHandler = (lineInMarkdown: number, checked: boolean) => void
|
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 {
|
export class TaskListReplacer extends ComponentReplacer {
|
||||||
onTaskCheckedChange?: (lineInMarkdown: number, checked: boolean) => void
|
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') {
|
if (node.attribs?.class !== 'task-list-item-checkbox') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,9 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DomElement } from 'domhandler'
|
import { Element } from 'domhandler'
|
||||||
|
|
||||||
export const getAttributesFromHedgeDocTag = (
|
export const getAttributesFromHedgeDocTag = (node: Element, tagName: string): { [s: string]: string } | undefined => {
|
||||||
node: DomElement,
|
|
||||||
tagName: string
|
|
||||||
): { [s: string]: string } | undefined => {
|
|
||||||
if (node.name !== `app-${tagName}` || !node.attribs) {
|
if (node.name !== `app-${tagName}` || !node.attribs) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,16 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DomElement } from 'domhandler'
|
import { Element } from 'domhandler'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { ComponentReplacer } from '../ComponentReplacer'
|
import { ComponentReplacer } from '../ComponentReplacer'
|
||||||
import { VegaChart } from './vega-chart'
|
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 (
|
if (
|
||||||
codeNode.name !== 'code' ||
|
codeNode.name !== 'code' ||
|
||||||
!codeNode.attribs ||
|
!codeNode.attribs ||
|
||||||
|
@ -22,7 +25,7 @@ export class VegaReplacer implements ComponentReplacer {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const code = codeNode.children[0].data as string
|
const code = ComponentReplacer.extractTextChildContent(codeNode)
|
||||||
|
|
||||||
return <VegaChart code={code} />
|
return <VegaChart code={code} />
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DomElement } from 'domhandler'
|
import { Element } from 'domhandler'
|
||||||
import MarkdownIt from 'markdown-it'
|
import MarkdownIt from 'markdown-it'
|
||||||
import markdownItRegex from 'markdown-it-regex'
|
import markdownItRegex from 'markdown-it-regex'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
@ -14,13 +14,16 @@ import { replaceLegacyVimeoShortCode } from './replace-legacy-vimeo-short-code'
|
||||||
import { replaceVimeoLink } from './replace-vimeo-link'
|
import { replaceVimeoLink } from './replace-vimeo-link'
|
||||||
import { VimeoFrame } from './vimeo-frame'
|
import { VimeoFrame } from './vimeo-frame'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detects 'app-vimeo' tags and renders them as vimeo embedding.
|
||||||
|
*/
|
||||||
export class VimeoReplacer extends ComponentReplacer {
|
export class VimeoReplacer extends ComponentReplacer {
|
||||||
public static readonly markdownItPlugin: MarkdownIt.PluginSimple = (markdownIt) => {
|
public static readonly markdownItPlugin: MarkdownIt.PluginSimple = (markdownIt) => {
|
||||||
markdownItRegex(markdownIt, replaceVimeoLink)
|
markdownItRegex(markdownIt, replaceVimeoLink)
|
||||||
markdownItRegex(markdownIt, replaceLegacyVimeoShortCode)
|
markdownItRegex(markdownIt, replaceLegacyVimeoShortCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
public getReplacement(node: DomElement): React.ReactElement | undefined {
|
public getReplacement(node: Element): React.ReactElement | undefined {
|
||||||
const attributes = getAttributesFromHedgeDocTag(node, 'vimeo')
|
const attributes = getAttributesFromHedgeDocTag(node, 'vimeo')
|
||||||
if (attributes && attributes.id) {
|
if (attributes && attributes.id) {
|
||||||
const videoId = attributes.id
|
const videoId = attributes.id
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DomElement } from 'domhandler'
|
import { Element } from 'domhandler'
|
||||||
import MarkdownIt from 'markdown-it'
|
import MarkdownIt from 'markdown-it'
|
||||||
import markdownItRegex from 'markdown-it-regex'
|
import markdownItRegex from 'markdown-it-regex'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
@ -14,13 +14,16 @@ import { replaceLegacyYoutubeShortCode } from './replace-legacy-youtube-short-co
|
||||||
import { replaceYouTubeLink } from './replace-youtube-link'
|
import { replaceYouTubeLink } from './replace-youtube-link'
|
||||||
import { YouTubeFrame } from './youtube-frame'
|
import { YouTubeFrame } from './youtube-frame'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detects 'app-youtube' tags and renders them as youtube embedding.
|
||||||
|
*/
|
||||||
export class YoutubeReplacer extends ComponentReplacer {
|
export class YoutubeReplacer extends ComponentReplacer {
|
||||||
public static readonly markdownItPlugin: MarkdownIt.PluginSimple = (markdownIt) => {
|
public static readonly markdownItPlugin: MarkdownIt.PluginSimple = (markdownIt) => {
|
||||||
markdownItRegex(markdownIt, replaceYouTubeLink)
|
markdownItRegex(markdownIt, replaceYouTubeLink)
|
||||||
markdownItRegex(markdownIt, replaceLegacyYoutubeShortCode)
|
markdownItRegex(markdownIt, replaceLegacyYoutubeShortCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
public getReplacement(node: DomElement): React.ReactElement | undefined {
|
public getReplacement(node: Element): React.ReactElement | undefined {
|
||||||
const attributes = getAttributesFromHedgeDocTag(node, 'youtube')
|
const attributes = getAttributesFromHedgeDocTag(node, 'youtube')
|
||||||
if (attributes && attributes.id) {
|
if (attributes && attributes.id) {
|
||||||
const videoId = attributes.id
|
const videoId = attributes.id
|
||||||
|
|
|
@ -4,18 +4,24 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DomElement } from 'domhandler'
|
import { Element, isTag } from 'domhandler'
|
||||||
import React, { ReactElement, Suspense } from 'react'
|
import React, { Suspense } from 'react'
|
||||||
import { convertNodeToElement, Transform } from 'react-html-parser'
|
import { convertNodeToReactElement } from '@hedgedoc/html-to-react/dist/convertNodeToReactElement'
|
||||||
import { ComponentReplacer, NativeRenderer, SubNodeTransform } from '../replace-components/ComponentReplacer'
|
import {
|
||||||
|
ComponentReplacer,
|
||||||
|
NativeRenderer,
|
||||||
|
SubNodeTransform,
|
||||||
|
ValidReactDomElement
|
||||||
|
} from '../replace-components/ComponentReplacer'
|
||||||
import { LineKeys } from '../types'
|
import { LineKeys } from '../types'
|
||||||
|
import { NodeToReactElementTransformer } from '@hedgedoc/html-to-react/dist/NodeToReactElementTransformer'
|
||||||
|
|
||||||
export interface TextDifferenceResult {
|
export interface TextDifferenceResult {
|
||||||
lines: LineKeys[]
|
lines: LineKeys[]
|
||||||
lastUsedLineId: number
|
lastUsedLineId: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export const calculateKeyFromLineMarker = (node: DomElement, lineKeys?: LineKeys[]): string | undefined => {
|
export const calculateKeyFromLineMarker = (node: Element, lineKeys?: LineKeys[]): string | undefined => {
|
||||||
if (!node.attribs || lineKeys === undefined) {
|
if (!node.attribs || lineKeys === undefined) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -26,7 +32,7 @@ export const calculateKeyFromLineMarker = (node: DomElement, lineKeys?: LineKeys
|
||||||
}
|
}
|
||||||
|
|
||||||
const lineMarker = node.prev
|
const lineMarker = node.prev
|
||||||
if (!lineMarker || !lineMarker.attribs) {
|
if (!lineMarker || !isTag(lineMarker) || !lineMarker.attribs) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,29 +54,49 @@ export const calculateKeyFromLineMarker = (node: DomElement, lineKeys?: LineKeys
|
||||||
}
|
}
|
||||||
|
|
||||||
export const findNodeReplacement = (
|
export const findNodeReplacement = (
|
||||||
node: DomElement,
|
node: Element,
|
||||||
allReplacers: ComponentReplacer[],
|
allReplacers: ComponentReplacer[],
|
||||||
subNodeTransform: SubNodeTransform,
|
subNodeTransform: SubNodeTransform,
|
||||||
nativeRenderer: NativeRenderer
|
nativeRenderer: NativeRenderer
|
||||||
): ReactElement | null | undefined => {
|
): ValidReactDomElement | undefined => {
|
||||||
return allReplacers
|
for (const componentReplacer of allReplacers) {
|
||||||
.map((componentReplacer) => componentReplacer.getReplacement(node, subNodeTransform, nativeRenderer))
|
const replacement = componentReplacer.getReplacement(node, subNodeTransform, nativeRenderer)
|
||||||
.find((replacement) => replacement !== undefined)
|
if (replacement !== undefined) {
|
||||||
|
return replacement
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const renderNativeNode = (node: DomElement, key: string, transform: Transform): ReactElement => {
|
/**
|
||||||
|
* Renders the given node without any replacement
|
||||||
|
*
|
||||||
|
* @param node The node to render
|
||||||
|
* @param key The unique key for the node
|
||||||
|
* @param transform The transform function that should be applied to the child nodes
|
||||||
|
*/
|
||||||
|
export const renderNativeNode = (
|
||||||
|
node: Element,
|
||||||
|
key: string,
|
||||||
|
transform: NodeToReactElementTransformer
|
||||||
|
): ValidReactDomElement => {
|
||||||
if (node.attribs === undefined) {
|
if (node.attribs === undefined) {
|
||||||
node.attribs = {}
|
node.attribs = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
delete node.attribs['data-key']
|
delete node.attribs['data-key']
|
||||||
return convertNodeToElement(node, key as unknown as number, transform)
|
return convertNodeToReactElement(node, key, transform)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const buildTransformer = (lineKeys: LineKeys[] | undefined, allReplacers: ComponentReplacer[]): Transform => {
|
export const buildTransformer = (
|
||||||
const transform: Transform = (node, index) => {
|
lineKeys: LineKeys[] | undefined,
|
||||||
|
allReplacers: ComponentReplacer[]
|
||||||
|
): NodeToReactElementTransformer => {
|
||||||
|
const transform: NodeToReactElementTransformer = (node, index) => {
|
||||||
|
if (!isTag(node)) {
|
||||||
|
return convertNodeToReactElement(node, index)
|
||||||
|
}
|
||||||
const nativeRenderer: NativeRenderer = () => renderNativeNode(node, key, transform)
|
const nativeRenderer: NativeRenderer = () => renderNativeNode(node, key, transform)
|
||||||
const subNodeTransform: SubNodeTransform = (subNode, subIndex) => transform(subNode, subIndex, transform)
|
const subNodeTransform: SubNodeTransform = (subNode, subKey) => transform(subNode, subKey, transform)
|
||||||
|
|
||||||
const key = calculateKeyFromLineMarker(node, lineKeys) ?? (-index).toString()
|
const key = calculateKeyFromLineMarker(node, lineKeys) ?? (-index).toString()
|
||||||
const tryReplacement = findNodeReplacement(node, allReplacers, subNodeTransform, nativeRenderer)
|
const tryReplacement = findNodeReplacement(node, allReplacers, subNodeTransform, nativeRenderer)
|
||||||
|
|
93
yarn.lock
93
yarn.lock
|
@ -1402,6 +1402,16 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@hapi/hoek" "^8.3.0"
|
"@hapi/hoek" "^8.3.0"
|
||||||
|
|
||||||
|
"@hedgedoc/html-to-react@1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@hedgedoc/html-to-react/-/html-to-react-1.0.0.tgz#275233189392addc9d2db4d943edd84eec618edf"
|
||||||
|
integrity sha512-bsEHYBPjj8MoSYSCuIwiMh+SOjgIVuRTYbJVpzJ8u3oxaJCAtysgrfsot7juRCClqQyprqAHmNT7f9lXBVXZtw==
|
||||||
|
dependencies:
|
||||||
|
"@types/react" "^17.0.11"
|
||||||
|
htmlparser2 "^6.1.0"
|
||||||
|
react "^17.0.2"
|
||||||
|
react-dom "^17.0.2"
|
||||||
|
|
||||||
"@hedgedoc/markdown-it-task-lists@1.0.0":
|
"@hedgedoc/markdown-it-task-lists@1.0.0":
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@hedgedoc/markdown-it-task-lists/-/markdown-it-task-lists-1.0.0.tgz#6514c411fb582d3de58ded393a3c4f69ba762028"
|
resolved "https://registry.yarnpkg.com/@hedgedoc/markdown-it-task-lists/-/markdown-it-task-lists-1.0.0.tgz#6514c411fb582d3de58ded393a3c4f69ba762028"
|
||||||
|
@ -2197,18 +2207,6 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/diff/-/diff-5.0.0.tgz#eb71e94feae62548282c4889308a3dfb57e36020"
|
resolved "https://registry.yarnpkg.com/@types/diff/-/diff-5.0.0.tgz#eb71e94feae62548282c4889308a3dfb57e36020"
|
||||||
integrity sha512-jrm2K65CokCCX4NmowtA+MfXyuprZC13jbRuwprs6/04z/EcFg/MCwYdsHn+zgV4CQBiATiI7AEq7y1sZCtWKA==
|
integrity sha512-jrm2K65CokCCX4NmowtA+MfXyuprZC13jbRuwprs6/04z/EcFg/MCwYdsHn+zgV4CQBiATiI7AEq7y1sZCtWKA==
|
||||||
|
|
||||||
"@types/domhandler@2.4.1":
|
|
||||||
version "2.4.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/domhandler/-/domhandler-2.4.1.tgz#7b3b347f7762180fbcb1ece1ce3dd0ebbb8c64cf"
|
|
||||||
integrity sha512-cfBw6q6tT5sa1gSPFSRKzF/xxYrrmeiut7E0TxNBObiLSBTuFEHibcfEe3waQPEDbqBsq+ql/TOniw65EyDFMA==
|
|
||||||
|
|
||||||
"@types/domutils@*":
|
|
||||||
version "1.7.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/domutils/-/domutils-1.7.3.tgz#890de5a79d86896e9e6a7445a43c9512e62d6f04"
|
|
||||||
integrity sha512-EucnS75OnnEdypNt+UpARisSF8eJBq4no+aVOis3Bs5kyABDXm1hEDv6jJxcMJPjR+a2YCrEANaW+BMT2QVG2Q==
|
|
||||||
dependencies:
|
|
||||||
domhandler "^2.4.0"
|
|
||||||
|
|
||||||
"@types/eslint@^7.2.6":
|
"@types/eslint@^7.2.6":
|
||||||
version "7.2.13"
|
version "7.2.13"
|
||||||
resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.13.tgz#e0ca7219ba5ded402062ad6f926d491ebb29dd53"
|
resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.13.tgz#e0ca7219ba5ded402062ad6f926d491ebb29dd53"
|
||||||
|
@ -2277,15 +2275,6 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#3c9ee980f1a10d6021ae6632ca3e79ca2ec4fb50"
|
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#3c9ee980f1a10d6021ae6632ca3e79ca2ec4fb50"
|
||||||
integrity sha512-giAlZwstKbmvMk1OO7WXSj4OZ0keXAcl2TQq4LWHiiPH2ByaH7WeUzng+Qej8UPxxv+8lRTuouo0iaNDBuzIBA==
|
integrity sha512-giAlZwstKbmvMk1OO7WXSj4OZ0keXAcl2TQq4LWHiiPH2ByaH7WeUzng+Qej8UPxxv+8lRTuouo0iaNDBuzIBA==
|
||||||
|
|
||||||
"@types/htmlparser2@*":
|
|
||||||
version "3.10.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/htmlparser2/-/htmlparser2-3.10.2.tgz#bd43702eaf2f15c2d26784c8427352a0a4aa75eb"
|
|
||||||
integrity sha512-81vjuO800UMoHjYbCbqtBmfC3iCsrROKpqndo0acKiN6k/cpW+YOw9FzRP0ghujHeUNCOox2AQPrrMy6+j5UpQ==
|
|
||||||
dependencies:
|
|
||||||
"@types/domutils" "*"
|
|
||||||
"@types/node" "*"
|
|
||||||
domhandler "^2.4.0"
|
|
||||||
|
|
||||||
"@types/invariant@^2.2.33":
|
"@types/invariant@^2.2.33":
|
||||||
version "2.2.34"
|
version "2.2.34"
|
||||||
resolved "https://registry.yarnpkg.com/@types/invariant/-/invariant-2.2.34.tgz#05e4f79f465c2007884374d4795452f995720bbe"
|
resolved "https://registry.yarnpkg.com/@types/invariant/-/invariant-2.2.34.tgz#05e4f79f465c2007884374d4795452f995720bbe"
|
||||||
|
@ -2456,14 +2445,6 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
|
|
||||||
"@types/react-html-parser@2.0.1":
|
|
||||||
version "2.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/react-html-parser/-/react-html-parser-2.0.1.tgz#2d9002ac5bf1adf9aff8eae77ace5488bd78c98d"
|
|
||||||
integrity sha512-Lyw0AtG3gahw78CX2pzmzhKaoZCfJNzzuhhPsFVhzFrylMv8NaCmzYaPKglMv3RRHpwBbHuMOkVx0HiwGZKgSA==
|
|
||||||
dependencies:
|
|
||||||
"@types/htmlparser2" "*"
|
|
||||||
"@types/react" "*"
|
|
||||||
|
|
||||||
"@types/react-redux@^7.1.16":
|
"@types/react-redux@^7.1.16":
|
||||||
version "7.1.16"
|
version "7.1.16"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.16.tgz#0fbd04c2500c12105494c83d4a3e45c084e3cb21"
|
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.16.tgz#0fbd04c2500c12105494c83d4a3e45c084e3cb21"
|
||||||
|
@ -2523,7 +2504,7 @@
|
||||||
"@types/scheduler" "*"
|
"@types/scheduler" "*"
|
||||||
csstype "^3.0.2"
|
csstype "^3.0.2"
|
||||||
|
|
||||||
"@types/react@17.0.11":
|
"@types/react@17.0.11", "@types/react@^17.0.11":
|
||||||
version "17.0.11"
|
version "17.0.11"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.11.tgz#67fcd0ddbf5a0b083a0f94e926c7d63f3b836451"
|
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.11.tgz#67fcd0ddbf5a0b083a0f94e926c7d63f3b836451"
|
||||||
integrity sha512-yFRQbD+whVonItSk7ZzP/L+gPTJVBkL/7shLEF+i9GC/1cV3JmUxEQz6+9ylhUpWSDuqo1N9qEvqS6vTj4USUA==
|
integrity sha512-yFRQbD+whVonItSk7ZzP/L+gPTJVBkL/7shLEF+i9GC/1cV3JmUxEQz6+9ylhUpWSDuqo1N9qEvqS6vTj4USUA==
|
||||||
|
@ -5978,6 +5959,15 @@ dom-serializer@0:
|
||||||
domelementtype "^2.0.1"
|
domelementtype "^2.0.1"
|
||||||
entities "^2.0.0"
|
entities "^2.0.0"
|
||||||
|
|
||||||
|
dom-serializer@^1.0.1:
|
||||||
|
version "1.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91"
|
||||||
|
integrity sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==
|
||||||
|
dependencies:
|
||||||
|
domelementtype "^2.0.1"
|
||||||
|
domhandler "^4.2.0"
|
||||||
|
entities "^2.0.0"
|
||||||
|
|
||||||
domain-browser@^1.1.1:
|
domain-browser@^1.1.1:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
|
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
|
||||||
|
@ -5988,7 +5978,7 @@ domelementtype@1, domelementtype@^1.3.1:
|
||||||
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
|
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
|
||||||
integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
|
integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
|
||||||
|
|
||||||
domelementtype@^2.0.1:
|
domelementtype@^2.0.1, domelementtype@^2.2.0:
|
||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57"
|
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57"
|
||||||
integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==
|
integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==
|
||||||
|
@ -6000,13 +5990,20 @@ domexception@^2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
webidl-conversions "^5.0.0"
|
webidl-conversions "^5.0.0"
|
||||||
|
|
||||||
domhandler@^2.3.0, domhandler@^2.4.0:
|
domhandler@^2.3.0:
|
||||||
version "2.4.2"
|
version "2.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803"
|
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803"
|
||||||
integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==
|
integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==
|
||||||
dependencies:
|
dependencies:
|
||||||
domelementtype "1"
|
domelementtype "1"
|
||||||
|
|
||||||
|
domhandler@^4.0.0, domhandler@^4.2.0:
|
||||||
|
version "4.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.2.0.tgz#f9768a5f034be60a89a27c2e4d0f74eba0d8b059"
|
||||||
|
integrity sha512-zk7sgt970kzPks2Bf+dwT/PLzghLnsivb9CcxkvR8Mzr66Olr0Ofd8neSbglHJHaHa2MadfoSdNlKYAaafmWfA==
|
||||||
|
dependencies:
|
||||||
|
domelementtype "^2.2.0"
|
||||||
|
|
||||||
domutils@^1.5.1, domutils@^1.7.0:
|
domutils@^1.5.1, domutils@^1.7.0:
|
||||||
version "1.7.0"
|
version "1.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"
|
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"
|
||||||
|
@ -6015,6 +6012,15 @@ domutils@^1.5.1, domutils@^1.7.0:
|
||||||
dom-serializer "0"
|
dom-serializer "0"
|
||||||
domelementtype "1"
|
domelementtype "1"
|
||||||
|
|
||||||
|
domutils@^2.5.2:
|
||||||
|
version "2.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.7.0.tgz#8ebaf0c41ebafcf55b0b72ec31c56323712c5442"
|
||||||
|
integrity sha512-8eaHa17IwJUPAiB+SoTYBo5mCdeMgdcAoXJ59m6DT1vw+5iLS3gNoqYaRowaBKtGVrOF1Jz4yDTgYKLK2kvfJg==
|
||||||
|
dependencies:
|
||||||
|
dom-serializer "^1.0.1"
|
||||||
|
domelementtype "^2.2.0"
|
||||||
|
domhandler "^4.2.0"
|
||||||
|
|
||||||
dot-case@^3.0.4:
|
dot-case@^3.0.4:
|
||||||
version "3.0.4"
|
version "3.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751"
|
resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751"
|
||||||
|
@ -7702,7 +7708,7 @@ html-webpack-plugin@4.5.0:
|
||||||
tapable "^1.1.3"
|
tapable "^1.1.3"
|
||||||
util.promisify "1.0.0"
|
util.promisify "1.0.0"
|
||||||
|
|
||||||
htmlparser2@^3.10.1, htmlparser2@^3.9.0:
|
htmlparser2@^3.10.1:
|
||||||
version "3.10.1"
|
version "3.10.1"
|
||||||
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f"
|
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f"
|
||||||
integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==
|
integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==
|
||||||
|
@ -7714,6 +7720,16 @@ htmlparser2@^3.10.1, htmlparser2@^3.9.0:
|
||||||
inherits "^2.0.1"
|
inherits "^2.0.1"
|
||||||
readable-stream "^3.1.1"
|
readable-stream "^3.1.1"
|
||||||
|
|
||||||
|
htmlparser2@^6.1.0:
|
||||||
|
version "6.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7"
|
||||||
|
integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==
|
||||||
|
dependencies:
|
||||||
|
domelementtype "^2.0.1"
|
||||||
|
domhandler "^4.0.0"
|
||||||
|
domutils "^2.5.2"
|
||||||
|
entities "^2.0.0"
|
||||||
|
|
||||||
http-deceiver@^1.2.7:
|
http-deceiver@^1.2.7:
|
||||||
version "1.2.7"
|
version "1.2.7"
|
||||||
resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
|
resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
|
||||||
|
@ -11904,7 +11920,7 @@ react-diff-viewer@3.1.1:
|
||||||
memoize-one "^5.0.4"
|
memoize-one "^5.0.4"
|
||||||
prop-types "^15.6.2"
|
prop-types "^15.6.2"
|
||||||
|
|
||||||
react-dom@17.0.2:
|
react-dom@17.0.2, react-dom@^17.0.2:
|
||||||
version "17.0.2"
|
version "17.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
|
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
|
||||||
integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
|
integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
|
||||||
|
@ -11918,13 +11934,6 @@ react-error-overlay@^6.0.9:
|
||||||
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a"
|
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a"
|
||||||
integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==
|
integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==
|
||||||
|
|
||||||
react-html-parser@2.0.2:
|
|
||||||
version "2.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/react-html-parser/-/react-html-parser-2.0.2.tgz#6dbe1ddd2cebc1b34ca15215158021db5fc5685e"
|
|
||||||
integrity sha512-XeerLwCVjTs3njZcgCOeDUqLgNIt/t+6Jgi5/qPsO/krUWl76kWKXMeVs2LhY2gwM6X378DkhLjur0zUQdpz0g==
|
|
||||||
dependencies:
|
|
||||||
htmlparser2 "^3.9.0"
|
|
||||||
|
|
||||||
react-i18next@11.11.0:
|
react-i18next@11.11.0:
|
||||||
version "11.11.0"
|
version "11.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.11.0.tgz#2f7c6cb4f81f94d1728a02d60e4bb5216709f942"
|
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.11.0.tgz#2f7c6cb4f81f94d1728a02d60e4bb5216709f942"
|
||||||
|
@ -12150,7 +12159,7 @@ react-use@17.2.4:
|
||||||
ts-easing "^0.2.0"
|
ts-easing "^0.2.0"
|
||||||
tslib "^2.1.0"
|
tslib "^2.1.0"
|
||||||
|
|
||||||
react@17.0.2:
|
react@17.0.2, react@^17.0.2:
|
||||||
version "17.0.2"
|
version "17.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
|
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
|
||||||
integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==
|
integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue