From 7a9951e351d42b3798a0fed0a53dbbe04fa12dc0 Mon Sep 17 00:00:00 2001 From: Tilman Vatteroth Date: Thu, 15 Sep 2022 17:52:22 +0200 Subject: [PATCH] fix(component replacer): Use symbol for DO_NOT_REPLACE instead of undefined Signed-off-by: Tilman Vatteroth --- .../blockquote-color-extra-tag-replacer.tsx | 32 ++++++++--------- .../blockquote-extra-tag-replacer.tsx | 21 +++++------ .../markdown-extension/csv/csv-replacer.tsx | 9 ++--- .../highlighted-code-replacer.tsx | 9 ++--- .../iframe-capsule-replacer.tsx | 24 ++++++------- .../image/proxy-image-replacer.tsx | 35 ++++++++++--------- .../katex/katex-replacer.tsx | 3 +- .../linemarker/linemarker-replacer.tsx | 9 ++--- .../link-replacer/jump-anchor-replacer.tsx | 12 +++---- .../task-list/task-list-replacer.tsx | 24 +++++-------- ...upload-indicating-image-frame-replacer.tsx | 12 ++++--- .../code-block-component-replacer.ts | 10 +++--- .../replace-components/component-replacer.ts | 10 +++--- .../custom-tag-with-id-component-replacer.ts | 6 ++-- .../utils/node-to-react-transformer.test.tsx | 4 +-- .../utils/node-to-react-transformer.tsx | 10 +++--- 16 files changed, 114 insertions(+), 116 deletions(-) diff --git a/src/components/markdown-renderer/markdown-extension/blockquote/blockquote-color-extra-tag-replacer.tsx b/src/components/markdown-renderer/markdown-extension/blockquote/blockquote-color-extra-tag-replacer.tsx index 904c8c96d..289578883 100644 --- a/src/components/markdown-renderer/markdown-extension/blockquote/blockquote-color-extra-tag-replacer.tsx +++ b/src/components/markdown-renderer/markdown-extension/blockquote/blockquote-color-extra-tag-replacer.tsx @@ -22,22 +22,20 @@ import { Optional } from '@mrdrogdrog/optional' */ export class BlockquoteColorExtraTagReplacer extends ComponentReplacer { replace(element: Element): NodeReplacement { - if ( - element.tagName === BlockquoteExtraTagMarkdownExtension.tagName && - element.attribs?.['data-label'] === 'color' && - element.children !== undefined - ) { - let index = 0 - return Optional.ofNullable(element.children[0]) - .filter(isText) - .map((child) => (child as Text).data) - .filter((content) => cssColor.test(content)) - .map((color) => ( - - - - )) - .orElse(DO_NOT_REPLACE) - } + return Optional.of(element) + .filter( + (element) => + element.tagName === BlockquoteExtraTagMarkdownExtension.tagName && element.attribs?.['data-label'] === 'color' + ) + .map((element) => element.children[0]) + .filter(isText) + .map((child) => (child as Text).data) + .filter((content) => cssColor.test(content)) + .map((color) => ( + + + + )) + .orElse(DO_NOT_REPLACE) } } diff --git a/src/components/markdown-renderer/markdown-extension/blockquote/blockquote-extra-tag-replacer.tsx b/src/components/markdown-renderer/markdown-extension/blockquote/blockquote-extra-tag-replacer.tsx index 9fe297cce..888e31ea0 100644 --- a/src/components/markdown-renderer/markdown-extension/blockquote/blockquote-extra-tag-replacer.tsx +++ b/src/components/markdown-renderer/markdown-extension/blockquote/blockquote-extra-tag-replacer.tsx @@ -23,16 +23,17 @@ import { BlockquoteExtraTagMarkdownExtension } from './blockquote-extra-tag-mark */ export class BlockquoteExtraTagReplacer extends ComponentReplacer { replace(element: Element, subNodeTransform: SubNodeTransform): NodeReplacement { - if (element.tagName !== BlockquoteExtraTagMarkdownExtension.tagName || !element.attribs) { - return DO_NOT_REPLACE - } - - return ( - - {this.buildIconElement(element)} - {BlockquoteExtraTagReplacer.transformChildren(element, subNodeTransform)} - - ) + return Optional.of(element) + .filter( + (element) => element.tagName === BlockquoteExtraTagMarkdownExtension.tagName && element.attribs !== undefined + ) + .map((element) => ( + + {this.buildIconElement(element)} + {BlockquoteExtraTagReplacer.transformChildren(element, subNodeTransform)} + + )) + .orElse(DO_NOT_REPLACE) } /** diff --git a/src/components/markdown-renderer/markdown-extension/csv/csv-replacer.tsx b/src/components/markdown-renderer/markdown-extension/csv/csv-replacer.tsx index 12f9bb78a..233fbf287 100644 --- a/src/components/markdown-renderer/markdown-extension/csv/csv-replacer.tsx +++ b/src/components/markdown-renderer/markdown-extension/csv/csv-replacer.tsx @@ -1,12 +1,13 @@ /* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ import type { Element } from 'domhandler' import React from 'react' -import { ComponentReplacer } from '../../replace-components/component-replacer' +import type { NodeReplacement } from '../../replace-components/component-replacer' +import { ComponentReplacer, DO_NOT_REPLACE } from '../../replace-components/component-replacer' import { CsvTable } from './csv-table' import { CodeBlockComponentReplacer } from '../../replace-components/code-block-component-replacer' @@ -14,10 +15,10 @@ import { CodeBlockComponentReplacer } from '../../replace-components/code-block- * Detects code blocks with "csv" as language and renders them as table. */ export class CsvReplacer extends ComponentReplacer { - public replace(codeNode: Element): React.ReactElement | undefined { + public replace(codeNode: Element): NodeReplacement { const code = CodeBlockComponentReplacer.extractTextFromCodeNode(codeNode, 'csv') if (!code) { - return + return DO_NOT_REPLACE } const extraData = codeNode.attribs['data-extra'] diff --git a/src/components/markdown-renderer/markdown-extension/highlighted-fence/highlighted-code-replacer.tsx b/src/components/markdown-renderer/markdown-extension/highlighted-fence/highlighted-code-replacer.tsx index ffe67eab7..cf63d2b88 100644 --- a/src/components/markdown-renderer/markdown-extension/highlighted-fence/highlighted-code-replacer.tsx +++ b/src/components/markdown-renderer/markdown-extension/highlighted-fence/highlighted-code-replacer.tsx @@ -1,12 +1,13 @@ /* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ import type { Element } from 'domhandler' import React from 'react' -import { ComponentReplacer } from '../../replace-components/component-replacer' +import type { NodeReplacement } from '../../replace-components/component-replacer' +import { ComponentReplacer, DO_NOT_REPLACE } from '../../replace-components/component-replacer' import { HighlightedCode } from './highlighted-code' /** @@ -21,10 +22,10 @@ export class HighlightedCodeReplacer extends ComponentReplacer { : undefined } - public replace(codeNode: Element): React.ReactElement | undefined { + public replace(codeNode: Element): NodeReplacement { const code = HighlightedCodeReplacer.extractCode(codeNode) if (!code) { - return + return DO_NOT_REPLACE } const language = codeNode.attribs['data-highlight-language'] diff --git a/src/components/markdown-renderer/markdown-extension/iframe-capsule/iframe-capsule-replacer.tsx b/src/components/markdown-renderer/markdown-extension/iframe-capsule/iframe-capsule-replacer.tsx index 994423a92..855c6a2c0 100644 --- a/src/components/markdown-renderer/markdown-extension/iframe-capsule/iframe-capsule-replacer.tsx +++ b/src/components/markdown-renderer/markdown-extension/iframe-capsule/iframe-capsule-replacer.tsx @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ @@ -16,17 +16,15 @@ import { ClickShield } from '../../replace-components/click-shield/click-shield' */ export class IframeCapsuleReplacer extends ComponentReplacer { replace(node: Element, subNodeTransform: SubNodeTransform, nativeRenderer: NativeRenderer): NodeReplacement { - if (node.name === 'iframe') { - return ( - - {nativeRenderer()} - - ) - } else { - return DO_NOT_REPLACE - } + return node.name !== 'iframe' ? ( + DO_NOT_REPLACE + ) : ( + + {nativeRenderer()} + + ) } } diff --git a/src/components/markdown-renderer/markdown-extension/image/proxy-image-replacer.tsx b/src/components/markdown-renderer/markdown-extension/image/proxy-image-replacer.tsx index 7d155fb1f..e37c8c717 100644 --- a/src/components/markdown-renderer/markdown-extension/image/proxy-image-replacer.tsx +++ b/src/components/markdown-renderer/markdown-extension/image/proxy-image-replacer.tsx @@ -1,12 +1,13 @@ /* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ import type { Element } from 'domhandler' import React from 'react' -import { ComponentReplacer } from '../../replace-components/component-replacer' +import type { NodeReplacement } from '../../replace-components/component-replacer' +import { ComponentReplacer, DO_NOT_REPLACE } from '../../replace-components/component-replacer' import { ProxyImageFrame } from './proxy-image-frame' export type ImageClickHandler = (event: React.MouseEvent) => void @@ -22,20 +23,20 @@ export class ProxyImageReplacer extends ComponentReplacer { this.clickHandler = clickHandler } - public replace(node: Element): React.ReactElement | undefined { - if (node.name === 'img') { - return ( - - ) - } + public replace(node: Element): NodeReplacement { + return node.name !== 'img' ? ( + DO_NOT_REPLACE + ) : ( + + ) } } diff --git a/src/components/markdown-renderer/markdown-extension/katex/katex-replacer.tsx b/src/components/markdown-renderer/markdown-extension/katex/katex-replacer.tsx index 933065021..9f7f00d30 100644 --- a/src/components/markdown-renderer/markdown-extension/katex/katex-replacer.tsx +++ b/src/components/markdown-renderer/markdown-extension/katex/katex-replacer.tsx @@ -7,6 +7,7 @@ import type { Element } from 'domhandler' import { isTag } from 'domhandler' import React from 'react' +import type { NodeReplacement } from '../../replace-components/component-replacer' import { ComponentReplacer, DO_NOT_REPLACE } from '../../replace-components/component-replacer' import { KatexMarkdownExtension } from './katex-markdown-extension' import { Optional } from '@mrdrogdrog/optional' @@ -17,7 +18,7 @@ const KaTeX = React.lazy(() => import(/* webpackChunkName: "katex" */ './katex-f * Detects LaTeX syntax and renders it with KaTeX. */ export class KatexReplacer extends ComponentReplacer { - public replace(node: Element): React.ReactElement | undefined { + public replace(node: Element): NodeReplacement { return this.extractKatexContent(node) .map((latexContent) => { const isInline = !!node.attribs?.['data-inline'] diff --git a/src/components/markdown-renderer/markdown-extension/linemarker/linemarker-replacer.tsx b/src/components/markdown-renderer/markdown-extension/linemarker/linemarker-replacer.tsx index 9a1c03748..99a8b6315 100644 --- a/src/components/markdown-renderer/markdown-extension/linemarker/linemarker-replacer.tsx +++ b/src/components/markdown-renderer/markdown-extension/linemarker/linemarker-replacer.tsx @@ -1,18 +1,19 @@ /* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ import type { Element } from 'domhandler' -import { ComponentReplacer } from '../../replace-components/component-replacer' +import type { NodeReplacement } from '../../replace-components/component-replacer' +import { ComponentReplacer, DO_NOT_REPLACE } from '../../replace-components/component-replacer' import { LinemarkerMarkdownExtension } from './linemarker-markdown-extension' /** * Detects line markers and suppresses them in the resulting DOM. */ export class LinemarkerReplacer extends ComponentReplacer { - public replace(codeNode: Element): null | undefined { - return codeNode.name === LinemarkerMarkdownExtension.tagName ? null : undefined + public replace(codeNode: Element): NodeReplacement { + return codeNode.name === LinemarkerMarkdownExtension.tagName ? null : DO_NOT_REPLACE } } diff --git a/src/components/markdown-renderer/markdown-extension/link-replacer/jump-anchor-replacer.tsx b/src/components/markdown-renderer/markdown-extension/link-replacer/jump-anchor-replacer.tsx index 23cd31058..676115eac 100644 --- a/src/components/markdown-renderer/markdown-extension/link-replacer/jump-anchor-replacer.tsx +++ b/src/components/markdown-renderer/markdown-extension/link-replacer/jump-anchor-replacer.tsx @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ @@ -22,10 +22,10 @@ export class JumpAnchorReplacer extends ComponentReplacer { const jumpId = node.attribs['data-jump-target-id'] delete node.attribs['data-jump-target-id'] const replacement = nativeRenderer() - if (replacement === null || typeof replacement === 'string') { - return replacement - } else { - return )} jumpTargetId={jumpId} /> - } + return replacement === null || typeof replacement === 'string' ? ( + replacement + ) : ( + )} jumpTargetId={jumpId} /> + ) } } diff --git a/src/components/markdown-renderer/markdown-extension/task-list/task-list-replacer.tsx b/src/components/markdown-renderer/markdown-extension/task-list/task-list-replacer.tsx index b96aa22df..26a4f894a 100644 --- a/src/components/markdown-renderer/markdown-extension/task-list/task-list-replacer.tsx +++ b/src/components/markdown-renderer/markdown-extension/task-list/task-list-replacer.tsx @@ -1,13 +1,13 @@ /* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ import type { Element } from 'domhandler' -import type { ReactElement } from 'react' import React from 'react' -import { ComponentReplacer } from '../../replace-components/component-replacer' +import type { NodeReplacement } from '../../replace-components/component-replacer' +import { ComponentReplacer, DO_NOT_REPLACE } from '../../replace-components/component-replacer' import { TaskListCheckbox } from './task-list-checkbox' export type TaskCheckedChangeHandler = (lineInMarkdown: number, checked: boolean) => void @@ -20,23 +20,17 @@ export class TaskListReplacer extends ComponentReplacer { constructor(onTaskCheckedChange?: TaskCheckedChangeHandler) { super() - this.onTaskCheckedChange = (lineInMarkdown, checked) => { - if (onTaskCheckedChange === undefined) { - return - } - onTaskCheckedChange(lineInMarkdown, checked) - } + this.onTaskCheckedChange = (lineInMarkdown, checked) => onTaskCheckedChange?.(lineInMarkdown, checked) } - public replace(node: Element): ReactElement | undefined { + public replace(node: Element): NodeReplacement { if (node.attribs?.class !== 'task-list-item-checkbox') { - return + return DO_NOT_REPLACE } const lineInMarkdown = Number(node.attribs['data-line']) - if (isNaN(lineInMarkdown)) { - return undefined - } - return ( + return isNaN(lineInMarkdown) ? ( + DO_NOT_REPLACE + ) : ( - } + return node.name !== 'img' || !uploadIdRegex.test(node.attribs.src) ? ( + DO_NOT_REPLACE + ) : ( + + ) } } diff --git a/src/components/markdown-renderer/replace-components/code-block-component-replacer.ts b/src/components/markdown-renderer/replace-components/code-block-component-replacer.ts index bd294a710..6b6f84ecd 100644 --- a/src/components/markdown-renderer/replace-components/code-block-component-replacer.ts +++ b/src/components/markdown-renderer/replace-components/code-block-component-replacer.ts @@ -1,11 +1,11 @@ /* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ -import type { ValidReactDomElement } from './component-replacer' -import { ComponentReplacer } from './component-replacer' +import type { NodeReplacement } from './component-replacer' +import { ComponentReplacer, DO_NOT_REPLACE } from './component-replacer' import type { FunctionComponent } from 'react' import React from 'react' import type { Element } from 'domhandler' @@ -22,9 +22,9 @@ export class CodeBlockComponentReplacer extends ComponentReplacer { super() } - replace(node: Element): ValidReactDomElement | undefined { + replace(node: Element): NodeReplacement { const code = CodeBlockComponentReplacer.extractTextFromCodeNode(node, this.language) - return code ? React.createElement(this.component, { code: code }) : undefined + return code ? React.createElement(this.component, { code: code }) : DO_NOT_REPLACE } /** diff --git a/src/components/markdown-renderer/replace-components/component-replacer.ts b/src/components/markdown-renderer/replace-components/component-replacer.ts index aeb811e04..63c8b1cb1 100644 --- a/src/components/markdown-renderer/replace-components/component-replacer.ts +++ b/src/components/markdown-renderer/replace-components/component-replacer.ts @@ -10,13 +10,13 @@ import type { ReactElement } from 'react' export type ValidReactDomElement = ReactElement | string | null -export type SubNodeTransform = (node: Node, subKey: number | string) => NodeReplacement +export type SubNodeTransform = (node: Node, subKey: number | string) => ValidReactDomElement export type NativeRenderer = () => ValidReactDomElement -export const REPLACE_WITH_NOTHING = null -export const DO_NOT_REPLACE = undefined -export type NodeReplacement = ValidReactDomElement | typeof REPLACE_WITH_NOTHING | typeof DO_NOT_REPLACE +export const DO_NOT_REPLACE = Symbol() + +export type NodeReplacement = ValidReactDomElement | typeof DO_NOT_REPLACE /** * Base class for all component replacers. @@ -42,7 +42,7 @@ export abstract class ComponentReplacer { * @param subNodeTransform The transformer that should be used. * @return The children as react elements. */ - protected static transformChildren(node: Element, subNodeTransform: SubNodeTransform): NodeReplacement[] { + protected static transformChildren(node: Element, subNodeTransform: SubNodeTransform): ValidReactDomElement[] { return node.children.map((value, index) => subNodeTransform(value, index)) } diff --git a/src/components/markdown-renderer/replace-components/custom-tag-with-id-component-replacer.ts b/src/components/markdown-renderer/replace-components/custom-tag-with-id-component-replacer.ts index ed18bb998..e539ed3ec 100644 --- a/src/components/markdown-renderer/replace-components/custom-tag-with-id-component-replacer.ts +++ b/src/components/markdown-renderer/replace-components/custom-tag-with-id-component-replacer.ts @@ -1,11 +1,11 @@ /* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ import type { NodeReplacement } from './component-replacer' -import { ComponentReplacer } from './component-replacer' +import { ComponentReplacer, DO_NOT_REPLACE } from './component-replacer' import type { FunctionComponent } from 'react' import React from 'react' import type { Element } from 'domhandler' @@ -24,7 +24,7 @@ export class CustomTagWithIdComponentReplacer extends ComponentReplacer { public replace(node: Element): NodeReplacement { const id = this.extractId(node) - return id ? React.createElement(this.component, { id: id }) : undefined + return id ? React.createElement(this.component, { id: id }) : DO_NOT_REPLACE } /** diff --git a/src/components/markdown-renderer/utils/node-to-react-transformer.test.tsx b/src/components/markdown-renderer/utils/node-to-react-transformer.test.tsx index 48f25e18a..19a7b193d 100644 --- a/src/components/markdown-renderer/utils/node-to-react-transformer.test.tsx +++ b/src/components/markdown-renderer/utils/node-to-react-transformer.test.tsx @@ -8,7 +8,7 @@ import { NodeToReactTransformer } from './node-to-react-transformer' import { Element } from 'domhandler' import type { ReactElement, ReactHTMLElement } from 'react' import type { NodeReplacement } from '../replace-components/component-replacer' -import { ComponentReplacer, DO_NOT_REPLACE, REPLACE_WITH_NOTHING } from '../replace-components/component-replacer' +import { ComponentReplacer, DO_NOT_REPLACE } from '../replace-components/component-replacer' describe('node to react transformer', () => { let nodeToReactTransformer: NodeToReactTransformer @@ -30,7 +30,7 @@ describe('node to react transformer', () => { nodeToReactTransformer.setReplacers([ new (class extends ComponentReplacer { replace(): NodeReplacement { - return REPLACE_WITH_NOTHING + return null } })() ]) diff --git a/src/components/markdown-renderer/utils/node-to-react-transformer.tsx b/src/components/markdown-renderer/utils/node-to-react-transformer.tsx index 1fce0bffa..b3cd749e2 100644 --- a/src/components/markdown-renderer/utils/node-to-react-transformer.tsx +++ b/src/components/markdown-renderer/utils/node-to-react-transformer.tsx @@ -8,7 +8,7 @@ import type { Element, Node } from 'domhandler' import { isTag } from 'domhandler' import { convertNodeToReactElement } from '@hedgedoc/html-to-react/dist/convertNodeToReactElement' import type { ComponentReplacer, NodeReplacement, ValidReactDomElement } from '../replace-components/component-replacer' -import { DO_NOT_REPLACE, REPLACE_WITH_NOTHING } from '../replace-components/component-replacer' +import { DO_NOT_REPLACE } from '../replace-components/component-replacer' import React from 'react' import type { LineWithId } from '../markdown-extension/linemarker/types' import { Optional } from '@mrdrogdrog/optional' @@ -45,7 +45,7 @@ export class NodeToReactTransformer { * @param index The index of the node within its parents child list. * @return the created react element */ - public translateNodeToReactElement(node: Node, index: number | string): ValidReactDomElement | undefined { + public translateNodeToReactElement(node: Node, index: number | string): ValidReactDomElement { return isTag(node) ? this.translateElementToReactElement(node, index) : convertNodeToReactElement(node, index, this.translateNodeToReactElement.bind(this)) @@ -58,10 +58,10 @@ export class NodeToReactTransformer { * @param index The index of the element within its parents child list. * @return the created react element */ - private translateElementToReactElement(element: Element, index: number | string): ValidReactDomElement | undefined { + private translateElementToReactElement(element: Element, index: number | string): ValidReactDomElement { const elementKey = this.calculateUniqueKey(element).orElseGet(() => (-index).toString()) const replacement = this.findElementReplacement(element, elementKey) - if (replacement === REPLACE_WITH_NOTHING) { + if (replacement === null) { return null } else if (replacement === DO_NOT_REPLACE) { return this.renderNativeNode(element, elementKey) @@ -101,7 +101,7 @@ export class NodeToReactTransformer { * * @param element The {@link Element} that should be checked. * @param elementKey The unique key for the element - * @return The replacement or {@link DO_NOT_REPLACE} if the element shouldn't be replaced or {@link REPLACE_WITH_NOTHING} if the node shouldn't be rendered at all. + * @return The replacement or {@link DO_NOT_REPLACE} if the element shouldn't be replaced with a custom component. */ private findElementReplacement(element: Element, elementKey: string): NodeReplacement { const transformer = this.translateNodeToReactElement.bind(this)