diff --git a/src/components/editor-page/app-bar/help-button/cheatsheet-line.tsx b/src/components/editor-page/app-bar/help-button/cheatsheet-line.tsx index 44ffa456b..d9d4a0ba0 100644 --- a/src/components/editor-page/app-bar/help-button/cheatsheet-line.tsx +++ b/src/components/editor-page/app-bar/help-button/cheatsheet-line.tsx @@ -4,8 +4,12 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import React, { Suspense, useCallback, useMemo } from 'react' +import React, { Suspense, useEffect, useMemo } from 'react' import { WaitSpinner } from '../../../common/wait-spinner/wait-spinner' +import { eventEmitterContext } from '../../../markdown-renderer/hooks/use-extension-event-emitter' +import EventEmitter2 from 'eventemitter2' +import type { TaskCheckedEventPayload } from '../../../../extensions/extra-integrations/task-list/event-emitting-task-list-checkbox' +import { TaskListCheckboxAppExtension } from '../../../../extensions/extra-integrations/task-list/task-list-checkbox-app-extension' export interface CheatsheetLineProps { markdown: string @@ -13,7 +17,7 @@ export interface CheatsheetLineProps { } const HighlightedCode = React.lazy( - () => import('../../../markdown-renderer/markdown-extension/highlighted-fence/highlighted-code') + () => import('../../../../extensions/extra-integrations/highlighted-code-fence/highlighted-code') ) const DocumentMarkdownRenderer = React.lazy(() => import('../../../markdown-renderer/document-markdown-renderer')) @@ -26,12 +30,15 @@ const DocumentMarkdownRenderer = React.lazy(() => import('../../../markdown-rend */ export const CheatsheetLine: React.FC = ({ markdown, onTaskCheckedChange }) => { const lines = useMemo(() => markdown.split('\n'), [markdown]) - const checkboxClick = useCallback( - (lineInMarkdown: number, newValue: boolean) => { - onTaskCheckedChange(newValue) - }, - [onTaskCheckedChange] - ) + const eventEmitter = useMemo(() => new EventEmitter2(), []) + + useEffect(() => { + const handler = ({ checked }: TaskCheckedEventPayload) => onTaskCheckedChange(checked) + eventEmitter.on(TaskListCheckboxAppExtension.EVENT_NAME, handler) + return () => { + eventEmitter.off(TaskListCheckboxAppExtension.EVENT_NAME, handler) + } + }) return ( = ({ markdown, onTask }> - + + + diff --git a/src/components/editor-page/editor-document-renderer/editor-document-renderer.tsx b/src/components/editor-page/editor-document-renderer/editor-document-renderer.tsx index 70fd0555a..bde90f2ea 100644 --- a/src/components/editor-page/editor-document-renderer/editor-document-renderer.tsx +++ b/src/components/editor-page/editor-document-renderer/editor-document-renderer.tsx @@ -11,7 +11,6 @@ import { useTrimmedNoteMarkdownContentWithoutFrontmatter } from '../../../hooks/ import { NoteType } from '../../../redux/note-details/types/note-details' import { useApplicationState } from '../../../hooks/common/use-application-state' import { RendererType } from '../../render-page/window-post-message-communicator/rendering-message' -import { useSetCheckboxInEditor } from './hooks/use-set-checkbox-in-editor' import { useOnScrollWithLineOffset } from './hooks/use-on-scroll-with-line-offset' import { useScrollStateWithoutLineOffset } from './hooks/use-scroll-state-without-line-offset' import { setRendererStatus } from '../../../redux/renderer-status/methods' @@ -31,7 +30,6 @@ export type EditorDocumentRendererProps = Omit< export const EditorDocumentRenderer: React.FC = ({ scrollState, onScroll, ...props }) => { const trimmedContentLines = useTrimmedNoteMarkdownContentWithoutFrontmatter() const noteType: NoteType = useApplicationState((state) => state.noteDetails.frontmatter.type) - const setCheckboxInEditor = useSetCheckboxInEditor() const adjustedOnScroll = useOnScrollWithLineOffset(onScroll) const adjustedScrollState = useScrollStateWithoutLineOffset(scrollState) @@ -40,7 +38,6 @@ export const EditorDocumentRenderer: React.FC = ({ {...props} onScroll={adjustedOnScroll} scrollState={adjustedScrollState} - onTaskCheckedChange={setCheckboxInEditor} rendererType={noteType === NoteType.SLIDE ? RendererType.SLIDESHOW : RendererType.DOCUMENT} markdownContentLines={trimmedContentLines} onRendererStatusChange={setRendererStatus} diff --git a/src/components/editor-page/editor-page-content.tsx b/src/components/editor-page/editor-page-content.tsx index 541a61257..4b01d1f84 100644 --- a/src/components/editor-page/editor-page-content.tsx +++ b/src/components/editor-page/editor-page-content.tsx @@ -24,6 +24,9 @@ import { NoteAndAppTitleHead } from '../layout/note-and-app-title-head' import equal from 'fast-deep-equal' import { EditorPane } from './editor-pane/editor-pane' import { ChangeEditorContentContextProvider } from './change-content-context/change-content-context' +import { ExtensionEventEmitterProvider } from '../markdown-renderer/hooks/use-extension-event-emitter' +import { useComponentsFromAppExtensions } from './editor-pane/hooks/use-components-from-app-extensions' +import { CommunicatorImageLightbox } from '../markdown-renderer/extensions/image/communicator-image-lightbox' export enum ScrollSource { EDITOR = 'editor', @@ -124,23 +127,29 @@ export const EditorPageContent: React.FC = () => { [onMarkdownRendererScroll, scrollState.rendererScrollState, setRendererToScrollSource] ) + const editorExtensionComponents = useComponentsFromAppExtensions() + return ( - - -
- -
- - + + {editorExtensionComponents} + + + +
+ +
+ + +
-
+ ) } diff --git a/src/components/editor-page/editor-pane/editor-pane.tsx b/src/components/editor-page/editor-pane/editor-pane.tsx index b101030b4..fda9cc883 100644 --- a/src/components/editor-page/editor-pane/editor-pane.tsx +++ b/src/components/editor-page/editor-pane/editor-pane.tsx @@ -21,7 +21,6 @@ import { markdown, markdownLanguage } from '@codemirror/lang-markdown' import { EditorView } from '@codemirror/view' import { autocompletion } from '@codemirror/autocomplete' import { cypressAttribute, cypressId } from '../../../utils/cypress-attribute' -import { findLanguageByCodeBlockName } from '../../markdown-renderer/markdown-extension/code-block-markdown-extension/find-language-by-code-block-name' import { languages } from '@codemirror/language-data' import { useCursorActivityCallback } from './hooks/use-cursor-activity-callback' import { useCodeMirrorReference, useSetCodeMirrorReference } from '../change-content-context/change-content-context' @@ -39,12 +38,10 @@ import { useIsConnectionSynced } from './hooks/yjs/use-is-connection-synced' import { useMarkdownContentYText } from './hooks/yjs/use-markdown-content-y-text' import { lintGutter } from '@codemirror/lint' import { useLinter } from './linter/linter' -import { YoutubeMarkdownExtension } from '../../markdown-renderer/markdown-extension/youtube/youtube-markdown-extension' -import { VimeoMarkdownExtension } from '../../markdown-renderer/markdown-extension/vimeo/vimeo-markdown-extension' -import { SequenceDiagramMarkdownExtension } from '../../markdown-renderer/markdown-extension/sequence-diagram/sequence-diagram-markdown-extension' -import { LegacyShortcodesMarkdownExtension } from '../../markdown-renderer/markdown-extension/legacy-short-codes/legacy-shortcodes-markdown-extension' import { FrontmatterLinter } from './linter/frontmatter-linter' import { useOnNoteDeleted } from './hooks/yjs/use-on-note-deleted' +import { findLanguageByCodeBlockName } from '../../markdown-renderer/extensions/base/code-block-markdown-extension/find-language-by-code-block-name' +import { optionalAppExtensions } from '../../../extensions/extra-integrations/optional-app-extensions' /** * Renders the text editor pane of the editor. @@ -90,15 +87,9 @@ export const EditorPane: React.FC = ({ scrollState, onScroll, onMak useInsertNoteContentIntoYTextInMockModeEffect(firstUpdateHappened, websocketConnection) const spellCheck = useApplicationState((state) => state.editorConfig.spellCheck) - // ToDo: Don't initialize new extension array here, instead refactor to global extension array const markdownExtensionsLinters = useMemo(() => { - return [ - new YoutubeMarkdownExtension(), - new VimeoMarkdownExtension(), - new SequenceDiagramMarkdownExtension(), - new LegacyShortcodesMarkdownExtension() - ] - .flatMap((extension) => extension.buildLinter()) + return optionalAppExtensions + .flatMap((extension) => extension.buildCodeMirrorLinter()) .concat(new FrontmatterLinter()) }, []) const linter = useLinter(markdownExtensionsLinters) diff --git a/src/components/editor-page/editor-pane/hooks/use-components-from-app-extensions.tsx b/src/components/editor-page/editor-pane/hooks/use-components-from-app-extensions.tsx new file mode 100644 index 000000000..56386c991 --- /dev/null +++ b/src/components/editor-page/editor-pane/hooks/use-components-from-app-extensions.tsx @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type { ReactElement } from 'react' +import React, { Fragment, useMemo } from 'react' +import { optionalAppExtensions } from '../../../../extensions/extra-integrations/optional-app-extensions' + +/** + * Generator react elements for components that are generated by the used {@link AppExtension app extensions}. + */ +export const useComponentsFromAppExtensions = (): ReactElement => { + return useMemo(() => { + return ( + + {optionalAppExtensions.map((extension, index) => + React.createElement(extension.buildEditorExtensionComponent(), { key: index }) + )} + + ) + }, []) +} diff --git a/src/components/editor-page/renderer-pane/communicator-image-lightbox.tsx b/src/components/editor-page/renderer-pane/communicator-image-lightbox.tsx deleted file mode 100644 index 41759de4c..000000000 --- a/src/components/editor-page/renderer-pane/communicator-image-lightbox.tsx +++ /dev/null @@ -1,44 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import React, { useCallback, useState } from 'react' -import { ImageLightboxModal } from '../../markdown-renderer/markdown-extension/image/image-lightbox-modal' -import type { - ImageClickedMessage, - ImageDetails -} from '../../render-page/window-post-message-communicator/rendering-message' -import { CommunicationMessageType } from '../../render-page/window-post-message-communicator/rendering-message' -import { useEditorReceiveHandler } from '../../render-page/window-post-message-communicator/hooks/use-editor-receive-handler' -import { useBooleanState } from '../../../hooks/common/use-boolean-state' - -/** - * Handles messages from the render in the iframe to open a {@link ImageLightboxModal}. - */ -export const CommunicatorImageLightbox: React.FC = () => { - const [lightboxDetails, setLightboxDetails] = useState(undefined) - const [modalVisibility, showModal, closeModal] = useBooleanState() - - useEditorReceiveHandler( - CommunicationMessageType.IMAGE_CLICKED, - useCallback( - (values: ImageClickedMessage) => { - setLightboxDetails?.(values.details) - showModal() - }, - [showModal] - ) - ) - - return ( - - ) -} diff --git a/src/components/editor-page/renderer-pane/render-iframe.tsx b/src/components/editor-page/renderer-pane/render-iframe.tsx index e48592937..53cce6448 100644 --- a/src/components/editor-page/renderer-pane/render-iframe.tsx +++ b/src/components/editor-page/renderer-pane/render-iframe.tsx @@ -3,20 +3,19 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react' +import React, { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { isTestMode } from '../../../utils/test-modes' import type { RendererProps } from '../../render-page/markdown-document' import type { + ExtensionEvent, OnFirstHeadingChangeMessage, OnHeightChangeMessage, - OnTaskCheckboxChangeMessage, RendererType, SetScrollStateMessage } from '../../render-page/window-post-message-communicator/rendering-message' import { CommunicationMessageType } from '../../render-page/window-post-message-communicator/rendering-message' import { useEditorToRendererCommunicator } from '../render-context/editor-to-renderer-communicator-context-provider' import { useForceRenderPageUrlOnIframeLoadCallback } from './hooks/use-force-render-page-url-on-iframe-load-callback' -import { CommunicatorImageLightbox } from './communicator-image-lightbox' import { useEditorReceiveHandler } from '../../render-page/window-post-message-communicator/hooks/use-editor-receive-handler' import { useSendDarkModeStatusToRenderer } from './hooks/use-send-dark-mode-status-to-renderer' import { useSendMarkdownToRenderer } from './hooks/use-send-markdown-to-renderer' @@ -24,10 +23,10 @@ import { useSendScrollState } from './hooks/use-send-scroll-state' import { Logger } from '../../../utils/logger' import { useEffectOnRenderTypeChange } from './hooks/use-effect-on-render-type-change' import { cypressAttribute, cypressId } from '../../../utils/cypress-attribute' -import { getGlobalState } from '../../../redux' import { ORIGIN, useBaseUrl } from '../../../hooks/common/use-base-url' import { ShowIf } from '../../common/show-if/show-if' import { WaitSpinner } from '../../common/wait-spinner/wait-spinner' +import { useExtensionEventEmitter } from '../../markdown-renderer/hooks/use-extension-event-emitter' export interface RenderIframeProps extends RendererProps { rendererType: RendererType @@ -58,7 +57,6 @@ const log = new Logger('RenderIframe') */ export const RenderIframe: React.FC = ({ markdownContentLines, - onTaskCheckedChange, scrollState, onFirstHeadingChange, onScroll, @@ -92,6 +90,10 @@ export const RenderIframe: React.FC = ({ } }, [iframeCommunicator, rendererReady]) + useEffect(() => { + onRendererStatusChange?.(rendererReady) + }, [onRendererStatusChange, rendererReady]) + useEditorReceiveHandler( CommunicationMessageType.ON_FIRST_HEADING_CHANGE, useCallback( @@ -105,15 +107,15 @@ export const RenderIframe: React.FC = ({ useCallback(() => onMakeScrollSource?.(), [onMakeScrollSource]) ) + const eventEmitter = useExtensionEventEmitter() + useEditorReceiveHandler( - CommunicationMessageType.ON_TASK_CHECKBOX_CHANGE, - useCallback( - (values: OnTaskCheckboxChangeMessage) => { - const lineOffset = getGlobalState().noteDetails.frontmatterRendererInfo.lineOffset - onTaskCheckedChange?.(values.lineInMarkdown + lineOffset, values.checked) - }, - [onTaskCheckedChange] - ) + CommunicationMessageType.EXTENSION_EVENT, + useMemo(() => { + return eventEmitter === undefined + ? undefined + : (values: ExtensionEvent) => eventEmitter.emit(values.eventName, values.payload) + }, [eventEmitter]) ) useEditorReceiveHandler( @@ -169,7 +171,6 @@ export const RenderIframe: React.FC = ({ return ( - diff --git a/src/components/editor-page/synced-scroll/utils.ts b/src/components/editor-page/synced-scroll/utils.ts index e79359757..2ddb38d71 100644 --- a/src/components/editor-page/synced-scroll/utils.ts +++ b/src/components/editor-page/synced-scroll/utils.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import type { LineMarkerPosition } from '../../markdown-renderer/markdown-extension/linemarker/types' +import type { LineMarkerPosition } from '../../markdown-renderer/extensions/linemarker/types' /** * Finds the {@link LineMarkerPosition line markers} from a list of given line markers that are the closest to the given line number. diff --git a/src/components/editor-page/table-of-contents/use-build-react-dom-from-toc-ast.tsx b/src/components/editor-page/table-of-contents/use-build-react-dom-from-toc-ast.tsx index c0e30c8f9..9cb80cf66 100644 --- a/src/components/editor-page/table-of-contents/use-build-react-dom-from-toc-ast.tsx +++ b/src/components/editor-page/table-of-contents/use-build-react-dom-from-toc-ast.tsx @@ -9,7 +9,7 @@ import type { ReactElement } from 'react' import React, { Fragment, useMemo } from 'react' import { ShowIf } from '../../common/show-if/show-if' import { tocSlugify } from './toc-slugify' -import { JumpAnchor } from '../../markdown-renderer/markdown-extension/link-replacer/jump-anchor' +import { JumpAnchor } from '../../markdown-renderer/extensions/link-replacer/jump-anchor' /** * Generates a React DOM part for the table of contents from the given AST of the document. diff --git a/src/components/markdown-renderer/common-markdown-renderer-props.ts b/src/components/markdown-renderer/common-markdown-renderer-props.ts index 3fb9190cb..84f25ba23 100644 --- a/src/components/markdown-renderer/common-markdown-renderer-props.ts +++ b/src/components/markdown-renderer/common-markdown-renderer-props.ts @@ -1,19 +1,14 @@ /* - * 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 { TocAst } from 'markdown-it-toc-done-right' -import type { ImageClickHandler } from './markdown-extension/image/proxy-image-replacer' import type { Ref } from 'react' export interface CommonMarkdownRendererProps { onFirstHeadingChange?: (firstHeading: string | undefined) => void - onTaskCheckedChange?: (lineInMarkdown: number, checked: boolean) => void - onTocChange?: (ast?: TocAst) => void baseUrl: string - onImageClick?: ImageClickHandler outerContainerRef?: Ref newlinesAreBreaks?: boolean lineOffset?: number diff --git a/src/components/markdown-renderer/document-markdown-renderer.tsx b/src/components/markdown-renderer/document-markdown-renderer.tsx index 60140d526..da54f047c 100644 --- a/src/components/markdown-renderer/document-markdown-renderer.tsx +++ b/src/components/markdown-renderer/document-markdown-renderer.tsx @@ -6,15 +6,15 @@ import React, { useEffect, useMemo, useRef } from 'react' import { useConvertMarkdownToReactDom } from './hooks/use-convert-markdown-to-react-dom' -import type { LineMarkerPosition } from './markdown-extension/linemarker/types' import { useTranslation } from 'react-i18next' -import type { LineMarkers } from './markdown-extension/linemarker/add-line-marker-markdown-it-plugin' import { useCalculateLineMarkerPosition } from './utils/calculate-line-marker-positions' import { useExtractFirstHeadline } from './hooks/use-extract-first-headline' import type { CommonMarkdownRendererProps } from './common-markdown-renderer-props' import { useMarkdownExtensions } from './hooks/use-markdown-extensions' -import { HeadlineAnchorsMarkdownExtension } from './markdown-extension/headline-anchors-markdown-extension' import { cypressId } from '../../utils/cypress-attribute' +import { HeadlineAnchorsMarkdownExtension } from './extensions/headline-anchors-markdown-extension' +import type { LineMarkerPosition } from './extensions/linemarker/types' +import type { LineMarkers } from './extensions/linemarker/add-line-marker-markdown-it-plugin' export interface DocumentMarkdownRendererProps extends CommonMarkdownRendererProps { onLineMarkerPositionChanged?: (lineMarkerPosition: LineMarkerPosition[]) => void @@ -39,10 +39,7 @@ export const DocumentMarkdownRenderer: React.FC = markdownContentLines, onFirstHeadingChange, onLineMarkerPositionChanged, - onTaskCheckedChange, - onTocChange, baseUrl, - onImageClick, outerContainerRef, newlinesAreBreaks }) => { @@ -52,12 +49,10 @@ export const DocumentMarkdownRenderer: React.FC = const extensions = useMarkdownExtensions( baseUrl, currentLineMarkers, - useMemo(() => [new HeadlineAnchorsMarkdownExtension()], []), - onTaskCheckedChange, - onImageClick, - onTocChange + useMemo(() => [new HeadlineAnchorsMarkdownExtension()], []) ) - const markdownReactDom = useConvertMarkdownToReactDom(markdownContentLines, extensions, newlinesAreBreaks) + + const markdownReactDom = useConvertMarkdownToReactDom(markdownContentLines, extensions, newlinesAreBreaks, true) useTranslation() useCalculateLineMarkerPosition( diff --git a/src/components/markdown-renderer/markdown-extension/code-block-markdown-extension/code-block-markdown-plugin.ts b/src/components/markdown-renderer/extensions/base/code-block-markdown-extension/code-block-markdown-plugin.ts similarity index 95% rename from src/components/markdown-renderer/markdown-extension/code-block-markdown-extension/code-block-markdown-plugin.ts rename to src/components/markdown-renderer/extensions/base/code-block-markdown-extension/code-block-markdown-plugin.ts index 4f7773b21..b13b5e694 100644 --- a/src/components/markdown-renderer/markdown-extension/code-block-markdown-extension/code-block-markdown-plugin.ts +++ b/src/components/markdown-renderer/extensions/base/code-block-markdown-extension/code-block-markdown-plugin.ts @@ -17,7 +17,7 @@ const ruleName = 'code-highlighter' * @param state The current state of the processing {@link MarkdownIt} instance. * @see MarkdownIt.RuleCore */ -const rule: RuleCore = (state) => { +const rule: RuleCore = (state): void => { state.tokens.forEach((token) => { if (token.type === 'fence') { const highlightInfos = parseCodeBlockParameters(token.info) @@ -29,7 +29,6 @@ const rule: RuleCore = (state) => { ) } }) - return true } /** @@ -37,7 +36,7 @@ const rule: RuleCore = (state) => { * * @param markdownIt The {@link MarkdownIt markdown-it instance} to which the rule should be added */ -export const codeBlockMarkdownPlugin: MarkdownIt.PluginSimple = (markdownIt) => { +export const codeBlockMarkdownPlugin: MarkdownIt.PluginSimple = (markdownIt: MarkdownIt) => { if (markdownIt.core.ruler.getRules(ruleName).length === 0) { markdownIt.core.ruler.push(ruleName, rule, { alt: [ruleName] }) } diff --git a/src/components/markdown-renderer/extensions/base/code-block-markdown-extension/code-block-markdown-renderer-extension.ts b/src/components/markdown-renderer/extensions/base/code-block-markdown-extension/code-block-markdown-renderer-extension.ts new file mode 100644 index 000000000..a3e4b9788 --- /dev/null +++ b/src/components/markdown-renderer/extensions/base/code-block-markdown-extension/code-block-markdown-renderer-extension.ts @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type MarkdownIt from 'markdown-it' +import { codeBlockMarkdownPlugin } from './code-block-markdown-plugin' +import type { ComponentReplacer } from '../../../replace-components/component-replacer' +import { MarkdownRendererExtension } from '../markdown-renderer-extension' + +/** + * A {@link MarkdownRendererExtension markdown extension} that is used for code fence replacements. + */ +export abstract class CodeBlockMarkdownRendererExtension extends MarkdownRendererExtension { + public configureMarkdownIt(markdownIt: MarkdownIt): void { + codeBlockMarkdownPlugin(markdownIt) + } + + public buildReplacers(): ComponentReplacer[] { + return [] + } +} diff --git a/src/components/markdown-renderer/markdown-extension/code-block-markdown-extension/code-block-parameters.test.ts b/src/components/markdown-renderer/extensions/base/code-block-markdown-extension/code-block-parameters.test.ts similarity index 100% rename from src/components/markdown-renderer/markdown-extension/code-block-markdown-extension/code-block-parameters.test.ts rename to src/components/markdown-renderer/extensions/base/code-block-markdown-extension/code-block-parameters.test.ts diff --git a/src/components/markdown-renderer/markdown-extension/code-block-markdown-extension/code-block-parameters.ts b/src/components/markdown-renderer/extensions/base/code-block-markdown-extension/code-block-parameters.ts similarity index 100% rename from src/components/markdown-renderer/markdown-extension/code-block-markdown-extension/code-block-parameters.ts rename to src/components/markdown-renderer/extensions/base/code-block-markdown-extension/code-block-parameters.ts diff --git a/src/components/markdown-renderer/markdown-extension/code-block-markdown-extension/find-language-by-code-block-name.test.ts b/src/components/markdown-renderer/extensions/base/code-block-markdown-extension/find-language-by-code-block-name.test.ts similarity index 100% rename from src/components/markdown-renderer/markdown-extension/code-block-markdown-extension/find-language-by-code-block-name.test.ts rename to src/components/markdown-renderer/extensions/base/code-block-markdown-extension/find-language-by-code-block-name.test.ts diff --git a/src/components/markdown-renderer/markdown-extension/code-block-markdown-extension/find-language-by-code-block-name.ts b/src/components/markdown-renderer/extensions/base/code-block-markdown-extension/find-language-by-code-block-name.ts similarity index 100% rename from src/components/markdown-renderer/markdown-extension/code-block-markdown-extension/find-language-by-code-block-name.ts rename to src/components/markdown-renderer/extensions/base/code-block-markdown-extension/find-language-by-code-block-name.ts diff --git a/src/components/markdown-renderer/markdown-extension/markdown-extension.ts b/src/components/markdown-renderer/extensions/base/markdown-renderer-extension.ts similarity index 68% rename from src/components/markdown-renderer/markdown-extension/markdown-extension.ts rename to src/components/markdown-renderer/extensions/base/markdown-renderer-extension.ts index 0663ee548..6c9b2e6f8 100644 --- a/src/components/markdown-renderer/markdown-extension/markdown-extension.ts +++ b/src/components/markdown-renderer/extensions/base/markdown-renderer-extension.ts @@ -4,15 +4,17 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +import type EventEmitter2 from 'eventemitter2' import type MarkdownIt from 'markdown-it' -import type { NodeProcessor } from '../node-preprocessors/node-processor' -import type { ComponentReplacer } from '../replace-components/component-replacer' -import type { Linter } from '../../editor-page/editor-pane/linter/linter' +import type { NodeProcessor } from '../../node-preprocessors/node-processor' +import type { ComponentReplacer } from '../../replace-components/component-replacer' /** * Base class for Markdown extensions. */ -export abstract class MarkdownExtension { +export abstract class MarkdownRendererExtension { + constructor(protected readonly eventEmitter?: EventEmitter2) {} + // eslint-disable-next-line @typescript-eslint/no-unused-vars public configureMarkdownIt(markdownIt: MarkdownIt): void { return @@ -34,8 +36,4 @@ export abstract class MarkdownExtension { public buildTagNameAllowList(): string[] { return [] } - - public buildLinter(): Linter[] { - return [] - } } diff --git a/src/components/markdown-renderer/markdown-extension/debugger-markdown-extension.ts b/src/components/markdown-renderer/extensions/debugger-markdown-extension.ts similarity index 80% rename from src/components/markdown-renderer/markdown-extension/debugger-markdown-extension.ts rename to src/components/markdown-renderer/extensions/debugger-markdown-extension.ts index 883e0b1a2..37c2a5639 100644 --- a/src/components/markdown-renderer/markdown-extension/debugger-markdown-extension.ts +++ b/src/components/markdown-renderer/extensions/debugger-markdown-extension.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { MarkdownExtension } from './markdown-extension' +import { MarkdownRendererExtension } from './base/markdown-renderer-extension' import type MarkdownIt from 'markdown-it' import { Logger } from '../../../utils/logger' import { isDevMode } from '../../../utils/test-modes' @@ -14,7 +14,7 @@ const log = new Logger('DebuggerMarkdownExtension') /** * Adds console debug logging to the markdown rendering. */ -export class DebuggerMarkdownExtension extends MarkdownExtension { +export class DebuggerMarkdownExtension extends MarkdownRendererExtension { public configureMarkdownItPost(markdownIt: MarkdownIt): void { if (isDevMode) { markdownIt.core.ruler.push('printStateToConsole', (state) => { diff --git a/src/components/markdown-renderer/markdown-extension/emoji/__snapshots__/emoji-markdown-extension.test.tsx.snap b/src/components/markdown-renderer/extensions/emoji/__snapshots__/emoji-markdown-extension.test.tsx.snap similarity index 100% rename from src/components/markdown-renderer/markdown-extension/emoji/__snapshots__/emoji-markdown-extension.test.tsx.snap rename to src/components/markdown-renderer/extensions/emoji/__snapshots__/emoji-markdown-extension.test.tsx.snap diff --git a/src/components/markdown-renderer/markdown-extension/emoji/emoji-markdown-extension.test.tsx b/src/components/markdown-renderer/extensions/emoji/emoji-markdown-extension.test.tsx similarity index 100% rename from src/components/markdown-renderer/markdown-extension/emoji/emoji-markdown-extension.test.tsx rename to src/components/markdown-renderer/extensions/emoji/emoji-markdown-extension.test.tsx diff --git a/src/components/markdown-renderer/markdown-extension/emoji/emoji-markdown-extension.ts b/src/components/markdown-renderer/extensions/emoji/emoji-markdown-extension.ts similarity index 63% rename from src/components/markdown-renderer/markdown-extension/emoji/emoji-markdown-extension.ts rename to src/components/markdown-renderer/extensions/emoji/emoji-markdown-extension.ts index ae59a02d2..6fd290617 100644 --- a/src/components/markdown-renderer/markdown-extension/emoji/emoji-markdown-extension.ts +++ b/src/components/markdown-renderer/extensions/emoji/emoji-markdown-extension.ts @@ -1,10 +1,10 @@ /* - * 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 { MarkdownExtension } from '../markdown-extension' +import { MarkdownRendererExtension } from '../base/markdown-renderer-extension' import type MarkdownIt from 'markdown-it' import emoji from 'markdown-it-emoji/bare' import { combinedEmojiData } from './mapping' @@ -12,7 +12,7 @@ import { combinedEmojiData } from './mapping' /** * Adds support for utf-8 emojis. */ -export class EmojiMarkdownExtension extends MarkdownExtension { +export class EmojiMarkdownExtension extends MarkdownRendererExtension { public configureMarkdownIt(markdownIt: MarkdownIt): void { markdownIt.use(emoji, { defs: combinedEmojiData diff --git a/src/components/markdown-renderer/markdown-extension/emoji/mapping.ts b/src/components/markdown-renderer/extensions/emoji/mapping.ts similarity index 95% rename from src/components/markdown-renderer/markdown-extension/emoji/mapping.ts rename to src/components/markdown-renderer/extensions/emoji/mapping.ts index cfedfd466..b545202cf 100644 --- a/src/components/markdown-renderer/markdown-extension/emoji/mapping.ts +++ b/src/components/markdown-renderer/extensions/emoji/mapping.ts @@ -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 */ diff --git a/src/components/markdown-renderer/markdown-extension/generic-syntax-markdown-extension.ts b/src/components/markdown-renderer/extensions/generic-syntax-markdown-extension.ts similarity index 84% rename from src/components/markdown-renderer/markdown-extension/generic-syntax-markdown-extension.ts rename to src/components/markdown-renderer/extensions/generic-syntax-markdown-extension.ts index 8fde99fb5..3ea349a44 100644 --- a/src/components/markdown-renderer/markdown-extension/generic-syntax-markdown-extension.ts +++ b/src/components/markdown-renderer/extensions/generic-syntax-markdown-extension.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { MarkdownExtension } from './markdown-extension' +import { MarkdownRendererExtension } from './base/markdown-renderer-extension' import type MarkdownIt from 'markdown-it' import abbreviation from 'markdown-it-abbr' import definitionList from 'markdown-it-deflist' @@ -18,7 +18,7 @@ import { imageSize } from '@hedgedoc/markdown-it-image-size' /** * Adds some common markdown syntaxes to the markdown rendering. */ -export class GenericSyntaxMarkdownExtension extends MarkdownExtension { +export class GenericSyntaxMarkdownExtension extends MarkdownRendererExtension { public configureMarkdownIt(markdownIt: MarkdownIt): void { abbreviation(markdownIt) definitionList(markdownIt) diff --git a/src/components/markdown-renderer/markdown-extension/headline-anchors-markdown-extension.ts b/src/components/markdown-renderer/extensions/headline-anchors-markdown-extension.ts similarity index 78% rename from src/components/markdown-renderer/markdown-extension/headline-anchors-markdown-extension.ts rename to src/components/markdown-renderer/extensions/headline-anchors-markdown-extension.ts index 554ce2c83..8f0917f68 100644 --- a/src/components/markdown-renderer/markdown-extension/headline-anchors-markdown-extension.ts +++ b/src/components/markdown-renderer/extensions/headline-anchors-markdown-extension.ts @@ -4,14 +4,14 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { MarkdownExtension } from './markdown-extension' +import { MarkdownRendererExtension } from './base/markdown-renderer-extension' import type MarkdownIt from 'markdown-it' import anchor from 'markdown-it-anchor' /** * Adds headline anchors to the markdown rendering. */ -export class HeadlineAnchorsMarkdownExtension extends MarkdownExtension { +export class HeadlineAnchorsMarkdownExtension extends MarkdownRendererExtension { public configureMarkdownIt(markdownIt: MarkdownIt): void { anchor(markdownIt, { permalink: anchor.permalink.ariaHidden({ diff --git a/src/components/markdown-renderer/markdown-extension/iframe-capsule/iframe-capsule-markdown-extension.ts b/src/components/markdown-renderer/extensions/iframe-capsule/iframe-capsule-markdown-extension.ts similarity index 76% rename from src/components/markdown-renderer/markdown-extension/iframe-capsule/iframe-capsule-markdown-extension.ts rename to src/components/markdown-renderer/extensions/iframe-capsule/iframe-capsule-markdown-extension.ts index efcbe42ae..c8059c4d7 100644 --- a/src/components/markdown-renderer/markdown-extension/iframe-capsule/iframe-capsule-markdown-extension.ts +++ b/src/components/markdown-renderer/extensions/iframe-capsule/iframe-capsule-markdown-extension.ts @@ -4,14 +4,14 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { MarkdownExtension } from '../markdown-extension' +import { MarkdownRendererExtension } from '../base/markdown-renderer-extension' import type { ComponentReplacer } from '../../replace-components/component-replacer' import { IframeCapsuleReplacer } from './iframe-capsule-replacer' /** * Adds a replacer that capsules iframes in a click shield. */ -export class IframeCapsuleMarkdownExtension extends MarkdownExtension { +export class IframeCapsuleMarkdownExtension extends MarkdownRendererExtension { public buildReplacers(): ComponentReplacer[] { return [new IframeCapsuleReplacer()] } diff --git a/src/components/markdown-renderer/markdown-extension/iframe-capsule/iframe-capsule-replacer.tsx b/src/components/markdown-renderer/extensions/iframe-capsule/iframe-capsule-replacer.tsx similarity index 100% rename from src/components/markdown-renderer/markdown-extension/iframe-capsule/iframe-capsule-replacer.tsx rename to src/components/markdown-renderer/extensions/iframe-capsule/iframe-capsule-replacer.tsx diff --git a/src/components/markdown-renderer/markdown-extension/image-placeholder/add-line-to-placeholder-image-tags.ts b/src/components/markdown-renderer/extensions/image-placeholder/add-line-to-placeholder-image-tags.ts similarity index 94% rename from src/components/markdown-renderer/markdown-extension/image-placeholder/add-line-to-placeholder-image-tags.ts rename to src/components/markdown-renderer/extensions/image-placeholder/add-line-to-placeholder-image-tags.ts index 4cc801294..346ce5cc4 100644 --- a/src/components/markdown-renderer/markdown-extension/image-placeholder/add-line-to-placeholder-image-tags.ts +++ b/src/components/markdown-renderer/extensions/image-placeholder/add-line-to-placeholder-image-tags.ts @@ -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 */ diff --git a/src/components/markdown-renderer/markdown-extension/image-placeholder/hooks/use-on-image-upload.ts b/src/components/markdown-renderer/extensions/image-placeholder/hooks/use-on-image-upload.ts similarity index 100% rename from src/components/markdown-renderer/markdown-extension/image-placeholder/hooks/use-on-image-upload.ts rename to src/components/markdown-renderer/extensions/image-placeholder/hooks/use-on-image-upload.ts diff --git a/src/components/markdown-renderer/markdown-extension/image-placeholder/hooks/use-placeholder-size-style.ts b/src/components/markdown-renderer/extensions/image-placeholder/hooks/use-placeholder-size-style.ts similarity index 92% rename from src/components/markdown-renderer/markdown-extension/image-placeholder/hooks/use-placeholder-size-style.ts rename to src/components/markdown-renderer/extensions/image-placeholder/hooks/use-placeholder-size-style.ts index 3ca8c26c7..4cbb0606d 100644 --- a/src/components/markdown-renderer/markdown-extension/image-placeholder/hooks/use-placeholder-size-style.ts +++ b/src/components/markdown-renderer/extensions/image-placeholder/hooks/use-placeholder-size-style.ts @@ -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 */ diff --git a/src/components/markdown-renderer/markdown-extension/image-placeholder/image-placeholder-markdown-extension.ts b/src/components/markdown-renderer/extensions/image-placeholder/image-placeholder-markdown-extension.ts similarity index 82% rename from src/components/markdown-renderer/markdown-extension/image-placeholder/image-placeholder-markdown-extension.ts rename to src/components/markdown-renderer/extensions/image-placeholder/image-placeholder-markdown-extension.ts index 3ebd11418..6fb815362 100644 --- a/src/components/markdown-renderer/markdown-extension/image-placeholder/image-placeholder-markdown-extension.ts +++ b/src/components/markdown-renderer/extensions/image-placeholder/image-placeholder-markdown-extension.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { MarkdownExtension } from '../markdown-extension' +import { MarkdownRendererExtension } from '../base/markdown-renderer-extension' import { addLineToPlaceholderImageTags } from './add-line-to-placeholder-image-tags' import type MarkdownIt from 'markdown-it/lib' import type { ComponentReplacer } from '../../replace-components/component-replacer' @@ -13,7 +13,7 @@ import { ImagePlaceholderReplacer } from './image-placeholder-replacer' /** * Adds support for {@link ImagePlaceholder}. */ -export class ImagePlaceholderMarkdownExtension extends MarkdownExtension { +export class ImagePlaceholderMarkdownExtension extends MarkdownRendererExtension { public static readonly PLACEHOLDER_URL = 'https://' constructor() { diff --git a/src/components/markdown-renderer/markdown-extension/image-placeholder/image-placeholder-replacer.tsx b/src/components/markdown-renderer/extensions/image-placeholder/image-placeholder-replacer.tsx similarity index 51% rename from src/components/markdown-renderer/markdown-extension/image-placeholder/image-placeholder-replacer.tsx rename to src/components/markdown-renderer/extensions/image-placeholder/image-placeholder-replacer.tsx index f7b2556f0..3dac78387 100644 --- a/src/components/markdown-renderer/markdown-extension/image-placeholder/image-placeholder-replacer.tsx +++ b/src/components/markdown-renderer/extensions/image-placeholder/image-placeholder-replacer.tsx @@ -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 '../../replace-components/component-replacer' -import { ComponentReplacer } from '../../replace-components/component-replacer' +import { ComponentReplacer, DO_NOT_REPLACE } from '../../replace-components/component-replacer' import type { Element } from 'domhandler' import { ImagePlaceholder } from './image-placeholder' import { ImagePlaceholderMarkdownExtension } from './image-placeholder-markdown-extension' @@ -25,20 +25,21 @@ export class ImagePlaceholderReplacer extends ComponentReplacer { } replace(node: Element): NodeReplacement { - if (node.name === 'img' && node.attribs && node.attribs.src === ImagePlaceholderMarkdownExtension.PLACEHOLDER_URL) { - const lineIndex = Number(node.attribs['data-line']) - const indexInLine = this.countPerSourceLine.get(lineIndex) ?? 0 - this.countPerSourceLine.set(lineIndex, indexInLine + 1) - return ( - - ) + if (node.name !== 'img' || node.attribs?.src !== ImagePlaceholderMarkdownExtension.PLACEHOLDER_URL) { + return DO_NOT_REPLACE } + const lineIndex = Number(node.attribs['data-line']) + const indexInLine = this.countPerSourceLine.get(lineIndex) ?? 0 + this.countPerSourceLine.set(lineIndex, indexInLine + 1) + return ( + + ) } } diff --git a/src/components/markdown-renderer/markdown-extension/image-placeholder/image-placeholder.module.scss b/src/components/markdown-renderer/extensions/image-placeholder/image-placeholder.module.scss similarity index 88% rename from src/components/markdown-renderer/markdown-extension/image-placeholder/image-placeholder.module.scss rename to src/components/markdown-renderer/extensions/image-placeholder/image-placeholder.module.scss index 015eeffc9..1f12e0565 100644 --- a/src/components/markdown-renderer/markdown-extension/image-placeholder/image-placeholder.module.scss +++ b/src/components/markdown-renderer/extensions/image-placeholder/image-placeholder.module.scss @@ -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 */ diff --git a/src/components/markdown-renderer/markdown-extension/image-placeholder/image-placeholder.tsx b/src/components/markdown-renderer/extensions/image-placeholder/image-placeholder.tsx similarity index 98% rename from src/components/markdown-renderer/markdown-extension/image-placeholder/image-placeholder.tsx rename to src/components/markdown-renderer/extensions/image-placeholder/image-placeholder.tsx index e522992b0..9b7b964a8 100644 --- a/src/components/markdown-renderer/markdown-extension/image-placeholder/image-placeholder.tsx +++ b/src/components/markdown-renderer/extensions/image-placeholder/image-placeholder.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 */ diff --git a/src/components/markdown-renderer/markdown-extension/image-placeholder/utils/build-placeholder-size-css.test.ts b/src/components/markdown-renderer/extensions/image-placeholder/utils/build-placeholder-size-css.test.ts similarity index 97% rename from src/components/markdown-renderer/markdown-extension/image-placeholder/utils/build-placeholder-size-css.test.ts rename to src/components/markdown-renderer/extensions/image-placeholder/utils/build-placeholder-size-css.test.ts index cb467ccb7..02836dd2a 100644 --- a/src/components/markdown-renderer/markdown-extension/image-placeholder/utils/build-placeholder-size-css.test.ts +++ b/src/components/markdown-renderer/extensions/image-placeholder/utils/build-placeholder-size-css.test.ts @@ -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 */ diff --git a/src/components/markdown-renderer/markdown-extension/image-placeholder/utils/build-placeholder-size-css.ts b/src/components/markdown-renderer/extensions/image-placeholder/utils/build-placeholder-size-css.ts similarity index 96% rename from src/components/markdown-renderer/markdown-extension/image-placeholder/utils/build-placeholder-size-css.ts rename to src/components/markdown-renderer/extensions/image-placeholder/utils/build-placeholder-size-css.ts index 2621e6c12..26caddbd0 100644 --- a/src/components/markdown-renderer/markdown-extension/image-placeholder/utils/build-placeholder-size-css.ts +++ b/src/components/markdown-renderer/extensions/image-placeholder/utils/build-placeholder-size-css.ts @@ -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 */ diff --git a/src/components/markdown-renderer/extensions/image/communicator-image-lightbox.tsx b/src/components/markdown-renderer/extensions/image/communicator-image-lightbox.tsx new file mode 100644 index 000000000..3e0f211f7 --- /dev/null +++ b/src/components/markdown-renderer/extensions/image/communicator-image-lightbox.tsx @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type { ImageDetails } from '../../../render-page/window-post-message-communicator/rendering-message' +import React, { useCallback, useState } from 'react' +import { useBooleanState } from '../../../../hooks/common/use-boolean-state' +import { ImageLightboxModal } from './image-lightbox-modal' +import { useExtensionEventEmitterHandler } from '../../hooks/use-extension-event-emitter' +import { SHOW_IMAGE_LIGHTBOX_EVENT_NAME } from './event-emitting-proxy-image-frame' + +/** + * Handles messages from the render in the iframe to open a {@link ImageLightboxModal}. + */ +export const CommunicatorImageLightbox: React.FC = () => { + const [lightboxDetails, setLightboxDetails] = useState(undefined) + const [modalVisibility, showModal, closeModal] = useBooleanState() + + const handler = useCallback( + (values: ImageDetails) => { + setLightboxDetails?.(values) + showModal() + }, + [showModal] + ) + + useExtensionEventEmitterHandler(SHOW_IMAGE_LIGHTBOX_EVENT_NAME, handler) + + return ( + + ) +} diff --git a/src/components/markdown-renderer/extensions/image/event-emitting-proxy-image-frame.tsx b/src/components/markdown-renderer/extensions/image/event-emitting-proxy-image-frame.tsx new file mode 100644 index 000000000..dea8cf046 --- /dev/null +++ b/src/components/markdown-renderer/extensions/image/event-emitting-proxy-image-frame.tsx @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import React, { useCallback } from 'react' +import { useExtensionEventEmitter } from '../../hooks/use-extension-event-emitter' +import { ProxyImageFrame } from './proxy-image-frame' +import type { ImageDetails } from '../../../render-page/window-post-message-communicator/rendering-message' + +type EventEmittingProxyImageFrameProps = Omit, 'onClick'> + +export const SHOW_IMAGE_LIGHTBOX_EVENT_NAME = 'ImageClick' + +/** + * Renders a {@link ProxyImageFrame} but claims the `onClick` event to send image information to the current event emitter. + * + * @param props props that will be forwarded to the inner image frame + */ +export const EventEmittingProxyImageFrame: React.FC = (props) => { + const eventEmitter = useExtensionEventEmitter() + + const onClick = useCallback( + (event: React.MouseEvent) => { + const image = event.target as HTMLImageElement + if (image.src === '') { + return + } + eventEmitter?.emit(SHOW_IMAGE_LIGHTBOX_EVENT_NAME, { + src: image.src, + alt: image.alt, + title: image.title + } as ImageDetails) + }, + [eventEmitter] + ) + + return +} diff --git a/src/components/markdown-renderer/markdown-extension/image/image-lightbox-modal.tsx b/src/components/markdown-renderer/extensions/image/image-lightbox-modal.tsx similarity index 100% rename from src/components/markdown-renderer/markdown-extension/image/image-lightbox-modal.tsx rename to src/components/markdown-renderer/extensions/image/image-lightbox-modal.tsx diff --git a/src/components/markdown-renderer/markdown-extension/image/lightbox.module.scss b/src/components/markdown-renderer/extensions/image/lightbox.module.scss similarity index 72% rename from src/components/markdown-renderer/markdown-extension/image/lightbox.module.scss rename to src/components/markdown-renderer/extensions/image/lightbox.module.scss index 3b84927c2..be12a7024 100644 --- a/src/components/markdown-renderer/markdown-extension/image/lightbox.module.scss +++ b/src/components/markdown-renderer/extensions/image/lightbox.module.scss @@ -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 */ diff --git a/src/components/markdown-renderer/markdown-extension/image/proxy-image-frame.tsx b/src/components/markdown-renderer/extensions/image/proxy-image-frame.tsx similarity index 96% rename from src/components/markdown-renderer/markdown-extension/image/proxy-image-frame.tsx rename to src/components/markdown-renderer/extensions/image/proxy-image-frame.tsx index d94303578..3ba9a211f 100644 --- a/src/components/markdown-renderer/markdown-extension/image/proxy-image-frame.tsx +++ b/src/components/markdown-renderer/extensions/image/proxy-image-frame.tsx @@ -32,7 +32,7 @@ export const ProxyImageFrame: React.FC .catch((err) => log.error(err)) }, [imageProxyEnabled, src]) - // The next image processor works with a whitelist of origins. Therefore we can't use it for general images. + // The next image processor works with a whitelist of origins. Therefore, we can't use it for general images. // eslint-disable-next-line @next/next/no-img-element return } diff --git a/src/components/markdown-renderer/extensions/image/proxy-image-markdown-extension.ts b/src/components/markdown-renderer/extensions/image/proxy-image-markdown-extension.ts new file mode 100644 index 000000000..8c43ef65b --- /dev/null +++ b/src/components/markdown-renderer/extensions/image/proxy-image-markdown-extension.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { MarkdownRendererExtension } from '../base/markdown-renderer-extension' +import type { ComponentReplacer } from '../../replace-components/component-replacer' +import { ProxyImageReplacer } from './proxy-image-replacer' + +/** + * Adds support for image lightbox and image proxy redirection. + */ +export class ProxyImageMarkdownExtension extends MarkdownRendererExtension { + buildReplacers(): ComponentReplacer[] { + return [new ProxyImageReplacer()] + } +} diff --git a/src/components/markdown-renderer/markdown-extension/image/proxy-image-replacer.tsx b/src/components/markdown-renderer/extensions/image/proxy-image-replacer.tsx similarity index 78% rename from src/components/markdown-renderer/markdown-extension/image/proxy-image-replacer.tsx rename to src/components/markdown-renderer/extensions/image/proxy-image-replacer.tsx index e37c8c717..9a4cbc7f7 100644 --- a/src/components/markdown-renderer/markdown-extension/image/proxy-image-replacer.tsx +++ b/src/components/markdown-renderer/extensions/image/proxy-image-replacer.tsx @@ -8,7 +8,7 @@ import type { Element } 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 { ProxyImageFrame } from './proxy-image-frame' +import { EventEmittingProxyImageFrame } from './event-emitting-proxy-image-frame' export type ImageClickHandler = (event: React.MouseEvent) => void @@ -16,18 +16,11 @@ export type ImageClickHandler = (event: React.MouseEvent ) } diff --git a/src/components/markdown-renderer/markdown-extension/linemarker/add-line-marker-markdown-it-plugin.ts b/src/components/markdown-renderer/extensions/linemarker/add-line-marker-markdown-it-plugin.ts similarity index 100% rename from src/components/markdown-renderer/markdown-extension/linemarker/add-line-marker-markdown-it-plugin.ts rename to src/components/markdown-renderer/extensions/linemarker/add-line-marker-markdown-it-plugin.ts diff --git a/src/components/markdown-renderer/markdown-extension/linemarker/linemarker-markdown-extension.ts b/src/components/markdown-renderer/extensions/linemarker/linemarker-markdown-extension.ts similarity index 86% rename from src/components/markdown-renderer/markdown-extension/linemarker/linemarker-markdown-extension.ts rename to src/components/markdown-renderer/extensions/linemarker/linemarker-markdown-extension.ts index e139824a4..013ad2e0c 100644 --- a/src/components/markdown-renderer/markdown-extension/linemarker/linemarker-markdown-extension.ts +++ b/src/components/markdown-renderer/extensions/linemarker/linemarker-markdown-extension.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { MarkdownExtension } from '../markdown-extension' +import { MarkdownRendererExtension } from '../base/markdown-renderer-extension' import { LinemarkerReplacer } from './linemarker-replacer' import type { ComponentReplacer } from '../../replace-components/component-replacer' import type { LineMarkers } from './add-line-marker-markdown-it-plugin' @@ -14,7 +14,7 @@ import type MarkdownIt from 'markdown-it' /** * Adds support for the generation of line marker elements which are needed for synced scrolling. */ -export class LinemarkerMarkdownExtension extends MarkdownExtension { +export class LinemarkerMarkdownExtension extends MarkdownRendererExtension { public static readonly tagName = 'app-linemarker' constructor(private onLineMarkers?: (lineMarkers: LineMarkers[]) => void) { diff --git a/src/components/markdown-renderer/markdown-extension/linemarker/linemarker-replacer.tsx b/src/components/markdown-renderer/extensions/linemarker/linemarker-replacer.tsx similarity index 100% rename from src/components/markdown-renderer/markdown-extension/linemarker/linemarker-replacer.tsx rename to src/components/markdown-renderer/extensions/linemarker/linemarker-replacer.tsx diff --git a/src/components/markdown-renderer/markdown-extension/linemarker/types.d.ts b/src/components/markdown-renderer/extensions/linemarker/types.d.ts similarity index 75% rename from src/components/markdown-renderer/markdown-extension/linemarker/types.d.ts rename to src/components/markdown-renderer/extensions/linemarker/types.d.ts index 3ef2e8544..44566a65a 100644 --- a/src/components/markdown-renderer/markdown-extension/linemarker/types.d.ts +++ b/src/components/markdown-renderer/extensions/linemarker/types.d.ts @@ -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 */ diff --git a/src/components/markdown-renderer/markdown-extension/link-replacer/anchor-node-preprocessor.ts b/src/components/markdown-renderer/extensions/link-replacer/anchor-node-preprocessor.ts similarity index 100% rename from src/components/markdown-renderer/markdown-extension/link-replacer/anchor-node-preprocessor.ts rename to src/components/markdown-renderer/extensions/link-replacer/anchor-node-preprocessor.ts diff --git a/src/components/markdown-renderer/markdown-extension/link-replacer/jump-anchor-replacer.tsx b/src/components/markdown-renderer/extensions/link-replacer/jump-anchor-replacer.tsx similarity index 100% rename from src/components/markdown-renderer/markdown-extension/link-replacer/jump-anchor-replacer.tsx rename to src/components/markdown-renderer/extensions/link-replacer/jump-anchor-replacer.tsx diff --git a/src/components/markdown-renderer/markdown-extension/link-replacer/jump-anchor.tsx b/src/components/markdown-renderer/extensions/link-replacer/jump-anchor.tsx similarity index 100% rename from src/components/markdown-renderer/markdown-extension/link-replacer/jump-anchor.tsx rename to src/components/markdown-renderer/extensions/link-replacer/jump-anchor.tsx diff --git a/src/components/markdown-renderer/markdown-extension/link-replacer/link-adjustment-markdown-extension.ts b/src/components/markdown-renderer/extensions/link-replacer/link-adjustment-markdown-extension.ts similarity index 75% rename from src/components/markdown-renderer/markdown-extension/link-replacer/link-adjustment-markdown-extension.ts rename to src/components/markdown-renderer/extensions/link-replacer/link-adjustment-markdown-extension.ts index ef9956bd1..183895d1f 100644 --- a/src/components/markdown-renderer/markdown-extension/link-replacer/link-adjustment-markdown-extension.ts +++ b/src/components/markdown-renderer/extensions/link-replacer/link-adjustment-markdown-extension.ts @@ -1,10 +1,10 @@ /* - * 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 { MarkdownExtension } from '../markdown-extension' +import { MarkdownRendererExtension } from '../base/markdown-renderer-extension' import { JumpAnchorReplacer } from './jump-anchor-replacer' import type { ComponentReplacer } from '../../replace-components/component-replacer' import type { NodeProcessor } from '../../node-preprocessors/node-processor' @@ -13,7 +13,7 @@ import { AnchorNodePreprocessor } from './anchor-node-preprocessor' /** * Adds tweaks for anchor tags which are needed for the use in the secured iframe. */ -export class LinkAdjustmentMarkdownExtension extends MarkdownExtension { +export class LinkAdjustmentMarkdownExtension extends MarkdownRendererExtension { constructor(private baseUrl: string) { super() } diff --git a/src/components/markdown-renderer/markdown-extension/__snapshots__/linkify-fix-markdown-extension.test.tsx.snap b/src/components/markdown-renderer/extensions/linkify-fix/__snapshots__/linkify-fix-markdown-extension.test.tsx.snap similarity index 100% rename from src/components/markdown-renderer/markdown-extension/__snapshots__/linkify-fix-markdown-extension.test.tsx.snap rename to src/components/markdown-renderer/extensions/linkify-fix/__snapshots__/linkify-fix-markdown-extension.test.tsx.snap diff --git a/src/components/markdown-renderer/markdown-extension/linkify-fix-markdown-extension.test.tsx b/src/components/markdown-renderer/extensions/linkify-fix/linkify-fix-markdown-extension.test.tsx similarity index 82% rename from src/components/markdown-renderer/markdown-extension/linkify-fix-markdown-extension.test.tsx rename to src/components/markdown-renderer/extensions/linkify-fix/linkify-fix-markdown-extension.test.tsx index 72c45de35..a84cb7e84 100644 --- a/src/components/markdown-renderer/markdown-extension/linkify-fix-markdown-extension.test.tsx +++ b/src/components/markdown-renderer/extensions/linkify-fix/linkify-fix-markdown-extension.test.tsx @@ -4,9 +4,9 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { mockI18n } from '../test-utils/mock-i18n' +import { mockI18n } from '../../test-utils/mock-i18n' import { render } from '@testing-library/react' -import { TestMarkdownRenderer } from '../test-utils/test-markdown-renderer' +import { TestMarkdownRenderer } from '../../test-utils/test-markdown-renderer' import { LinkifyFixMarkdownExtension } from './linkify-fix-markdown-extension' describe('Linkify markdown extensions', () => { diff --git a/src/components/markdown-renderer/markdown-extension/linkify-fix-markdown-extension.ts b/src/components/markdown-renderer/extensions/linkify-fix/linkify-fix-markdown-extension.ts similarity index 80% rename from src/components/markdown-renderer/markdown-extension/linkify-fix-markdown-extension.ts rename to src/components/markdown-renderer/extensions/linkify-fix/linkify-fix-markdown-extension.ts index 1cdeff8c9..2a9fc7e07 100644 --- a/src/components/markdown-renderer/markdown-extension/linkify-fix-markdown-extension.ts +++ b/src/components/markdown-renderer/extensions/linkify-fix/linkify-fix-markdown-extension.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { MarkdownExtension } from './markdown-extension' +import { MarkdownRendererExtension } from '../base/markdown-renderer-extension' import linkify from 'markdown-it/lib/rules_core/linkify' import type MarkdownIt from 'markdown-it' import tlds from 'tlds' @@ -12,7 +12,7 @@ import tlds from 'tlds' /** * A markdown extension that detects plain text URLs and converts them into links. */ -export class LinkifyFixMarkdownExtension extends MarkdownExtension { +export class LinkifyFixMarkdownExtension extends MarkdownRendererExtension { public configureMarkdownItPost(markdownIt: MarkdownIt): void { markdownIt.linkify.tlds(tlds) markdownIt.core.ruler.push('linkify', (state) => { diff --git a/src/components/markdown-renderer/markdown-extension/reveal/process-reveal-comment-nodes.ts b/src/components/markdown-renderer/extensions/reveal/process-reveal-comment-nodes.ts similarity index 100% rename from src/components/markdown-renderer/markdown-extension/reveal/process-reveal-comment-nodes.ts rename to src/components/markdown-renderer/extensions/reveal/process-reveal-comment-nodes.ts diff --git a/src/components/markdown-renderer/markdown-extension/reveal/reveal-markdown-extension.ts b/src/components/markdown-renderer/extensions/reveal/reveal-markdown-extension.ts similarity index 77% rename from src/components/markdown-renderer/markdown-extension/reveal/reveal-markdown-extension.ts rename to src/components/markdown-renderer/extensions/reveal/reveal-markdown-extension.ts index 9c3e525fd..5fc4ee53a 100644 --- a/src/components/markdown-renderer/markdown-extension/reveal/reveal-markdown-extension.ts +++ b/src/components/markdown-renderer/extensions/reveal/reveal-markdown-extension.ts @@ -1,10 +1,10 @@ /* - * 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 { MarkdownExtension } from '../markdown-extension' +import { MarkdownRendererExtension } from '../base/markdown-renderer-extension' import type MarkdownIt from 'markdown-it' import { addSlideSectionsMarkdownItPlugin } from './reveal-sections' import { RevealCommentCommandNodePreprocessor } from './process-reveal-comment-nodes' @@ -14,7 +14,7 @@ import type { NodeProcessor } from '../../node-preprocessors/node-processor' * Adds support for reveal.js to the markdown rendering. * This includes the generation of sections and the manipulation of elements using reveal comments. */ -export class RevealMarkdownExtension extends MarkdownExtension { +export class RevealMarkdownExtension extends MarkdownRendererExtension { public configureMarkdownIt(markdownIt: MarkdownIt): void { addSlideSectionsMarkdownItPlugin(markdownIt) } diff --git a/src/components/markdown-renderer/markdown-extension/reveal/reveal-sections.ts b/src/components/markdown-renderer/extensions/reveal/reveal-sections.ts similarity index 97% rename from src/components/markdown-renderer/markdown-extension/reveal/reveal-sections.ts rename to src/components/markdown-renderer/extensions/reveal/reveal-sections.ts index 1a0c5069e..a4f23e5e8 100644 --- a/src/components/markdown-renderer/markdown-extension/reveal/reveal-sections.ts +++ b/src/components/markdown-renderer/extensions/reveal/reveal-sections.ts @@ -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 */ diff --git a/src/components/markdown-renderer/markdown-extension/sanitizer/dom-purifier-node-preprocessor.ts b/src/components/markdown-renderer/extensions/sanitizer/dom-purifier-node-preprocessor.ts similarity index 100% rename from src/components/markdown-renderer/markdown-extension/sanitizer/dom-purifier-node-preprocessor.ts rename to src/components/markdown-renderer/extensions/sanitizer/dom-purifier-node-preprocessor.ts diff --git a/src/components/markdown-renderer/markdown-extension/sanitizer/sanitizer-markdown-extension.ts b/src/components/markdown-renderer/extensions/sanitizer/sanitizer-markdown-extension.ts similarity index 69% rename from src/components/markdown-renderer/markdown-extension/sanitizer/sanitizer-markdown-extension.ts rename to src/components/markdown-renderer/extensions/sanitizer/sanitizer-markdown-extension.ts index 545bdbc0e..d66b8f31c 100644 --- a/src/components/markdown-renderer/markdown-extension/sanitizer/sanitizer-markdown-extension.ts +++ b/src/components/markdown-renderer/extensions/sanitizer/sanitizer-markdown-extension.ts @@ -1,17 +1,17 @@ /* - * 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 { MarkdownExtension } from '../markdown-extension' import { SanitizerNodePreprocessor } from './dom-purifier-node-preprocessor' import type { NodeProcessor } from '../../node-preprocessors/node-processor' +import { MarkdownRendererExtension } from '../base/markdown-renderer-extension' /** * Adds support for html sanitizing using dompurify to the markdown rendering. */ -export class SanitizerMarkdownExtension extends MarkdownExtension { +export class SanitizerMarkdownExtension extends MarkdownRendererExtension { constructor(private tagNameWhiteList: string[]) { super() } diff --git a/src/components/markdown-renderer/markdown-extension/table-of-contents-markdown-extension.ts b/src/components/markdown-renderer/extensions/table-of-contents-markdown-extension.ts similarity index 59% rename from src/components/markdown-renderer/markdown-extension/table-of-contents-markdown-extension.ts rename to src/components/markdown-renderer/extensions/table-of-contents-markdown-extension.ts index 62892d9fc..04b7d83e3 100644 --- a/src/components/markdown-renderer/markdown-extension/table-of-contents-markdown-extension.ts +++ b/src/components/markdown-renderer/extensions/table-of-contents-markdown-extension.ts @@ -4,29 +4,31 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { MarkdownExtension } from './markdown-extension' import type MarkdownIt from 'markdown-it' import type { TocAst } from 'markdown-it-toc-done-right' import toc from 'markdown-it-toc-done-right' import { tocSlugify } from '../../editor-page/table-of-contents/toc-slugify' +import { MarkdownRendererExtension } from './base/markdown-renderer-extension' +import equal from 'fast-deep-equal' + /** * Adds table of content to the markdown rendering. */ -export class TableOfContentsMarkdownExtension extends MarkdownExtension { - constructor(private onTocChange?: (ast: TocAst) => void) { - super() - } +export class TableOfContentsMarkdownExtension extends MarkdownRendererExtension { + public static readonly EVENT_NAME = 'TocChange' + private lastAst: TocAst | undefined = undefined public configureMarkdownIt(markdownIt: MarkdownIt): void { - if (!this.onTocChange) { - return - } toc(markdownIt, { placeholder: '(\\[TOC\\]|\\[toc\\])', listType: 'ul', level: [1, 2, 3], callback: (code: string, ast: TocAst): void => { - this.onTocChange?.(ast) + if (equal(ast, this.lastAst)) { + return + } + this.lastAst = ast + this.eventEmitter?.emit(TableOfContentsMarkdownExtension.EVENT_NAME, ast) }, slugify: tocSlugify }) diff --git a/src/components/markdown-renderer/markdown-extension/upload-indicating-image-frame/upload-indicating-frame.tsx b/src/components/markdown-renderer/extensions/upload-indicating-image-frame/upload-indicating-frame.tsx similarity index 94% rename from src/components/markdown-renderer/markdown-extension/upload-indicating-image-frame/upload-indicating-frame.tsx rename to src/components/markdown-renderer/extensions/upload-indicating-image-frame/upload-indicating-frame.tsx index 72a99de57..90454cb98 100644 --- a/src/components/markdown-renderer/markdown-extension/upload-indicating-image-frame/upload-indicating-frame.tsx +++ b/src/components/markdown-renderer/extensions/upload-indicating-image-frame/upload-indicating-frame.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 */ diff --git a/src/components/markdown-renderer/markdown-extension/upload-indicating-image-frame/upload-indicating-image-frame-markdown-extension.ts b/src/components/markdown-renderer/extensions/upload-indicating-image-frame/upload-indicating-image-frame-markdown-extension.ts similarity index 75% rename from src/components/markdown-renderer/markdown-extension/upload-indicating-image-frame/upload-indicating-image-frame-markdown-extension.ts rename to src/components/markdown-renderer/extensions/upload-indicating-image-frame/upload-indicating-image-frame-markdown-extension.ts index c24c4bcc7..ff6d2efb0 100644 --- a/src/components/markdown-renderer/markdown-extension/upload-indicating-image-frame/upload-indicating-image-frame-markdown-extension.ts +++ b/src/components/markdown-renderer/extensions/upload-indicating-image-frame/upload-indicating-image-frame-markdown-extension.ts @@ -1,17 +1,17 @@ /* - * 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 { MarkdownExtension } from '../markdown-extension' +import { MarkdownRendererExtension } from '../base/markdown-renderer-extension' import type { ComponentReplacer } from '../../replace-components/component-replacer' import { UploadIndicatingImageFrameReplacer } from './upload-indicating-image-frame-replacer' /** * A markdown extension that shows {@link UploadIndicatingFrame} for images that are getting uploaded. */ -export class UploadIndicatingImageFrameMarkdownExtension extends MarkdownExtension { +export class UploadIndicatingImageFrameMarkdownExtension extends MarkdownRendererExtension { buildReplacers(): ComponentReplacer[] { return [new UploadIndicatingImageFrameReplacer()] } diff --git a/src/components/markdown-renderer/markdown-extension/upload-indicating-image-frame/upload-indicating-image-frame-replacer.tsx b/src/components/markdown-renderer/extensions/upload-indicating-image-frame/upload-indicating-image-frame-replacer.tsx similarity index 100% rename from src/components/markdown-renderer/markdown-extension/upload-indicating-image-frame/upload-indicating-image-frame-replacer.tsx rename to src/components/markdown-renderer/extensions/upload-indicating-image-frame/upload-indicating-image-frame-replacer.tsx diff --git a/src/components/markdown-renderer/hooks/use-combined-node-preprocessor.ts b/src/components/markdown-renderer/hooks/use-combined-node-preprocessor.ts new file mode 100644 index 000000000..ebfb8ebac --- /dev/null +++ b/src/components/markdown-renderer/hooks/use-combined-node-preprocessor.ts @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { useMemo } from 'react' +import type { Document } from 'domhandler' +import type { MarkdownRendererExtension } from '../extensions/base/markdown-renderer-extension' + +/** + * Creates a function that applies the node preprocessors of every given {@link MarkdownRendererExtension} to a {@link Document}. + * + * @param extensions The extensions who provide node processors + * @return The created apply function + */ +export const useCombinedNodePreprocessor = (extensions: MarkdownRendererExtension[]): ((nodes: Document) => Document) => + useMemo(() => { + return extensions + .flatMap((extension) => extension.buildNodeProcessors()) + .reduce( + (state, processor) => (document: Document) => state(processor.process(document)), + (document: Document) => document + ) + }, [extensions]) diff --git a/src/components/markdown-renderer/hooks/use-configured-markdown-it.ts b/src/components/markdown-renderer/hooks/use-configured-markdown-it.ts new file mode 100644 index 000000000..ce2f66d43 --- /dev/null +++ b/src/components/markdown-renderer/hooks/use-configured-markdown-it.ts @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { useMemo } from 'react' +import MarkdownIt from 'markdown-it/lib' +import type { MarkdownRendererExtension } from '../extensions/base/markdown-renderer-extension' + +/** + * Creates a new {@link MarkdownIt markdown-it instance} and configures it using the given {@link MarkdownRendererExtension markdown renderer extensions}. + * + * @param extensions The extensions that configure the new markdown-it instance + * @param allowHtml Defines if html in markdown is allowed + * @param newlinesAreBreaks Defines if new lines should be treated as line breaks or paragraphs + * @return the created markdown-it instance + */ +export const useConfiguredMarkdownIt = ( + extensions: MarkdownRendererExtension[], + allowHtml: boolean, + newlinesAreBreaks: boolean +): MarkdownIt => { + return useMemo(() => { + const newMarkdownIt = new MarkdownIt('default', { + html: allowHtml, + breaks: newlinesAreBreaks, + langPrefix: '', + typographer: true + }) + extensions.forEach((extension) => newMarkdownIt.use((markdownIt) => extension.configureMarkdownIt(markdownIt))) + extensions.forEach((extension) => newMarkdownIt.use((markdownIt) => extension.configureMarkdownItPost(markdownIt))) + return newMarkdownIt + }, [allowHtml, extensions, newlinesAreBreaks]) +} diff --git a/src/components/markdown-renderer/hooks/use-convert-markdown-to-react-dom.ts b/src/components/markdown-renderer/hooks/use-convert-markdown-to-react-dom.ts deleted file mode 100644 index d829ff861..000000000 --- a/src/components/markdown-renderer/hooks/use-convert-markdown-to-react-dom.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import MarkdownIt from 'markdown-it/lib' -import { useMemo } from 'react' -import type { ValidReactDomElement } from '../replace-components/component-replacer' -import convertHtmlToReact from '@hedgedoc/html-to-react' -import { NodeToReactTransformer } from '../utils/node-to-react-transformer' -import { LineIdMapper } from '../utils/line-id-mapper' -import type { MarkdownExtension } from '../markdown-extension/markdown-extension' -import { MarkdownExtensionCollection } from '../markdown-extension/markdown-extension-collection' - -/** - * Renders Markdown-Code into react elements. - * - * @param markdownContentLines The markdown code lines that should be rendered - * @param additionalMarkdownExtensions A list of {@link MarkdownExtension markdown extensions} that should be used - * @param newlinesAreBreaks Defines if the alternative break mode of markdown it should be used - * @param allowHtml Defines if html is allowed in markdown - * @return The React DOM that represents the rendered markdown code - */ -export const useConvertMarkdownToReactDom = ( - markdownContentLines: string[], - additionalMarkdownExtensions: MarkdownExtension[], - newlinesAreBreaks = true, - allowHtml = true -): ValidReactDomElement[] => { - const lineNumberMapper = useMemo(() => new LineIdMapper(), []) - const htmlToReactTransformer = useMemo(() => new NodeToReactTransformer(), []) - const markdownExtensions = useMemo( - () => new MarkdownExtensionCollection(additionalMarkdownExtensions), - [additionalMarkdownExtensions] - ) - - const markdownIt = useMemo(() => { - const newMarkdownIt = new MarkdownIt('default', { - html: allowHtml, - breaks: newlinesAreBreaks, - langPrefix: '', - typographer: true - }) - markdownExtensions.configureMarkdownIt(newMarkdownIt) - return newMarkdownIt - }, [allowHtml, markdownExtensions, newlinesAreBreaks]) - - useMemo(() => { - htmlToReactTransformer.setReplacers(markdownExtensions.buildReplacers()) - }, [htmlToReactTransformer, markdownExtensions]) - - useMemo(() => { - htmlToReactTransformer.setLineIds(lineNumberMapper.updateLineMapping(markdownContentLines)) - }, [htmlToReactTransformer, lineNumberMapper, markdownContentLines]) - - const nodePreProcessor = useMemo(() => markdownExtensions.buildFlatNodeProcessor(), [markdownExtensions]) - - return useMemo(() => { - const html = markdownIt.render(markdownContentLines.join('\n')) - htmlToReactTransformer.resetReplacers() - - return convertHtmlToReact(html, { - transform: (node, index) => htmlToReactTransformer.translateNodeToReactElement(node, index), - preprocessNodes: (document) => nodePreProcessor(document) - }) - }, [htmlToReactTransformer, markdownContentLines, markdownIt, nodePreProcessor]) -} diff --git a/src/components/markdown-renderer/hooks/use-convert-markdown-to-react-dom.tsx b/src/components/markdown-renderer/hooks/use-convert-markdown-to-react-dom.tsx new file mode 100644 index 000000000..4522c29f0 --- /dev/null +++ b/src/components/markdown-renderer/hooks/use-convert-markdown-to-react-dom.tsx @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import React, { Fragment, useMemo } from 'react' +import type { ValidReactDomElement } from '../replace-components/component-replacer' +import convertHtmlToReact from '@hedgedoc/html-to-react' +import { NodeToReactTransformer } from '../utils/node-to-react-transformer' +import { LineIdMapper } from '../utils/line-id-mapper' +import type { MarkdownRendererExtension } from '../extensions/base/markdown-renderer-extension' +import { SanitizerMarkdownExtension } from '../extensions/sanitizer/sanitizer-markdown-extension' +import { useCombinedNodePreprocessor } from './use-combined-node-preprocessor' +import { useConfiguredMarkdownIt } from './use-configured-markdown-it' + +/** + * Renders Markdown-Code into react elements. + * + * @param markdownContentLines The Markdown code lines that should be rendered + * @param additionalMarkdownExtensions A list of {@link MarkdownRendererExtension markdown extensions} that should be used + * @param newlinesAreBreaks Defines if the alternative break mode of markdown it should be used + * @param allowHtml Defines if html is allowed in markdown + * @return The React DOM that represents the rendered Markdown code + */ +export const useConvertMarkdownToReactDom = ( + markdownContentLines: string[], + additionalMarkdownExtensions: MarkdownRendererExtension[], + newlinesAreBreaks = true, + allowHtml = true +): ValidReactDomElement => { + const lineNumberMapper = useMemo(() => new LineIdMapper(), []) + const htmlToReactTransformer = useMemo(() => new NodeToReactTransformer(), []) + const markdownExtensions = useMemo(() => { + const tagWhiteLists = additionalMarkdownExtensions.flatMap((extension) => extension.buildTagNameAllowList()) + return [...additionalMarkdownExtensions, new SanitizerMarkdownExtension(tagWhiteLists)] + }, [additionalMarkdownExtensions]) + + useMemo(() => { + htmlToReactTransformer.setReplacers(markdownExtensions.flatMap((extension) => extension.buildReplacers())) + }, [htmlToReactTransformer, markdownExtensions]) + + useMemo(() => { + htmlToReactTransformer.setLineIds(lineNumberMapper.updateLineMapping(markdownContentLines)) + }, [htmlToReactTransformer, lineNumberMapper, markdownContentLines]) + + const nodePreProcessor = useCombinedNodePreprocessor(markdownExtensions) + const markdownIt = useConfiguredMarkdownIt(markdownExtensions, allowHtml, newlinesAreBreaks) + + return useMemo(() => { + const html = markdownIt.render(markdownContentLines.join('\n')) + htmlToReactTransformer.resetReplacers() + + return ( + + {convertHtmlToReact(html, { + transform: (node, index) => htmlToReactTransformer.translateNodeToReactElement(node, index), + preprocessNodes: (document) => nodePreProcessor(document) + })} + + ) + }, [htmlToReactTransformer, markdownContentLines, markdownIt, nodePreProcessor]) +} diff --git a/src/components/markdown-renderer/hooks/use-extension-event-emitter.tsx b/src/components/markdown-renderer/hooks/use-extension-event-emitter.tsx new file mode 100644 index 000000000..cdf002505 --- /dev/null +++ b/src/components/markdown-renderer/hooks/use-extension-event-emitter.tsx @@ -0,0 +1,51 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type { PropsWithChildren } from 'react' +import React, { createContext, useContext, useEffect, useMemo } from 'react' +import EventEmitter2 from 'eventemitter2' + +export const eventEmitterContext = createContext(undefined) + +/** + * Provides the {@link EventEmitter2 event emitter} from the current {@link eventEmitterContext context}. + */ +export const useExtensionEventEmitter = () => { + return useContext(eventEmitterContext) +} + +/** + * Creates a new {@link EventEmitter2 event emitter} and provides it as {@link eventEmitterContext context}. + * + * @param children The elements that should receive the context value + */ +export const ExtensionEventEmitterProvider: React.FC = ({ children }) => { + const eventEmitter = useMemo(() => new EventEmitter2(), []) + return {children} +} + +/** + * Registers a handler callback on the current {@link EventEmitter2 event emitter} that is provided in the {@link eventEmitterContext context}. + * + * @param eventName The name of the event which should be subscribed + * @param handler The callback that should be executed. If undefined the event will be unsubscribed. + */ +export const useExtensionEventEmitterHandler = ( + eventName: string, + handler: ((values: T) => void) | undefined +): void => { + const eventEmitter = useExtensionEventEmitter() + + useEffect(() => { + if (!eventEmitter || !handler) { + return + } + eventEmitter.on(eventName, handler) + return () => { + eventEmitter.off(eventName, handler) + } + }, [eventEmitter, eventName, handler]) +} diff --git a/src/components/markdown-renderer/hooks/use-markdown-extensions.ts b/src/components/markdown-renderer/hooks/use-markdown-extensions.ts index 377d113bc..97db98a3c 100644 --- a/src/components/markdown-renderer/hooks/use-markdown-extensions.ts +++ b/src/components/markdown-renderer/hooks/use-markdown-extensions.ts @@ -5,98 +5,59 @@ */ import type { MutableRefObject } from 'react' -import { useMemo, useRef } from 'react' -import { TableOfContentsMarkdownExtension } from '../markdown-extension/table-of-contents-markdown-extension' -import { VegaLiteMarkdownExtension } from '../markdown-extension/vega-lite/vega-lite-markdown-extension' -import { LinemarkerMarkdownExtension } from '../markdown-extension/linemarker/linemarker-markdown-extension' -import { GistMarkdownExtension } from '../markdown-extension/gist/gist-markdown-extension' -import { YoutubeMarkdownExtension } from '../markdown-extension/youtube/youtube-markdown-extension' -import { VimeoMarkdownExtension } from '../markdown-extension/vimeo/vimeo-markdown-extension' -import { ProxyImageMarkdownExtension } from '../markdown-extension/image/proxy-image-markdown-extension' -import { CsvTableMarkdownExtension } from '../markdown-extension/csv/csv-table-markdown-extension' -import { AbcjsMarkdownExtension } from '../markdown-extension/abcjs/abcjs-markdown-extension' -import { SequenceDiagramMarkdownExtension } from '../markdown-extension/sequence-diagram/sequence-diagram-markdown-extension' -import { FlowchartMarkdownExtension } from '../markdown-extension/flowchart/flowchart-markdown-extension' -import { MermaidMarkdownExtension } from '../markdown-extension/mermaid/mermaid-markdown-extension' -import { GraphvizMarkdownExtension } from '../markdown-extension/graphviz/graphviz-markdown-extension' -import { BlockquoteExtraTagMarkdownExtension } from '../markdown-extension/blockquote/blockquote-extra-tag-markdown-extension' -import { LinkAdjustmentMarkdownExtension } from '../markdown-extension/link-replacer/link-adjustment-markdown-extension' -import { KatexMarkdownExtension } from '../markdown-extension/katex/katex-markdown-extension' -import { TaskListMarkdownExtension } from '../markdown-extension/task-list/task-list-markdown-extension' -import { PlantumlMarkdownExtension } from '../markdown-extension/plantuml/plantuml-markdown-extension' -import { LegacyShortcodesMarkdownExtension } from '../markdown-extension/legacy-short-codes/legacy-shortcodes-markdown-extension' -import { EmojiMarkdownExtension } from '../markdown-extension/emoji/emoji-markdown-extension' -import { GenericSyntaxMarkdownExtension } from '../markdown-extension/generic-syntax-markdown-extension' -import { AlertMarkdownExtension } from '../markdown-extension/alert-markdown-extension' -import { SpoilerMarkdownExtension } from '../markdown-extension/spoiler-markdown-extension' -import { LinkifyFixMarkdownExtension } from '../markdown-extension/linkify-fix-markdown-extension' -import { HighlightedCodeMarkdownExtension } from '../markdown-extension/highlighted-fence/highlighted-code-markdown-extension' -import { DebuggerMarkdownExtension } from '../markdown-extension/debugger-markdown-extension' -import type { LineMarkers } from '../markdown-extension/linemarker/add-line-marker-markdown-it-plugin' -import type { ImageClickHandler } from '../markdown-extension/image/proxy-image-replacer' -import type { TocAst } from 'markdown-it-toc-done-right' -import type { MarkdownExtension } from '../markdown-extension/markdown-extension' -import { IframeCapsuleMarkdownExtension } from '../markdown-extension/iframe-capsule/iframe-capsule-markdown-extension' -import { ImagePlaceholderMarkdownExtension } from '../markdown-extension/image-placeholder/image-placeholder-markdown-extension' -import { UploadIndicatingImageFrameMarkdownExtension } from '../markdown-extension/upload-indicating-image-frame/upload-indicating-image-frame-markdown-extension' -import { useOnRefChange } from './use-on-ref-change' +import { useMemo } from 'react' +import { GenericSyntaxMarkdownExtension } from '../extensions/generic-syntax-markdown-extension' +import type { LineMarkers } from '../extensions/linemarker/add-line-marker-markdown-it-plugin' +import { DebuggerMarkdownExtension } from '../extensions/debugger-markdown-extension' +import { UploadIndicatingImageFrameMarkdownExtension } from '../extensions/upload-indicating-image-frame/upload-indicating-image-frame-markdown-extension' +import { IframeCapsuleMarkdownExtension } from '../extensions/iframe-capsule/iframe-capsule-markdown-extension' +import { LinkifyFixMarkdownExtension } from '../extensions/linkify-fix/linkify-fix-markdown-extension' +import { LinkAdjustmentMarkdownExtension } from '../extensions/link-replacer/link-adjustment-markdown-extension' +import { EmojiMarkdownExtension } from '../extensions/emoji/emoji-markdown-extension' +import { LinemarkerMarkdownExtension } from '../extensions/linemarker/linemarker-markdown-extension' +import { TableOfContentsMarkdownExtension } from '../extensions/table-of-contents-markdown-extension' +import { ImagePlaceholderMarkdownExtension } from '../extensions/image-placeholder/image-placeholder-markdown-extension' +import type { MarkdownRendererExtension } from '../extensions/base/markdown-renderer-extension' +import { useExtensionEventEmitter } from './use-extension-event-emitter' +import { optionalAppExtensions } from '../../../extensions/extra-integrations/optional-app-extensions' +import { ProxyImageMarkdownExtension } from '../extensions/image/proxy-image-markdown-extension' + +const optionalMarkdownRendererExtensions = optionalAppExtensions.flatMap((value) => + value.buildMarkdownRendererExtensions() +) /** - * Provides a list of {@link MarkdownExtension markdown extensions} that is a combination of the common extensions and the given additional. + * Provides a list of {@link MarkdownRendererExtension markdown extensions} that is a combination of the common extensions and the given additional. * * @param baseUrl The base url for the {@link LinkAdjustmentMarkdownExtension} * @param currentLineMarkers A {@link MutableRefObject reference} to {@link LineMarkers} for the {@link LinemarkerMarkdownExtension} * @param additionalExtensions The additional extensions that should be included in the list - * @param onTaskCheckedChange The checkbox click callback for the {@link TaskListMarkdownExtension} - * @param onImageClick The image click callback for the {@link ProxyImageMarkdownExtension} - * @param onTocChange The toc-changed callback for the {@link TableOfContentsMarkdownExtension} * @return The created list of markdown extensions */ export const useMarkdownExtensions = ( baseUrl: string, currentLineMarkers: MutableRefObject | undefined, - additionalExtensions: MarkdownExtension[], - onTaskCheckedChange?: (lineInMarkdown: number, checked: boolean) => void, - onImageClick?: ImageClickHandler, - onTocChange?: (ast?: TocAst) => void -): MarkdownExtension[] => { - const toc = useRef(undefined) - useOnRefChange(toc, onTocChange) - + additionalExtensions: MarkdownRendererExtension[] +): MarkdownRendererExtension[] => { + const extensionEventEmitter = useExtensionEventEmitter() + //replace with global list return useMemo(() => { return [ - new TableOfContentsMarkdownExtension((ast?: TocAst) => (toc.current = ast)), + ...optionalMarkdownRendererExtensions, ...additionalExtensions, - new VegaLiteMarkdownExtension(), + new TableOfContentsMarkdownExtension(extensionEventEmitter), new LinemarkerMarkdownExtension( currentLineMarkers ? (lineMarkers) => (currentLineMarkers.current = lineMarkers) : undefined ), new IframeCapsuleMarkdownExtension(), new ImagePlaceholderMarkdownExtension(), new UploadIndicatingImageFrameMarkdownExtension(), - new GistMarkdownExtension(), - new YoutubeMarkdownExtension(), - new VimeoMarkdownExtension(), - new ProxyImageMarkdownExtension(onImageClick), - new CsvTableMarkdownExtension(), - new AbcjsMarkdownExtension(), - new SequenceDiagramMarkdownExtension(), - new FlowchartMarkdownExtension(), - new MermaidMarkdownExtension(), - new GraphvizMarkdownExtension(), - new BlockquoteExtraTagMarkdownExtension(), new LinkAdjustmentMarkdownExtension(baseUrl), - new KatexMarkdownExtension(), - new TaskListMarkdownExtension(onTaskCheckedChange), - new PlantumlMarkdownExtension(), - new LegacyShortcodesMarkdownExtension(), new EmojiMarkdownExtension(), new GenericSyntaxMarkdownExtension(), - new AlertMarkdownExtension(), - new SpoilerMarkdownExtension(), new LinkifyFixMarkdownExtension(), - new HighlightedCodeMarkdownExtension(), - new DebuggerMarkdownExtension() + new DebuggerMarkdownExtension(), + new ProxyImageMarkdownExtension() ] - }, [additionalExtensions, baseUrl, currentLineMarkers, onImageClick, onTaskCheckedChange]) + }, [additionalExtensions, baseUrl, currentLineMarkers, extensionEventEmitter]) } diff --git a/src/components/markdown-renderer/markdown-extension/abcjs/abcjs-markdown-extension.ts b/src/components/markdown-renderer/markdown-extension/abcjs/abcjs-markdown-extension.ts deleted file mode 100644 index 1ba1b56e0..000000000 --- a/src/components/markdown-renderer/markdown-extension/abcjs/abcjs-markdown-extension.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { CodeBlockComponentReplacer } from '../../replace-components/code-block-component-replacer' -import { AbcFrame } from './abc-frame' -import type { ComponentReplacer } from '../../replace-components/component-replacer' -import { CodeBlockMarkdownExtension } from '../code-block-markdown-extension/code-block-markdown-extension' - -/** - * Adds support for abc.js to the markdown rendering using code fences with "abc" as language. - */ -export class AbcjsMarkdownExtension extends CodeBlockMarkdownExtension { - public buildReplacers(): ComponentReplacer[] { - return [new CodeBlockComponentReplacer(AbcFrame, 'abc')] - } -} diff --git a/src/components/markdown-renderer/markdown-extension/code-block-markdown-extension/code-block-markdown-extension.ts b/src/components/markdown-renderer/markdown-extension/code-block-markdown-extension/code-block-markdown-extension.ts deleted file mode 100644 index 2e26fc3af..000000000 --- a/src/components/markdown-renderer/markdown-extension/code-block-markdown-extension/code-block-markdown-extension.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import type MarkdownIt from 'markdown-it' -import { codeBlockMarkdownPlugin } from './code-block-markdown-plugin' -import { MarkdownExtension } from '../markdown-extension' - -/** - * A {@link MarkdownExtension markdown extension} that is used for code fence replacements. - */ -export abstract class CodeBlockMarkdownExtension extends MarkdownExtension { - public configureMarkdownIt(markdownIt: MarkdownIt): void { - codeBlockMarkdownPlugin(markdownIt) - } -} diff --git a/src/components/markdown-renderer/markdown-extension/csv/csv-table-markdown-extension.ts b/src/components/markdown-renderer/markdown-extension/csv/csv-table-markdown-extension.ts deleted file mode 100644 index 2c43a12d2..000000000 --- a/src/components/markdown-renderer/markdown-extension/csv/csv-table-markdown-extension.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { MarkdownExtension } from '../markdown-extension' -import { CsvReplacer } from './csv-replacer' -import type { ComponentReplacer } from '../../replace-components/component-replacer' - -/** - * Adds support for csv tables to the markdown rendering using code fences with "csv" as language. - */ -export class CsvTableMarkdownExtension extends MarkdownExtension { - public buildReplacers(): ComponentReplacer[] { - return [new CsvReplacer()] - } -} diff --git a/src/components/markdown-renderer/markdown-extension/flowchart/flowchart-markdown-extension.ts b/src/components/markdown-renderer/markdown-extension/flowchart/flowchart-markdown-extension.ts deleted file mode 100644 index 62ae03b6a..000000000 --- a/src/components/markdown-renderer/markdown-extension/flowchart/flowchart-markdown-extension.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { CodeBlockComponentReplacer } from '../../replace-components/code-block-component-replacer' -import type { ComponentReplacer } from '../../replace-components/component-replacer' -import { FlowChart } from './flowchart' -import { CodeBlockMarkdownExtension } from '../code-block-markdown-extension/code-block-markdown-extension' - -/** - * Adds support for flow charts to the markdown rendering using code fences with "flow" as language. - */ -export class FlowchartMarkdownExtension extends CodeBlockMarkdownExtension { - public buildReplacers(): ComponentReplacer[] { - return [new CodeBlockComponentReplacer(FlowChart, 'flow')] - } -} diff --git a/src/components/markdown-renderer/markdown-extension/graphviz/graphviz-markdown-extension.ts b/src/components/markdown-renderer/markdown-extension/graphviz/graphviz-markdown-extension.ts deleted file mode 100644 index dc9d62a78..000000000 --- a/src/components/markdown-renderer/markdown-extension/graphviz/graphviz-markdown-extension.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { CodeBlockComponentReplacer } from '../../replace-components/code-block-component-replacer' -import type { ComponentReplacer } from '../../replace-components/component-replacer' -import { GraphvizFrame } from './graphviz-frame' -import { CodeBlockMarkdownExtension } from '../code-block-markdown-extension/code-block-markdown-extension' - -/** - * Adds support for graphviz to the markdown rendering using code fences with "graphviz" as language. - */ -export class GraphvizMarkdownExtension extends CodeBlockMarkdownExtension { - public buildReplacers(): ComponentReplacer[] { - return [new CodeBlockComponentReplacer(GraphvizFrame, 'graphviz')] - } -} diff --git a/src/components/markdown-renderer/markdown-extension/highlighted-fence/highlighted-code-markdown-extension.ts b/src/components/markdown-renderer/markdown-extension/highlighted-fence/highlighted-code-markdown-extension.ts deleted file mode 100644 index 911fbef98..000000000 --- a/src/components/markdown-renderer/markdown-extension/highlighted-fence/highlighted-code-markdown-extension.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { HighlightedCodeReplacer } from './highlighted-code-replacer' -import type { ComponentReplacer } from '../../replace-components/component-replacer' -import { CodeBlockMarkdownExtension } from '../code-block-markdown-extension/code-block-markdown-extension' - -/** - * Adds support code highlighting to the markdown rendering. - * Every code fence that is not replaced by another replacer is highlighted using highlight-js. - */ -export class HighlightedCodeMarkdownExtension extends CodeBlockMarkdownExtension { - public buildReplacers(): ComponentReplacer[] { - return [new HighlightedCodeReplacer()] - } -} diff --git a/src/components/markdown-renderer/markdown-extension/image/proxy-image-markdown-extension.ts b/src/components/markdown-renderer/markdown-extension/image/proxy-image-markdown-extension.ts deleted file mode 100644 index 7cafe1f5d..000000000 --- a/src/components/markdown-renderer/markdown-extension/image/proxy-image-markdown-extension.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { MarkdownExtension } from '../markdown-extension' -import type { ComponentReplacer } from '../../replace-components/component-replacer' -import type { ImageClickHandler } from './proxy-image-replacer' -import { ProxyImageReplacer } from './proxy-image-replacer' - -/** - * Adds support for image lightbox and image proxy redirection. - */ -export class ProxyImageMarkdownExtension extends MarkdownExtension { - constructor(private onImageClick?: ImageClickHandler) { - super() - } - - buildReplacers(): ComponentReplacer[] { - return [new ProxyImageReplacer(this.onImageClick)] - } -} diff --git a/src/components/markdown-renderer/markdown-extension/legacy-short-codes/legacy-shortcodes-markdown-extension.ts b/src/components/markdown-renderer/markdown-extension/legacy-short-codes/legacy-shortcodes-markdown-extension.ts deleted file mode 100644 index 0de7707d7..000000000 --- a/src/components/markdown-renderer/markdown-extension/legacy-short-codes/legacy-shortcodes-markdown-extension.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { MarkdownExtension } from '../markdown-extension' -import type MarkdownIt from 'markdown-it' -import { legacyPdfRegex, legacyPdfShortCode } from './replace-legacy-pdf-short-code' -import { legacySlideshareRegex, legacySlideshareShortCode } from './replace-legacy-slideshare-short-code' -import { legacySpeakerdeckRegex, legacySpeakerdeckShortCode } from './replace-legacy-speakerdeck-short-code' -import { SingleLineRegexLinter } from '../../../editor-page/editor-pane/linter/single-line-regex-linter' -import type { Linter } from '../../../editor-page/editor-pane/linter/linter' -import { t } from 'i18next' - -/** - * Adds support for legacy shortcodes (pdf, slideshare and speakerdeck) by replacing them with anchor elements. - */ -export class LegacyShortcodesMarkdownExtension extends MarkdownExtension { - public configureMarkdownIt(markdownIt: MarkdownIt): void { - legacyPdfShortCode(markdownIt) - legacySlideshareShortCode(markdownIt) - legacySpeakerdeckShortCode(markdownIt) - } - - public buildLinter(): Linter[] { - return [ - new SingleLineRegexLinter( - legacySpeakerdeckRegex, - t('editor.linter.shortcode', { shortcode: 'SpeakerDeck' }), - (match: string) => `https://speakerdeck.com/${match}` - ), - new SingleLineRegexLinter( - legacySlideshareRegex, - t('editor.linter.shortcode', { shortcode: 'SlideShare' }), - (match: string) => `https://www.slideshare.net/${match}` - ), - new SingleLineRegexLinter( - legacyPdfRegex, - t('editor.linter.shortcode', { shortcode: 'PDF' }), - (match: string) => match - ) - ] - } -} diff --git a/src/components/markdown-renderer/markdown-extension/markdown-extension-collection.ts b/src/components/markdown-renderer/markdown-extension/markdown-extension-collection.ts deleted file mode 100644 index cf7d3969b..000000000 --- a/src/components/markdown-renderer/markdown-extension/markdown-extension-collection.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import type { MarkdownExtension } from './markdown-extension' -import type { ComponentReplacer } from '../replace-components/component-replacer' -import type { Document } from 'domhandler' -import type MarkdownIt from 'markdown-it' -import { SanitizerMarkdownExtension } from './sanitizer/sanitizer-markdown-extension' - -/** - * Contains multiple {@link MarkdownExtension} and uses them to configure parts of the renderer. - */ -export class MarkdownExtensionCollection { - private extensions: MarkdownExtension[] - - public constructor(additionalExtensions: MarkdownExtension[]) { - const tagWhiteLists = additionalExtensions.flatMap((extension) => extension.buildTagNameAllowList()) - - this.extensions = [...additionalExtensions, new SanitizerMarkdownExtension(tagWhiteLists)] - } - - /** - * Configures the given {@link MarkdownIt markdown-it instance} using every saved {@link MarkdownExtension extension}. - * - * @param markdownIt The markdown-it instance to configure. - */ - public configureMarkdownIt(markdownIt: MarkdownIt): void { - this.extensions.forEach((extension) => markdownIt.use((markdownIt) => extension.configureMarkdownIt(markdownIt))) - this.extensions.forEach((extension) => - markdownIt.use((markdownIt) => extension.configureMarkdownItPost(markdownIt)) - ) - } - - /** - * Creates a node processor that applies the node processor of every saved {@link MarkdownExtension extension}. - * - * @return the created node processor function - */ - public buildFlatNodeProcessor(): (document: Document) => Document { - return this.extensions - .flatMap((extension) => extension.buildNodeProcessors()) - .reduce( - (state, processor) => (document: Document) => state(processor.process(document)), - (document: Document) => document - ) - } - - /** - * Collects all {@link ComponentReplacer component replacers} from all saved {@link MarkdownExtension extension}. - */ - public buildReplacers(): ComponentReplacer[] { - return this.extensions.flatMap((extension) => extension.buildReplacers()) - } -} diff --git a/src/components/markdown-renderer/markdown-extension/mermaid/mermaid-markdown-extension.ts b/src/components/markdown-renderer/markdown-extension/mermaid/mermaid-markdown-extension.ts deleted file mode 100644 index 4218b0531..000000000 --- a/src/components/markdown-renderer/markdown-extension/mermaid/mermaid-markdown-extension.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { CodeBlockComponentReplacer } from '../../replace-components/code-block-component-replacer' -import type { ComponentReplacer } from '../../replace-components/component-replacer' -import { MermaidChart } from './mermaid-chart' -import { CodeBlockMarkdownExtension } from '../code-block-markdown-extension/code-block-markdown-extension' - -/** - * Adds support for chart rendering using mermaid to the markdown rendering using code fences with "mermaid" as language. - */ -export class MermaidMarkdownExtension extends CodeBlockMarkdownExtension { - public buildReplacers(): ComponentReplacer[] { - return [new CodeBlockComponentReplacer(MermaidChart, 'mermaid')] - } -} diff --git a/src/components/markdown-renderer/markdown-extension/sequence-diagram/sequence-diagram-markdown-extension.ts b/src/components/markdown-renderer/markdown-extension/sequence-diagram/sequence-diagram-markdown-extension.ts deleted file mode 100644 index a4846774c..000000000 --- a/src/components/markdown-renderer/markdown-extension/sequence-diagram/sequence-diagram-markdown-extension.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { CodeBlockComponentReplacer } from '../../replace-components/code-block-component-replacer' -import type { ComponentReplacer } from '../../replace-components/component-replacer' -import { SequenceDiagram } from './sequence-diagram' -import { CodeBlockMarkdownExtension } from '../code-block-markdown-extension/code-block-markdown-extension' -import type { Linter } from '../../../editor-page/editor-pane/linter/linter' -import { SingleLineRegexLinter } from '../../../editor-page/editor-pane/linter/single-line-regex-linter' -import { t } from 'i18next' - -/** - * Adds legacy support for sequence diagram to the markdown rendering using code fences with "sequence" as language. - */ -export class SequenceDiagramMarkdownExtension extends CodeBlockMarkdownExtension { - public buildReplacers(): ComponentReplacer[] { - return [new CodeBlockComponentReplacer(SequenceDiagram, 'sequence')] - } - - public buildLinter(): Linter[] { - return [new SingleLineRegexLinter(/```sequence/, t('editor.linter.sequence'), () => '```mermaid\nsequenceDiagram')] - } -} 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 deleted file mode 100644 index 26a4f894a..000000000 --- a/src/components/markdown-renderer/markdown-extension/task-list/task-list-replacer.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 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 - -/** - * Detects task lists and renders them as checkboxes that execute a callback if clicked. - */ -export class TaskListReplacer extends ComponentReplacer { - onTaskCheckedChange?: (lineInMarkdown: number, checked: boolean) => void - - constructor(onTaskCheckedChange?: TaskCheckedChangeHandler) { - super() - this.onTaskCheckedChange = (lineInMarkdown, checked) => onTaskCheckedChange?.(lineInMarkdown, checked) - } - - public replace(node: Element): NodeReplacement { - if (node.attribs?.class !== 'task-list-item-checkbox') { - return DO_NOT_REPLACE - } - const lineInMarkdown = Number(node.attribs['data-line']) - return isNaN(lineInMarkdown) ? ( - DO_NOT_REPLACE - ) : ( - - ) - } -} diff --git a/src/components/markdown-renderer/markdown-extension/vega-lite/vega-lite-markdown-extension.ts b/src/components/markdown-renderer/markdown-extension/vega-lite/vega-lite-markdown-extension.ts deleted file mode 100644 index 311a88d45..000000000 --- a/src/components/markdown-renderer/markdown-extension/vega-lite/vega-lite-markdown-extension.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { CodeBlockComponentReplacer } from '../../replace-components/code-block-component-replacer' -import type { ComponentReplacer } from '../../replace-components/component-replacer' -import { VegaLiteChart } from './vega-lite-chart' -import { CodeBlockMarkdownExtension } from '../code-block-markdown-extension/code-block-markdown-extension' - -/** - * Adds support for chart rendering using vega lite to the markdown rendering using code fences with "vega-lite" as language. - */ -export class VegaLiteMarkdownExtension extends CodeBlockMarkdownExtension { - public buildReplacers(): ComponentReplacer[] { - return [new CodeBlockComponentReplacer(VegaLiteChart, 'vega-lite')] - } -} diff --git a/src/components/markdown-renderer/markdown-extension/vimeo/vimeo-markdown-extension.ts b/src/components/markdown-renderer/markdown-extension/vimeo/vimeo-markdown-extension.ts deleted file mode 100644 index e0d96a676..000000000 --- a/src/components/markdown-renderer/markdown-extension/vimeo/vimeo-markdown-extension.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { MarkdownExtension } from '../markdown-extension' -import type MarkdownIt from 'markdown-it' -import type { ComponentReplacer } from '../../replace-components/component-replacer' -import { CustomTagWithIdComponentReplacer } from '../../replace-components/custom-tag-with-id-component-replacer' -import { replaceVimeoLinkMarkdownItPlugin } from './replace-vimeo-link' -import { VimeoFrame } from './vimeo-frame' -import { legacyVimeoRegex, replaceLegacyVimeoShortCodeMarkdownItPlugin } from './replace-legacy-vimeo-short-code' -import type { Linter } from '../../../editor-page/editor-pane/linter/linter' -import { SingleLineRegexLinter } from '../../../editor-page/editor-pane/linter/single-line-regex-linter' -import { t } from 'i18next' - -/** - * Adds vimeo video embeddings using link detection and the legacy vimeo short code syntax. - */ -export class VimeoMarkdownExtension extends MarkdownExtension { - public static readonly tagName = 'app-vimeo' - - public configureMarkdownIt(markdownIt: MarkdownIt): void { - replaceLegacyVimeoShortCodeMarkdownItPlugin(markdownIt) - replaceVimeoLinkMarkdownItPlugin(markdownIt) - } - - public buildReplacers(): ComponentReplacer[] { - return [new CustomTagWithIdComponentReplacer(VimeoFrame, VimeoMarkdownExtension.tagName)] - } - - public buildTagNameAllowList(): string[] { - return [VimeoMarkdownExtension.tagName] - } - - public buildLinter(): Linter[] { - return [ - new SingleLineRegexLinter( - legacyVimeoRegex, - t('editor.linter.shortcode', { shortcode: 'Vimeo' }), - (match: string) => `https://player.vimeo.com/video/${match}` - ) - ] - } -} diff --git a/src/components/markdown-renderer/markdown-extension/youtube/youtube-markdown-extension.ts b/src/components/markdown-renderer/markdown-extension/youtube/youtube-markdown-extension.ts deleted file mode 100644 index e2eac2c10..000000000 --- a/src/components/markdown-renderer/markdown-extension/youtube/youtube-markdown-extension.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { MarkdownExtension } from '../markdown-extension' -import { replaceYouTubeLinkMarkdownItPlugin } from './replace-youtube-link' -import { legacyYouTubeRegex, replaceLegacyYoutubeShortCodeMarkdownItPlugin } from './replace-legacy-youtube-short-code' -import type MarkdownIt from 'markdown-it' -import type { ComponentReplacer } from '../../replace-components/component-replacer' -import { CustomTagWithIdComponentReplacer } from '../../replace-components/custom-tag-with-id-component-replacer' -import { YouTubeFrame } from './youtube-frame' -import type { Linter } from '../../../editor-page/editor-pane/linter/linter' -import { SingleLineRegexLinter } from '../../../editor-page/editor-pane/linter/single-line-regex-linter' -import { t } from 'i18next' - -/** - * Adds YouTube video embeddings using link detection and the legacy YouTube short code syntax. - */ -export class YoutubeMarkdownExtension extends MarkdownExtension { - public static readonly tagName = 'app-youtube' - - public configureMarkdownIt(markdownIt: MarkdownIt): void { - replaceYouTubeLinkMarkdownItPlugin(markdownIt) - replaceLegacyYoutubeShortCodeMarkdownItPlugin(markdownIt) - } - - public buildReplacers(): ComponentReplacer[] { - return [new CustomTagWithIdComponentReplacer(YouTubeFrame, YoutubeMarkdownExtension.tagName)] - } - - public buildTagNameAllowList(): string[] { - return [YoutubeMarkdownExtension.tagName] - } - - public buildLinter(): Linter[] { - return [ - new SingleLineRegexLinter( - legacyYouTubeRegex, - t('editor.linter.shortcode', { shortcode: 'YouTube' }), - (match: string) => `https://www.youtube.com/watch?v=${match}` - ) - ] - } -} diff --git a/src/components/markdown-renderer/replace-components/click-shield/click-shield.tsx b/src/components/markdown-renderer/replace-components/click-shield/click-shield.tsx index f019be895..8d94f7f68 100644 --- a/src/components/markdown-renderer/replace-components/click-shield/click-shield.tsx +++ b/src/components/markdown-renderer/replace-components/click-shield/click-shield.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 */ @@ -15,7 +15,7 @@ import type { Property } from 'csstype' import type { PropsWithDataCypressId } from '../../../../utils/cypress-attribute' import { cypressId } from '../../../../utils/cypress-attribute' import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon' -import { ProxyImageFrame } from '../../markdown-extension/image/proxy-image-frame' +import { ProxyImageFrame } from '../../extensions/image/proxy-image-frame' const log = new Logger('OneClickEmbedding') diff --git a/src/components/markdown-renderer/slideshow-markdown-renderer.tsx b/src/components/markdown-renderer/slideshow-markdown-renderer.tsx index 0460482b3..97eafb1e2 100644 --- a/src/components/markdown-renderer/slideshow-markdown-renderer.tsx +++ b/src/components/markdown-renderer/slideshow-markdown-renderer.tsx @@ -7,15 +7,13 @@ import React, { useEffect, useMemo, useRef } from 'react' import { useConvertMarkdownToReactDom } from './hooks/use-convert-markdown-to-react-dom' import { useExtractFirstHeadline } from './hooks/use-extract-first-headline' -import type { TocAst } from 'markdown-it-toc-done-right' -import { useOnRefChange } from './hooks/use-on-ref-change' import { REVEAL_STATUS, useReveal } from './hooks/use-reveal' import type { ScrollProps } from '../editor-page/synced-scroll/scroll-props' import type { CommonMarkdownRendererProps } from './common-markdown-renderer-props' import { LoadingSlide } from './loading-slide' -import { RevealMarkdownExtension } from './markdown-extension/reveal/reveal-markdown-extension' import { useMarkdownExtensions } from './hooks/use-markdown-extensions' import type { SlideOptions } from '../../redux/note-details/types/slide-show-options' +import { RevealMarkdownExtension } from './extensions/reveal/reveal-markdown-extension' export interface SlideshowMarkdownRendererProps extends CommonMarkdownRendererProps { slideOptions?: SlideOptions @@ -39,26 +37,19 @@ export const SlideshowMarkdownRenderer: React.FC { const markdownBodyRef = useRef(null) - const tocAst = useRef() const extensions = useMarkdownExtensions( baseUrl, undefined, - useMemo(() => [new RevealMarkdownExtension()], []), - onTaskCheckedChange, - onImageClick, - onTocChange + useMemo(() => [new RevealMarkdownExtension()], []) ) - const markdownReactDom = useConvertMarkdownToReactDom(markdownContentLines, extensions, newlinesAreBreaks) + const markdownReactDom = useConvertMarkdownToReactDom(markdownContentLines, extensions, newlinesAreBreaks, true) const revealStatus = useReveal(markdownContentLines, slideOptions) const extractFirstHeadline = useExtractFirstHeadline(markdownBodyRef, onFirstHeadingChange) @@ -68,8 +59,6 @@ export const SlideshowMarkdownRenderer: React.FC (revealStatus === REVEAL_STATUS.INITIALISED ? markdownReactDom : ), [markdownReactDom, revealStatus] @@ -83,5 +72,3 @@ export const SlideshowMarkdownRenderer: React.FC ) } - -export default SlideshowMarkdownRenderer diff --git a/src/components/markdown-renderer/test-utils/test-markdown-renderer.tsx b/src/components/markdown-renderer/test-utils/test-markdown-renderer.tsx index dc47f0a59..873e63c89 100644 --- a/src/components/markdown-renderer/test-utils/test-markdown-renderer.tsx +++ b/src/components/markdown-renderer/test-utils/test-markdown-renderer.tsx @@ -6,19 +6,19 @@ import React, { useMemo } from 'react' import { useConvertMarkdownToReactDom } from '../hooks/use-convert-markdown-to-react-dom' -import type { MarkdownExtension } from '../markdown-extension/markdown-extension' import { StoreProvider } from '../../../redux/store-provider' +import type { MarkdownRendererExtension } from '../extensions/base/markdown-renderer-extension' export interface SimpleMarkdownRendererProps { content: string - extensions: MarkdownExtension[] + extensions: MarkdownRendererExtension[] } /** * A markdown renderer for tests. * * @param content The content to be rendered. - * @param extensions The {@link MarkdownExtension MarkdownExtensions} to use for rendering. + * @param extensions The {@link MarkdownRendererExtension MarkdownExtensions} to use for rendering. */ export const TestMarkdownRenderer: React.FC = ({ content, extensions }) => { const lines = useMemo(() => content.split('\n'), [content]) diff --git a/src/components/markdown-renderer/utils/calculate-line-marker-positions.ts b/src/components/markdown-renderer/utils/calculate-line-marker-positions.ts index 3f6b6e5e1..4a5a0311f 100644 --- a/src/components/markdown-renderer/utils/calculate-line-marker-positions.ts +++ b/src/components/markdown-renderer/utils/calculate-line-marker-positions.ts @@ -7,9 +7,9 @@ import equal from 'fast-deep-equal' import type { RefObject } from 'react' import { useCallback, useEffect, useRef } from 'react' -import type { LineMarkerPosition } from '../markdown-extension/linemarker/types' -import type { LineMarkers } from '../markdown-extension/linemarker/add-line-marker-markdown-it-plugin' import useResizeObserver from '@react-hook/resize-observer' +import type { LineMarkerPosition } from '../extensions/linemarker/types' +import type { LineMarkers } from '../extensions/linemarker/add-line-marker-markdown-it-plugin' const calculateLineMarkerPositions = ( documentElement: HTMLDivElement, diff --git a/src/components/markdown-renderer/utils/line-id-mapper.ts b/src/components/markdown-renderer/utils/line-id-mapper.ts index 097eb6eeb..fb1bfa9f1 100644 --- a/src/components/markdown-renderer/utils/line-id-mapper.ts +++ b/src/components/markdown-renderer/utils/line-id-mapper.ts @@ -4,9 +4,9 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import type { LineWithId } from '../markdown-extension/linemarker/types' import type { ArrayChange } from 'diff' import { diffArrays } from 'diff' +import type { LineWithId } from '../extensions/linemarker/types' type NewLine = string type LineChange = ArrayChange 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 b3cd749e2..c8cb594ac 100644 --- a/src/components/markdown-renderer/utils/node-to-react-transformer.tsx +++ b/src/components/markdown-renderer/utils/node-to-react-transformer.tsx @@ -10,9 +10,9 @@ import { convertNodeToReactElement } from '@hedgedoc/html-to-react/dist/convertN import type { ComponentReplacer, NodeReplacement, ValidReactDomElement } 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' -import { LinemarkerMarkdownExtension } from '../markdown-extension/linemarker/linemarker-markdown-extension' +import { LinemarkerMarkdownExtension } from '../extensions/linemarker/linemarker-markdown-extension' +import type { LineWithId } from '../extensions/linemarker/types' type LineIndexPair = [startLineIndex: number, endLineIndex: number] diff --git a/src/components/render-page/document-toc-sidebar.tsx b/src/components/render-page/document-toc-sidebar.tsx new file mode 100644 index 000000000..11e87f245 --- /dev/null +++ b/src/components/render-page/document-toc-sidebar.tsx @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import React, { useState } from 'react' +import styles from './markdown-document.module.scss' +import { ShowIf } from '../common/show-if/show-if' +import { WidthBasedTableOfContents } from './width-based-table-of-contents' +import type { TocAst } from 'markdown-it-toc-done-right' +import { useExtensionEventEmitterHandler } from '../markdown-renderer/hooks/use-extension-event-emitter' +import { TableOfContentsMarkdownExtension } from '../markdown-renderer/extensions/table-of-contents-markdown-extension' + +export interface DocumentTocSidebarProps { + width: number + disableToc: boolean + baseUrl: string +} + +export const DocumentTocSidebar: React.FC = ({ disableToc, width, baseUrl }) => { + const [tocAst, setTocAst] = useState() + useExtensionEventEmitterHandler(TableOfContentsMarkdownExtension.EVENT_NAME, setTocAst) + return ( +
+ + + +
+ ) +} diff --git a/src/components/render-page/hooks/sync-scroll/use-document-sync-scrolling.ts b/src/components/render-page/hooks/sync-scroll/use-document-sync-scrolling.ts index d655af7b0..144343346 100644 --- a/src/components/render-page/hooks/sync-scroll/use-document-sync-scrolling.ts +++ b/src/components/render-page/hooks/sync-scroll/use-document-sync-scrolling.ts @@ -6,10 +6,10 @@ import type React from 'react' import { useCallback, useMemo, useState } from 'react' -import type { LineMarkerPosition } from '../../../markdown-renderer/markdown-extension/linemarker/types' import type { ScrollState } from '../../../editor-page/synced-scroll/scroll-props' import { useOnUserScroll } from './use-on-user-scroll' import { useScrollToLineMark } from './use-scroll-to-line-mark' +import type { LineMarkerPosition } from '../../../markdown-renderer/extensions/linemarker/types' /** * Synchronizes the scroll status of the given container with the given scroll state and posts changes if the user scrolls. diff --git a/src/components/render-page/hooks/sync-scroll/use-on-user-scroll.ts b/src/components/render-page/hooks/sync-scroll/use-on-user-scroll.ts index 9a2886d05..9bf467b0e 100644 --- a/src/components/render-page/hooks/sync-scroll/use-on-user-scroll.ts +++ b/src/components/render-page/hooks/sync-scroll/use-on-user-scroll.ts @@ -6,8 +6,8 @@ import type React from 'react' import { useCallback } from 'react' -import type { LineMarkerPosition } from '../../../markdown-renderer/markdown-extension/linemarker/types' import type { ScrollState } from '../../../editor-page/synced-scroll/scroll-props' +import type { LineMarkerPosition } from '../../../markdown-renderer/extensions/linemarker/types' /** * Provides a callback to handle user scrolling. diff --git a/src/components/render-page/hooks/sync-scroll/use-scroll-to-line-mark.ts b/src/components/render-page/hooks/sync-scroll/use-scroll-to-line-mark.ts index c8eee1625..702684b24 100644 --- a/src/components/render-page/hooks/sync-scroll/use-scroll-to-line-mark.ts +++ b/src/components/render-page/hooks/sync-scroll/use-scroll-to-line-mark.ts @@ -6,9 +6,9 @@ import type { RefObject } from 'react' import { useCallback, useEffect, useRef } from 'react' -import type { LineMarkerPosition } from '../../../markdown-renderer/markdown-extension/linemarker/types' import type { ScrollState } from '../../../editor-page/synced-scroll/scroll-props' import { findLineMarks } from '../../../editor-page/synced-scroll/utils' +import type { LineMarkerPosition } from '../../../markdown-renderer/extensions/linemarker/types' /** * Scrolls the given container to the correct {@link LineMarkerPosition}. diff --git a/src/components/render-page/hooks/use-image-click-handler.ts b/src/components/render-page/hooks/use-image-click-handler.ts deleted file mode 100644 index 4ebcf8d4b..000000000 --- a/src/components/render-page/hooks/use-image-click-handler.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import type React from 'react' -import { useCallback } from 'react' -import type { ImageClickHandler } from '../../markdown-renderer/markdown-extension/image/proxy-image-replacer' -import type { RendererToEditorCommunicator } from '../window-post-message-communicator/renderer-to-editor-communicator' -import { CommunicationMessageType } from '../window-post-message-communicator/rendering-message' - -/** - * Generates a callback to send information about a clicked image from the iframe back to the editor. - * - * @param iframeCommunicator The communicator to send the message with. - * @return The callback to give to on onClick handler - */ -export const useImageClickHandler = (iframeCommunicator: RendererToEditorCommunicator): ImageClickHandler => { - return useCallback( - (event: React.MouseEvent) => { - const image = event.target as HTMLImageElement - if (image.src === '') { - return - } - iframeCommunicator.sendMessageToOtherSide({ - type: CommunicationMessageType.IMAGE_CLICKED, - details: { - src: image.src, - alt: image.alt, - title: image.title - } - }) - }, - [iframeCommunicator] - ) -} diff --git a/src/components/render-page/iframe-markdown-renderer.tsx b/src/components/render-page/iframe-markdown-renderer.tsx index 5f84df853..802424fae 100644 --- a/src/components/render-page/iframe-markdown-renderer.tsx +++ b/src/components/render-page/iframe-markdown-renderer.tsx @@ -4,19 +4,19 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import React, { useCallback, useRef, useState } from 'react' +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import type { ScrollState } from '../editor-page/synced-scroll/scroll-props' import type { BaseConfiguration } from './window-post-message-communicator/rendering-message' import { CommunicationMessageType, RendererType } from './window-post-message-communicator/rendering-message' import { setDarkMode } from '../../redux/dark-mode/methods' -import type { ImageClickHandler } from '../markdown-renderer/markdown-extension/image/proxy-image-replacer' -import { useImageClickHandler } from './hooks/use-image-click-handler' import { MarkdownDocument } from './markdown-document' import { countWords } from './word-counter' import { useRendererToEditorCommunicator } from '../editor-page/render-context/renderer-to-editor-communicator-context-provider' import { useRendererReceiveHandler } from './window-post-message-communicator/hooks/use-renderer-receive-handler' import { SlideshowMarkdownRenderer } from '../markdown-renderer/slideshow-markdown-renderer' import type { SlideOptions } from '../../redux/note-details/types/slide-show-options' +import EventEmitter2 from 'eventemitter2' +import { eventEmitterContext } from '../markdown-renderer/hooks/use-extension-event-emitter' /** * Wraps the markdown rendering in an iframe. @@ -74,17 +74,6 @@ export const IframeMarkdownRenderer: React.FC = () => { }, [communicator]) ) - const onTaskCheckedChange = useCallback( - (lineInMarkdown: number, checked: boolean) => { - communicator.sendMessageToOtherSide({ - type: CommunicationMessageType.ON_TASK_CHECKBOX_CHANGE, - checked, - lineInMarkdown - }) - }, - [communicator] - ) - const onFirstHeadingChange = useCallback( (firstHeading?: string) => { communicator.sendMessageToOtherSide({ @@ -115,8 +104,6 @@ export const IframeMarkdownRenderer: React.FC = () => { [communicator] ) - const onImageClick: ImageClickHandler = useImageClickHandler(communicator) - const onHeightChange = useCallback( (height: number) => { communicator.sendMessageToOtherSide({ @@ -127,51 +114,75 @@ export const IframeMarkdownRenderer: React.FC = () => { [communicator] ) - if (!baseConfiguration) { - return ( - This is the render endpoint. If you can read this text then please check your HedgeDoc configuration. - ) - } + const renderer = useMemo(() => { + if (!baseConfiguration) { + return ( + + This is the render endpoint. If you can read this text then please check your HedgeDoc configuration. + + ) + } - switch (baseConfiguration.rendererType) { - case RendererType.DOCUMENT: - return ( - - ) - case RendererType.SLIDESHOW: - return ( - - ) - case RendererType.MOTD: - case RendererType.INTRO: - return ( - - ) - default: - return null - } + switch (baseConfiguration.rendererType) { + case RendererType.DOCUMENT: + return ( + + ) + case RendererType.SLIDESHOW: + return ( + + ) + case RendererType.MOTD: + case RendererType.INTRO: + return ( + + ) + default: + return null + } + }, [ + baseConfiguration, + markdownContentLines, + onFirstHeadingChange, + onHeightChange, + onMakeScrollSource, + onScroll, + scrollState, + slideOptions + ]) + + const extensionEventEmitter = useMemo(() => new EventEmitter2({ wildcard: true }), []) + + useEffect(() => { + extensionEventEmitter.onAny((event, values) => { + communicator.sendMessageToOtherSide({ + type: CommunicationMessageType.EXTENSION_EVENT, + eventName: typeof event === 'object' ? event.join('.') : event, + payload: values + }) + }) + }, [communicator, extensionEventEmitter]) + + return {renderer} } diff --git a/src/components/render-page/markdown-document.tsx b/src/components/render-page/markdown-document.tsx index 696a7c814..32dd305e3 100644 --- a/src/components/render-page/markdown-document.tsx +++ b/src/components/render-page/markdown-document.tsx @@ -4,25 +4,20 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import type { TocAst } from 'markdown-it-toc-done-right' import type { MutableRefObject } from 'react' import React, { useEffect, useMemo, useRef, useState } from 'react' import useResizeObserver from '@react-hook/resize-observer' import { useDocumentSyncScrolling } from './hooks/sync-scroll/use-document-sync-scrolling' import type { ScrollProps } from '../editor-page/synced-scroll/scroll-props' import { DocumentMarkdownRenderer } from '../markdown-renderer/document-markdown-renderer' -import type { ImageClickHandler } from '../markdown-renderer/markdown-extension/image/proxy-image-replacer' import styles from './markdown-document.module.scss' -import { WidthBasedTableOfContents } from './width-based-table-of-contents' -import { ShowIf } from '../common/show-if/show-if' import { useApplicationState } from '../../hooks/common/use-application-state' +import { DocumentTocSidebar } from './document-toc-sidebar' export interface RendererProps extends ScrollProps { onFirstHeadingChange?: (firstHeading: string | undefined) => void - onTaskCheckedChange?: (lineInMarkdown: number, checked: boolean) => void documentRenderPaneRef?: MutableRefObject markdownContentLines: string[] - onImageClick?: ImageClickHandler onHeightChange?: (height: number) => void } @@ -55,10 +50,8 @@ export const MarkdownDocument: React.FC = ({ additionalRendererClasses, onFirstHeadingChange, onMakeScrollSource, - onTaskCheckedChange, baseUrl, markdownContentLines, - onImageClick, onScroll, scrollState, onHeightChange, @@ -77,8 +70,6 @@ export const MarkdownDocument: React.FC = ({ setInternalDocumentRenderPaneSize(entry.contentRect) ) - const containerWidth = internalDocumentRenderPaneSize?.width ?? 0 - const [tocAst, setTocAst] = useState() const newlinesAreBreaks = useApplicationState((state) => state.noteDetails.frontmatter.newlinesAreBreaks) const contentLineCount = useMemo(() => markdownContentLines.length, [markdownContentLines]) @@ -106,18 +97,15 @@ export const MarkdownDocument: React.FC = ({ markdownContentLines={markdownContentLines} onFirstHeadingChange={onFirstHeadingChange} onLineMarkerPositionChanged={onLineMarkerPositionChanged} - onTaskCheckedChange={onTaskCheckedChange} - onTocChange={setTocAst} baseUrl={baseUrl} - onImageClick={onImageClick} newlinesAreBreaks={newlinesAreBreaks} />
-
- - - -
+ ) } diff --git a/src/components/render-page/window-post-message-communicator/rendering-message.ts b/src/components/render-page/window-post-message-communicator/rendering-message.ts index 8763e2085..12ee861ce 100644 --- a/src/components/render-page/window-post-message-communicator/rendering-message.ts +++ b/src/components/render-page/window-post-message-communicator/rendering-message.ts @@ -10,18 +10,17 @@ export enum CommunicationMessageType { SET_MARKDOWN_CONTENT = 'SET_MARKDOWN_CONTENT', RENDERER_READY = 'RENDERER_READY', SET_DARKMODE = 'SET_DARKMODE', - ON_TASK_CHECKBOX_CHANGE = 'ON_TASK_CHECKBOX_CHANGE', ON_FIRST_HEADING_CHANGE = 'ON_FIRST_HEADING_CHANGE', ENABLE_RENDERER_SCROLL_SOURCE = 'ENABLE_RENDERER_SCROLL_SOURCE', DISABLE_RENDERER_SCROLL_SOURCE = 'DISABLE_RENDERER_SCROLL_SOURCE', SET_SCROLL_STATE = 'SET_SCROLL_STATE', - IMAGE_CLICKED = 'IMAGE_CLICKED', ON_HEIGHT_CHANGE = 'ON_HEIGHT_CHANGE', SET_BASE_CONFIGURATION = 'SET_BASE_CONFIGURATION', GET_WORD_COUNT = 'GET_WORD_COUNT', ON_WORD_COUNT_CALCULATED = 'ON_WORD_COUNT_CALCULATED', SET_SLIDE_OPTIONS = 'SET_SLIDE_OPTIONS', - IMAGE_UPLOAD = 'IMAGE_UPLOAD' + IMAGE_UPLOAD = 'IMAGE_UPLOAD', + EXTENSION_EVENT = 'EXTENSION_EVENT' } export interface NoPayloadMessage { @@ -33,6 +32,12 @@ export interface SetDarkModeMessage { activated: boolean } +export interface ExtensionEvent { + type: CommunicationMessageType.EXTENSION_EVENT + eventName: string + payload: unknown +} + export interface ImageDetails { alt?: string src: string @@ -56,11 +61,6 @@ export interface GetWordCountMessage { type: CommunicationMessageType.GET_WORD_COUNT } -export interface ImageClickedMessage { - type: CommunicationMessageType.IMAGE_CLICKED - details: ImageDetails -} - export interface SetMarkdownContentMessage { type: CommunicationMessageType.SET_MARKDOWN_CONTENT content: string[] @@ -71,12 +71,6 @@ export interface SetScrollStateMessage { scrollState: ScrollState } -export interface OnTaskCheckboxChangeMessage { - type: CommunicationMessageType.ON_TASK_CHECKBOX_CHANGE - lineInMarkdown: number - checked: boolean -} - export interface OnFirstHeadingChangeMessage { type: CommunicationMessageType.ON_FIRST_HEADING_CHANGE firstHeading: string | undefined @@ -104,15 +98,14 @@ export type CommunicationMessages = | SetDarkModeMessage | SetBaseUrlMessage | GetWordCountMessage - | ImageClickedMessage | SetMarkdownContentMessage | SetScrollStateMessage - | OnTaskCheckboxChangeMessage | OnFirstHeadingChangeMessage | SetSlideOptionsMessage | OnHeightChangeMessage | OnWordCountCalculatedMessage | ImageUploadMessage + | ExtensionEvent export type EditorToRendererMessageType = | CommunicationMessageType.SET_MARKDOWN_CONTENT @@ -127,12 +120,11 @@ export type RendererToEditorMessageType = | CommunicationMessageType.RENDERER_READY | CommunicationMessageType.ENABLE_RENDERER_SCROLL_SOURCE | CommunicationMessageType.ON_FIRST_HEADING_CHANGE - | CommunicationMessageType.ON_TASK_CHECKBOX_CHANGE | CommunicationMessageType.SET_SCROLL_STATE - | CommunicationMessageType.IMAGE_CLICKED | CommunicationMessageType.ON_HEIGHT_CHANGE | CommunicationMessageType.ON_WORD_COUNT_CALCULATED | CommunicationMessageType.IMAGE_UPLOAD + | CommunicationMessageType.EXTENSION_EVENT export enum RendererType { DOCUMENT = 'document', diff --git a/src/extensions/base/app-extension.ts b/src/extensions/base/app-extension.ts new file mode 100644 index 000000000..d8cedd86c --- /dev/null +++ b/src/extensions/base/app-extension.ts @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type { Linter } from '../../components/editor-page/editor-pane/linter/linter' +import type { MarkdownRendererExtension } from '../../components/markdown-renderer/extensions/base/markdown-renderer-extension' +import type React from 'react' +import { Fragment } from 'react' +import type EventEmitter2 from 'eventemitter2' + +export abstract class AppExtension { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public buildMarkdownRendererExtensions(eventEmitter?: EventEmitter2): MarkdownRendererExtension[] { + return [] + } + + public buildCodeMirrorLinter(): Linter[] { + return [] + } + + public buildEditorExtensionComponent(): React.FC { + return Fragment + } +} diff --git a/src/components/markdown-renderer/markdown-extension/abcjs/__snapshots__/abc-frame.test.tsx.snap b/src/extensions/extra-integrations/abcjs/__snapshots__/abc-frame.test.tsx.snap similarity index 100% rename from src/components/markdown-renderer/markdown-extension/abcjs/__snapshots__/abc-frame.test.tsx.snap rename to src/extensions/extra-integrations/abcjs/__snapshots__/abc-frame.test.tsx.snap diff --git a/src/components/markdown-renderer/markdown-extension/abcjs/__snapshots__/abcjs-markdown-extension.test.tsx.snap b/src/extensions/extra-integrations/abcjs/__snapshots__/abcjs-markdown-extension.test.tsx.snap similarity index 100% rename from src/components/markdown-renderer/markdown-extension/abcjs/__snapshots__/abcjs-markdown-extension.test.tsx.snap rename to src/extensions/extra-integrations/abcjs/__snapshots__/abcjs-markdown-extension.test.tsx.snap diff --git a/src/components/markdown-renderer/markdown-extension/abcjs/abc-frame.test.tsx b/src/extensions/extra-integrations/abcjs/abc-frame.test.tsx similarity index 95% rename from src/components/markdown-renderer/markdown-extension/abcjs/abc-frame.test.tsx rename to src/extensions/extra-integrations/abcjs/abc-frame.test.tsx index 5d9e65819..fd7cd868a 100644 --- a/src/components/markdown-renderer/markdown-extension/abcjs/abc-frame.test.tsx +++ b/src/extensions/extra-integrations/abcjs/abc-frame.test.tsx @@ -7,16 +7,14 @@ import React from 'react' import { render, screen } from '@testing-library/react' import { AbcFrame } from './abc-frame' -import { mockI18n } from '../../test-utils/mock-i18n' +import { mockI18n } from '../../../components/markdown-renderer/test-utils/mock-i18n' describe('AbcFrame', () => { afterEach(() => { jest.resetModules() jest.restoreAllMocks() }) - beforeEach(async () => { - await mockI18n() - }) + beforeEach(() => mockI18n()) it('renders a music sheet', async () => { const element = ( diff --git a/src/components/markdown-renderer/markdown-extension/abcjs/abc-frame.tsx b/src/extensions/extra-integrations/abcjs/abc-frame.tsx similarity index 74% rename from src/components/markdown-renderer/markdown-extension/abcjs/abc-frame.tsx rename to src/extensions/extra-integrations/abcjs/abc-frame.tsx index 92a387c1a..ee59d70db 100644 --- a/src/components/markdown-renderer/markdown-extension/abcjs/abc-frame.tsx +++ b/src/extensions/extra-integrations/abcjs/abc-frame.tsx @@ -6,16 +6,16 @@ import React, { useRef } from 'react' import styles from './abc.module.scss' -import { Logger } from '../../../../utils/logger' -import type { CodeProps } from '../../replace-components/code-block-component-replacer' -import { cypressId } from '../../../../utils/cypress-attribute' import { useAsync } from 'react-use' -import { AsyncLoadingBoundary } from '../../../common/async-loading-boundary' -import { WaitSpinner } from '../../../common/wait-spinner/wait-spinner' -import { useEffectWithCatch } from '../../../../hooks/common/use-effect-with-catch' import { Alert } from 'react-bootstrap' -import { ShowIf } from '../../../common/show-if/show-if' import { Trans } from 'react-i18next' +import { WaitSpinner } from '../../../components/common/wait-spinner/wait-spinner' +import type { CodeProps } from '../../../components/markdown-renderer/replace-components/code-block-component-replacer' +import { cypressId } from '../../../utils/cypress-attribute' +import { AsyncLoadingBoundary } from '../../../components/common/async-loading-boundary' +import { ShowIf } from '../../../components/common/show-if/show-if' +import { useEffectWithCatch } from '../../../hooks/common/use-effect-with-catch' +import { Logger } from '../../../utils/logger' const log = new Logger('AbcFrame') diff --git a/src/components/markdown-renderer/markdown-extension/abcjs/abc.module.scss b/src/extensions/extra-integrations/abcjs/abc.module.scss similarity index 66% rename from src/components/markdown-renderer/markdown-extension/abcjs/abc.module.scss rename to src/extensions/extra-integrations/abcjs/abc.module.scss index 249e055f3..2b9295b29 100644 --- a/src/components/markdown-renderer/markdown-extension/abcjs/abc.module.scss +++ b/src/extensions/extra-integrations/abcjs/abc.module.scss @@ -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 */ @@ -15,7 +15,7 @@ } &, text { - @import "../../../../../global-styles/variables.module"; + @import "../../../../global-styles/variables.module"; font-family: $font-family-base; } } diff --git a/src/extensions/extra-integrations/abcjs/abcjs-app-extension.ts b/src/extensions/extra-integrations/abcjs/abcjs-app-extension.ts new file mode 100644 index 000000000..d2bbdf1a0 --- /dev/null +++ b/src/extensions/extra-integrations/abcjs/abcjs-app-extension.ts @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { AppExtension } from '../../base/app-extension' +import { AbcjsMarkdownExtension } from './abcjs-markdown-extension' +import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension' + +export class AbcjsAppExtension extends AppExtension { + buildMarkdownRendererExtensions(): MarkdownRendererExtension[] { + return [new AbcjsMarkdownExtension()] + } +} diff --git a/src/components/markdown-renderer/markdown-extension/abcjs/abcjs-markdown-extension.test.tsx b/src/extensions/extra-integrations/abcjs/abcjs-markdown-extension.test.tsx similarity index 79% rename from src/components/markdown-renderer/markdown-extension/abcjs/abcjs-markdown-extension.test.tsx rename to src/extensions/extra-integrations/abcjs/abcjs-markdown-extension.test.tsx index 92f4e2103..2c67f46d1 100644 --- a/src/components/markdown-renderer/markdown-extension/abcjs/abcjs-markdown-extension.test.tsx +++ b/src/extensions/extra-integrations/abcjs/abcjs-markdown-extension.test.tsx @@ -6,11 +6,11 @@ import React from 'react' import { render } from '@testing-library/react' -import { TestMarkdownRenderer } from '../../test-utils/test-markdown-renderer' import * as AbcFrameModule from './abc-frame' -import type { CodeProps } from '../../replace-components/code-block-component-replacer' import { AbcjsMarkdownExtension } from './abcjs-markdown-extension' -import { mockI18n } from '../../test-utils/mock-i18n' +import { mockI18n } from '../../../components/markdown-renderer/test-utils/mock-i18n' +import type { CodeProps } from '../../../components/markdown-renderer/replace-components/code-block-component-replacer' +import { TestMarkdownRenderer } from '../../../components/markdown-renderer/test-utils/test-markdown-renderer' jest.mock('./abc-frame') diff --git a/src/extensions/extra-integrations/abcjs/abcjs-markdown-extension.ts b/src/extensions/extra-integrations/abcjs/abcjs-markdown-extension.ts new file mode 100644 index 000000000..1ea19f747 --- /dev/null +++ b/src/extensions/extra-integrations/abcjs/abcjs-markdown-extension.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { AbcFrame } from './abc-frame' +import { CodeBlockMarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/code-block-markdown-extension/code-block-markdown-renderer-extension' +import { CodeBlockComponentReplacer } from '../../../components/markdown-renderer/replace-components/code-block-component-replacer' + +/** + * Adds support for abc.js to the markdown rendering using code fences with "abc" as language. + */ +export class AbcjsMarkdownExtension extends CodeBlockMarkdownRendererExtension { + public buildReplacers(): CodeBlockComponentReplacer[] { + return [new CodeBlockComponentReplacer(AbcFrame, 'abc')] + } +} diff --git a/src/extensions/extra-integrations/alert/alert-app-extension.ts b/src/extensions/extra-integrations/alert/alert-app-extension.ts new file mode 100644 index 000000000..aaf51bf50 --- /dev/null +++ b/src/extensions/extra-integrations/alert/alert-app-extension.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { AppExtension } from '../../base/app-extension' +import { AlertMarkdownExtension } from './alert-markdown-extension' +import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension' + +/** + * Adds alert boxes to the markdown rendering. + */ +export class AlertAppExtension extends AppExtension { + buildMarkdownRendererExtensions(): MarkdownRendererExtension[] { + return [new AlertMarkdownExtension()] + } +} diff --git a/src/components/markdown-renderer/markdown-extension/alert-markdown-extension.ts b/src/extensions/extra-integrations/alert/alert-markdown-extension.ts similarity index 82% rename from src/components/markdown-renderer/markdown-extension/alert-markdown-extension.ts rename to src/extensions/extra-integrations/alert/alert-markdown-extension.ts index 7badbc334..2f158f9c1 100644 --- a/src/components/markdown-renderer/markdown-extension/alert-markdown-extension.ts +++ b/src/extensions/extra-integrations/alert/alert-markdown-extension.ts @@ -4,18 +4,18 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { MarkdownExtension } from './markdown-extension' import type MarkdownIt from 'markdown-it' import markdownItContainer from 'markdown-it-container' import type Token from 'markdown-it/lib/token' import type Renderer from 'markdown-it/lib/renderer' +import { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension' export const alertLevels = ['success', 'danger', 'info', 'warning'] /** * Adds alert boxes to the markdown rendering. */ -export class AlertMarkdownExtension extends MarkdownExtension { +export class AlertMarkdownExtension extends MarkdownRendererExtension { public configureMarkdownIt(markdownIt: MarkdownIt): void { alertLevels.forEach((level) => { markdownItContainer(markdownIt, level, { diff --git a/src/extensions/extra-integrations/blockquote/blockquote-app-extension.ts b/src/extensions/extra-integrations/blockquote/blockquote-app-extension.ts new file mode 100644 index 000000000..44fe10d6c --- /dev/null +++ b/src/extensions/extra-integrations/blockquote/blockquote-app-extension.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { AppExtension } from '../../base/app-extension' +import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension' +import { BlockquoteExtraTagMarkdownExtension } from './blockquote-extra-tag-markdown-extension' + +/** + * Adds support for generic blockquote extra tags and blockquote color extra tags. + */ +export class BlockquoteAppExtension extends AppExtension { + buildMarkdownRendererExtensions(): MarkdownRendererExtension[] { + return [new BlockquoteExtraTagMarkdownExtension()] + } +} diff --git a/src/components/markdown-renderer/markdown-extension/blockquote/blockquote-border-color-node-preprocessor.ts b/src/extensions/extra-integrations/blockquote/blockquote-border-color-node-preprocessor.ts similarity index 97% rename from src/components/markdown-renderer/markdown-extension/blockquote/blockquote-border-color-node-preprocessor.ts rename to src/extensions/extra-integrations/blockquote/blockquote-border-color-node-preprocessor.ts index a9987bd42..649cbe4ad 100644 --- a/src/components/markdown-renderer/markdown-extension/blockquote/blockquote-border-color-node-preprocessor.ts +++ b/src/extensions/extra-integrations/blockquote/blockquote-border-color-node-preprocessor.ts @@ -6,9 +6,9 @@ import type { Element, Node } from 'domhandler' import { isTag, isText } from 'domhandler' -import { TravelerNodeProcessor } from '../../node-preprocessors/traveler-node-processor' import { Optional } from '@mrdrogdrog/optional' import { BlockquoteExtraTagMarkdownExtension } from './blockquote-extra-tag-markdown-extension' +import { TravelerNodeProcessor } from '../../../components/markdown-renderer/node-preprocessors/traveler-node-processor' /** * Detects blockquotes with blockquote color tags and uses them to color the blockquote border. diff --git a/src/components/markdown-renderer/markdown-extension/blockquote/blockquote-color-extra-tag-replacer.tsx b/src/extensions/extra-integrations/blockquote/blockquote-color-extra-tag-replacer.tsx similarity index 79% rename from src/components/markdown-renderer/markdown-extension/blockquote/blockquote-color-extra-tag-replacer.tsx rename to src/extensions/extra-integrations/blockquote/blockquote-color-extra-tag-replacer.tsx index 289578883..0edc048f9 100644 --- a/src/components/markdown-renderer/markdown-extension/blockquote/blockquote-color-extra-tag-replacer.tsx +++ b/src/extensions/extra-integrations/blockquote/blockquote-color-extra-tag-replacer.tsx @@ -4,15 +4,18 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import type { NodeReplacement } from '../../replace-components/component-replacer' -import { ComponentReplacer, DO_NOT_REPLACE } from '../../replace-components/component-replacer' import type { Element } from 'domhandler' import { isText } from 'domhandler' -import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon' import { cssColor } from './blockquote-border-color-node-preprocessor' import type { Text } from 'domhandler/lib/node' import { BlockquoteExtraTagMarkdownExtension } from './blockquote-extra-tag-markdown-extension' import { Optional } from '@mrdrogdrog/optional' +import type { NodeReplacement } from '../../../components/markdown-renderer/replace-components/component-replacer' +import { + ComponentReplacer, + DO_NOT_REPLACE +} from '../../../components/markdown-renderer/replace-components/component-replacer' +import { ForkAwesomeIcon } from '../../../components/common/fork-awesome/fork-awesome-icon' /** * Replaces elements with "color" as label and a valid color as content diff --git a/src/components/markdown-renderer/markdown-extension/blockquote/blockquote-extra-tag-markdown-extension.ts b/src/extensions/extra-integrations/blockquote/blockquote-extra-tag-markdown-extension.ts similarity index 79% rename from src/components/markdown-renderer/markdown-extension/blockquote/blockquote-extra-tag-markdown-extension.ts rename to src/extensions/extra-integrations/blockquote/blockquote-extra-tag-markdown-extension.ts index b404e2cd9..b6f1f6dec 100644 --- a/src/components/markdown-renderer/markdown-extension/blockquote/blockquote-extra-tag-markdown-extension.ts +++ b/src/extensions/extra-integrations/blockquote/blockquote-extra-tag-markdown-extension.ts @@ -4,19 +4,19 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { MarkdownExtension } from '../markdown-extension' import { BlockquoteColorExtraTagReplacer } from './blockquote-color-extra-tag-replacer' import { BlockquoteExtraTagReplacer } from './blockquote-extra-tag-replacer' -import type { ComponentReplacer } from '../../replace-components/component-replacer' import type MarkdownIt from 'markdown-it' -import type { NodeProcessor } from '../../node-preprocessors/node-processor' import { BlockquoteBorderColorNodePreprocessor } from './blockquote-border-color-node-preprocessor' import { BlockquoteExtraTagMarkdownItPlugin } from './blockquote-extra-tag-markdown-it-plugin' +import type { NodeProcessor } from '../../../components/markdown-renderer/node-preprocessors/node-processor' +import type { ComponentReplacer } from '../../../components/markdown-renderer/replace-components/component-replacer' +import { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension' /** * Adds support for generic blockquote extra tags and blockquote color extra tags. */ -export class BlockquoteExtraTagMarkdownExtension extends MarkdownExtension { +export class BlockquoteExtraTagMarkdownExtension extends MarkdownRendererExtension { public static readonly tagName = 'app-blockquote-tag' public configureMarkdownIt(markdownIt: MarkdownIt): void { diff --git a/src/components/markdown-renderer/markdown-extension/blockquote/blockquote-extra-tag-markdown-it-plugin.test.ts b/src/extensions/extra-integrations/blockquote/blockquote-extra-tag-markdown-it-plugin.test.ts similarity index 95% rename from src/components/markdown-renderer/markdown-extension/blockquote/blockquote-extra-tag-markdown-it-plugin.test.ts rename to src/extensions/extra-integrations/blockquote/blockquote-extra-tag-markdown-it-plugin.test.ts index 149d9983f..8a77270ff 100644 --- a/src/components/markdown-renderer/markdown-extension/blockquote/blockquote-extra-tag-markdown-it-plugin.test.ts +++ b/src/extensions/extra-integrations/blockquote/blockquote-extra-tag-markdown-it-plugin.test.ts @@ -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 */ diff --git a/src/components/markdown-renderer/markdown-extension/blockquote/blockquote-extra-tag-markdown-it-plugin.ts b/src/extensions/extra-integrations/blockquote/blockquote-extra-tag-markdown-it-plugin.ts similarity index 98% rename from src/components/markdown-renderer/markdown-extension/blockquote/blockquote-extra-tag-markdown-it-plugin.ts rename to src/extensions/extra-integrations/blockquote/blockquote-extra-tag-markdown-it-plugin.ts index cbcc63b55..c2e26b0c0 100644 --- a/src/components/markdown-renderer/markdown-extension/blockquote/blockquote-extra-tag-markdown-it-plugin.ts +++ b/src/extensions/extra-integrations/blockquote/blockquote-extra-tag-markdown-it-plugin.ts @@ -6,11 +6,11 @@ import type MarkdownIt from 'markdown-it/lib' import type Token from 'markdown-it/lib/token' -import type { IconName } from '../../../common/fork-awesome/types' import { Optional } from '@mrdrogdrog/optional' import type StateInline from 'markdown-it/lib/rules_inline/state_inline' import { BlockquoteExtraTagMarkdownExtension } from './blockquote-extra-tag-markdown-extension' import type { RuleInline } from 'markdown-it/lib/parser_inline' +import type { IconName } from '../../../components/common/fork-awesome/types' export interface BlockquoteTagOptions { parseSubTags?: boolean diff --git a/src/components/markdown-renderer/markdown-extension/blockquote/blockquote-extra-tag-replacer.tsx b/src/extensions/extra-integrations/blockquote/blockquote-extra-tag-replacer.tsx similarity index 73% rename from src/components/markdown-renderer/markdown-extension/blockquote/blockquote-extra-tag-replacer.tsx rename to src/extensions/extra-integrations/blockquote/blockquote-extra-tag-replacer.tsx index 888e31ea0..ce5db804b 100644 --- a/src/components/markdown-renderer/markdown-extension/blockquote/blockquote-extra-tag-replacer.tsx +++ b/src/extensions/extra-integrations/blockquote/blockquote-extra-tag-replacer.tsx @@ -4,16 +4,22 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import type { NodeReplacement, SubNodeTransform } from '../../replace-components/component-replacer' -import { ComponentReplacer, DO_NOT_REPLACE } from '../../replace-components/component-replacer' import type { Element } from 'domhandler' -import type { ForkAwesomeIconProps } from '../../../common/fork-awesome/fork-awesome-icon' -import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon' -import type { IconName } from '../../../common/fork-awesome/types' -import { ForkAwesomeIcons } from '../../../common/fork-awesome/fork-awesome-icons' import { Optional } from '@mrdrogdrog/optional' import type { ReactElement } from 'react' import { BlockquoteExtraTagMarkdownExtension } from './blockquote-extra-tag-markdown-extension' +import type { + NodeReplacement, + SubNodeTransform +} from '../../../components/markdown-renderer/replace-components/component-replacer' +import { + ComponentReplacer, + DO_NOT_REPLACE +} from '../../../components/markdown-renderer/replace-components/component-replacer' +import { ForkAwesomeIcons } from '../../../components/common/fork-awesome/fork-awesome-icons' +import type { ForkAwesomeIconProps } from '../../../components/common/fork-awesome/fork-awesome-icon' +import { ForkAwesomeIcon } from '../../../components/common/fork-awesome/fork-awesome-icon' +import type { IconName } from '../../../components/common/fork-awesome/types' /** * Replaces elements with an icon and a small text. diff --git a/src/components/markdown-renderer/markdown-extension/csv/__snapshots__/csv-table-markdown-extension.test.tsx.snap b/src/extensions/extra-integrations/csv/__snapshots__/csv-table-markdown-extension.test.tsx.snap similarity index 100% rename from src/components/markdown-renderer/markdown-extension/csv/__snapshots__/csv-table-markdown-extension.test.tsx.snap rename to src/extensions/extra-integrations/csv/__snapshots__/csv-table-markdown-extension.test.tsx.snap diff --git a/src/components/markdown-renderer/markdown-extension/csv/__snapshots__/csv-table.test.tsx.snap b/src/extensions/extra-integrations/csv/__snapshots__/csv-table.test.tsx.snap similarity index 100% rename from src/components/markdown-renderer/markdown-extension/csv/__snapshots__/csv-table.test.tsx.snap rename to src/extensions/extra-integrations/csv/__snapshots__/csv-table.test.tsx.snap diff --git a/src/components/markdown-renderer/markdown-extension/csv/csv-parser.test.ts b/src/extensions/extra-integrations/csv/csv-parser.test.ts similarity index 92% rename from src/components/markdown-renderer/markdown-extension/csv/csv-parser.test.ts rename to src/extensions/extra-integrations/csv/csv-parser.test.ts index 3273daeab..21d678437 100644 --- a/src/components/markdown-renderer/markdown-extension/csv/csv-parser.test.ts +++ b/src/extensions/extra-integrations/csv/csv-parser.test.ts @@ -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 */ diff --git a/src/components/markdown-renderer/markdown-extension/csv/csv-parser.ts b/src/extensions/extra-integrations/csv/csv-parser.ts similarity index 100% rename from src/components/markdown-renderer/markdown-extension/csv/csv-parser.ts rename to src/extensions/extra-integrations/csv/csv-parser.ts diff --git a/src/components/markdown-renderer/markdown-extension/csv/csv-replacer.tsx b/src/extensions/extra-integrations/csv/csv-replacer.tsx similarity index 70% rename from src/components/markdown-renderer/markdown-extension/csv/csv-replacer.tsx rename to src/extensions/extra-integrations/csv/csv-replacer.tsx index 233fbf287..df4ada86c 100644 --- a/src/components/markdown-renderer/markdown-extension/csv/csv-replacer.tsx +++ b/src/extensions/extra-integrations/csv/csv-replacer.tsx @@ -6,10 +6,13 @@ import type { Element } 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 type { NodeReplacement } from '../../../components/markdown-renderer/replace-components/component-replacer' import { CsvTable } from './csv-table' -import { CodeBlockComponentReplacer } from '../../replace-components/code-block-component-replacer' +import { + ComponentReplacer, + DO_NOT_REPLACE +} from '../../../components/markdown-renderer/replace-components/component-replacer' +import { CodeBlockComponentReplacer } from '../../../components/markdown-renderer/replace-components/code-block-component-replacer' /** * Detects code blocks with "csv" as language and renders them as table. diff --git a/src/extensions/extra-integrations/csv/csv-table-app-extension.ts b/src/extensions/extra-integrations/csv/csv-table-app-extension.ts new file mode 100644 index 000000000..0eaf2befe --- /dev/null +++ b/src/extensions/extra-integrations/csv/csv-table-app-extension.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { AppExtension } from '../../base/app-extension' +import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension' +import { CsvTableMarkdownExtension } from './csv-table-markdown-extension' + +/** + * Adds support for csv tables to the markdown rendering. + */ +export class CsvTableAppExtension extends AppExtension { + buildMarkdownRendererExtensions(): MarkdownRendererExtension[] { + return [new CsvTableMarkdownExtension()] + } +} diff --git a/src/components/markdown-renderer/markdown-extension/csv/csv-table-markdown-extension.test.tsx b/src/extensions/extra-integrations/csv/csv-table-markdown-extension.test.tsx similarity index 76% rename from src/components/markdown-renderer/markdown-extension/csv/csv-table-markdown-extension.test.tsx rename to src/extensions/extra-integrations/csv/csv-table-markdown-extension.test.tsx index eb624bb3b..1875c2c7e 100644 --- a/src/components/markdown-renderer/markdown-extension/csv/csv-table-markdown-extension.test.tsx +++ b/src/extensions/extra-integrations/csv/csv-table-markdown-extension.test.tsx @@ -6,11 +6,11 @@ import * as CsvTableModule from '../csv/csv-table' import React from 'react' -import type { CodeProps } from '../../replace-components/code-block-component-replacer' -import { mockI18n } from '../../test-utils/mock-i18n' import { render } from '@testing-library/react' -import { TestMarkdownRenderer } from '../../test-utils/test-markdown-renderer' import { CsvTableMarkdownExtension } from './csv-table-markdown-extension' +import { mockI18n } from '../../../components/markdown-renderer/test-utils/mock-i18n' +import type { CodeProps } from '../../../components/markdown-renderer/replace-components/code-block-component-replacer' +import { TestMarkdownRenderer } from '../../../components/markdown-renderer/test-utils/test-markdown-renderer' jest.mock('../csv/csv-table') diff --git a/src/extensions/extra-integrations/csv/csv-table-markdown-extension.ts b/src/extensions/extra-integrations/csv/csv-table-markdown-extension.ts new file mode 100644 index 000000000..188781329 --- /dev/null +++ b/src/extensions/extra-integrations/csv/csv-table-markdown-extension.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { CsvReplacer } from './csv-replacer' +import { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension' +import type { ComponentReplacer } from '../../../components/markdown-renderer/replace-components/component-replacer' + +/** + * Adds support for csv tables to the markdown rendering using code fences with "csv" as language. + */ +export class CsvTableMarkdownExtension extends MarkdownRendererExtension { + public buildReplacers(): ComponentReplacer[] { + return [new CsvReplacer()] + } +} diff --git a/src/components/markdown-renderer/markdown-extension/csv/csv-table.test.tsx b/src/extensions/extra-integrations/csv/csv-table.test.tsx similarity index 100% rename from src/components/markdown-renderer/markdown-extension/csv/csv-table.test.tsx rename to src/extensions/extra-integrations/csv/csv-table.test.tsx diff --git a/src/components/markdown-renderer/markdown-extension/csv/csv-table.tsx b/src/extensions/extra-integrations/csv/csv-table.tsx similarity index 96% rename from src/components/markdown-renderer/markdown-extension/csv/csv-table.tsx rename to src/extensions/extra-integrations/csv/csv-table.tsx index 9d3e0f31f..3c776aa38 100644 --- a/src/components/markdown-renderer/markdown-extension/csv/csv-table.tsx +++ b/src/extensions/extra-integrations/csv/csv-table.tsx @@ -6,7 +6,7 @@ import React, { useMemo } from 'react' import { parseCsv } from './csv-parser' -import { cypressId } from '../../../../utils/cypress-attribute' +import { cypressId } from '../../../utils/cypress-attribute' export interface CsvTableProps { code: string diff --git a/src/components/markdown-renderer/markdown-extension/flowchart/__snapshots__/flowchart-markdown-extension.test.tsx.snap b/src/extensions/extra-integrations/flowchart/__snapshots__/flowchart-markdown-extension.test.tsx.snap similarity index 100% rename from src/components/markdown-renderer/markdown-extension/flowchart/__snapshots__/flowchart-markdown-extension.test.tsx.snap rename to src/extensions/extra-integrations/flowchart/__snapshots__/flowchart-markdown-extension.test.tsx.snap diff --git a/src/components/markdown-renderer/markdown-extension/flowchart/__snapshots__/flowchart.test.tsx.snap b/src/extensions/extra-integrations/flowchart/__snapshots__/flowchart.test.tsx.snap similarity index 100% rename from src/components/markdown-renderer/markdown-extension/flowchart/__snapshots__/flowchart.test.tsx.snap rename to src/extensions/extra-integrations/flowchart/__snapshots__/flowchart.test.tsx.snap diff --git a/src/extensions/extra-integrations/flowchart/flowchart-app-extension.ts b/src/extensions/extra-integrations/flowchart/flowchart-app-extension.ts new file mode 100644 index 000000000..c8d926469 --- /dev/null +++ b/src/extensions/extra-integrations/flowchart/flowchart-app-extension.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { AppExtension } from '../../base/app-extension' +import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension' +import { FlowchartMarkdownExtension } from './flowchart-markdown-extension' + +/** + * Adds support for flow charts to the markdown rendering. + */ +export class FlowchartAppExtension extends AppExtension { + buildMarkdownRendererExtensions(): MarkdownRendererExtension[] { + return [new FlowchartMarkdownExtension()] + } +} diff --git a/src/components/markdown-renderer/markdown-extension/flowchart/flowchart-markdown-extension.test.tsx b/src/extensions/extra-integrations/flowchart/flowchart-markdown-extension.test.tsx similarity index 77% rename from src/components/markdown-renderer/markdown-extension/flowchart/flowchart-markdown-extension.test.tsx rename to src/extensions/extra-integrations/flowchart/flowchart-markdown-extension.test.tsx index 9ac424203..90b6915b9 100644 --- a/src/components/markdown-renderer/markdown-extension/flowchart/flowchart-markdown-extension.test.tsx +++ b/src/extensions/extra-integrations/flowchart/flowchart-markdown-extension.test.tsx @@ -5,12 +5,12 @@ */ import { render } from '@testing-library/react' -import { TestMarkdownRenderer } from '../../test-utils/test-markdown-renderer' import React from 'react' -import { mockI18n } from '../../test-utils/mock-i18n' import { FlowchartMarkdownExtension } from './flowchart-markdown-extension' import * as Flowchart from '../flowchart/flowchart' -import type { CodeProps } from '../../replace-components/code-block-component-replacer' +import { mockI18n } from '../../../components/markdown-renderer/test-utils/mock-i18n' +import type { CodeProps } from '../../../components/markdown-renderer/replace-components/code-block-component-replacer' +import { TestMarkdownRenderer } from '../../../components/markdown-renderer/test-utils/test-markdown-renderer' jest.mock('../flowchart/flowchart') diff --git a/src/extensions/extra-integrations/flowchart/flowchart-markdown-extension.ts b/src/extensions/extra-integrations/flowchart/flowchart-markdown-extension.ts new file mode 100644 index 000000000..420d4f1d0 --- /dev/null +++ b/src/extensions/extra-integrations/flowchart/flowchart-markdown-extension.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { FlowChart } from './flowchart' +import { CodeBlockMarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/code-block-markdown-extension/code-block-markdown-renderer-extension' +import { CodeBlockComponentReplacer } from '../../../components/markdown-renderer/replace-components/code-block-component-replacer' + +/** + * Adds support for flow charts to the markdown rendering using code fences with "flow" as language. + */ +export class FlowchartMarkdownExtension extends CodeBlockMarkdownRendererExtension { + public buildReplacers(): CodeBlockComponentReplacer[] { + return [new CodeBlockComponentReplacer(FlowChart, 'flow')] + } +} diff --git a/src/components/markdown-renderer/markdown-extension/flowchart/flowchart.test.tsx b/src/extensions/extra-integrations/flowchart/flowchart.test.tsx similarity index 92% rename from src/components/markdown-renderer/markdown-extension/flowchart/flowchart.test.tsx rename to src/extensions/extra-integrations/flowchart/flowchart.test.tsx index dec918b6f..a2e1b1fdc 100644 --- a/src/components/markdown-renderer/markdown-extension/flowchart/flowchart.test.tsx +++ b/src/extensions/extra-integrations/flowchart/flowchart.test.tsx @@ -4,19 +4,17 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { mockI18n } from '../../test-utils/mock-i18n' import { render, screen } from '@testing-library/react' import { FlowChart } from './flowchart' import type * as flowchartJsModule from 'flowchart.js' -import { StoreProvider } from '../../../../redux/store-provider' +import { mockI18n } from '../../../components/markdown-renderer/test-utils/mock-i18n' +import { StoreProvider } from '../../../redux/store-provider' describe('Flowchart', () => { const successText = 'Flowchart rendering succeeded!' const expectedValidFlowchartCode = 'test code' - beforeAll(async () => { - await mockI18n() - }) + beforeAll(() => mockI18n()) afterEach(() => { jest.resetModules() diff --git a/src/components/markdown-renderer/markdown-extension/flowchart/flowchart.tsx b/src/extensions/extra-integrations/flowchart/flowchart.tsx similarity index 81% rename from src/components/markdown-renderer/markdown-extension/flowchart/flowchart.tsx rename to src/extensions/extra-integrations/flowchart/flowchart.tsx index 52ddb13e5..8c34c0a4c 100644 --- a/src/components/markdown-renderer/markdown-extension/flowchart/flowchart.tsx +++ b/src/extensions/extra-integrations/flowchart/flowchart.tsx @@ -7,14 +7,14 @@ import React, { useEffect, useRef, useState } from 'react' import { Alert } from 'react-bootstrap' import { Trans, useTranslation } from 'react-i18next' -import { useIsDarkModeActivated } from '../../../../hooks/common/use-is-dark-mode-activated' -import { Logger } from '../../../../utils/logger' -import fontStyles from '../../../../../global-styles/variables.module.scss' +import fontStyles from '../../../../global-styles/variables.module.scss' import { useAsync } from 'react-use' -import { AsyncLoadingBoundary } from '../../../common/async-loading-boundary' -import { ShowIf } from '../../../common/show-if/show-if' -import type { CodeProps } from '../../replace-components/code-block-component-replacer' -import { testId } from '../../../../utils/test-id' +import type { CodeProps } from '../../../components/markdown-renderer/replace-components/code-block-component-replacer' +import { AsyncLoadingBoundary } from '../../../components/common/async-loading-boundary' +import { ShowIf } from '../../../components/common/show-if/show-if' +import { testId } from '../../../utils/test-id' +import { useIsDarkModeActivated } from '../../../hooks/common/use-is-dark-mode-activated' +import { Logger } from '../../../utils/logger' const log = new Logger('FlowChart') diff --git a/src/extensions/extra-integrations/gist/gist-app-extension.ts b/src/extensions/extra-integrations/gist/gist-app-extension.ts new file mode 100644 index 000000000..70eccde53 --- /dev/null +++ b/src/extensions/extra-integrations/gist/gist-app-extension.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { AppExtension } from '../../base/app-extension' +import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension' +import { GistMarkdownExtension } from './gist-markdown-extension' + +/** + * Adds support for embeddings of GitHub Gists to the markdown renderer. + */ +export class GistAppExtension extends AppExtension { + buildMarkdownRendererExtensions(): MarkdownRendererExtension[] { + return [new GistMarkdownExtension()] + } +} diff --git a/src/components/markdown-renderer/markdown-extension/gist/gist-frame.module.scss b/src/extensions/extra-integrations/gist/gist-frame.module.scss similarity index 81% rename from src/components/markdown-renderer/markdown-extension/gist/gist-frame.module.scss rename to src/extensions/extra-integrations/gist/gist-frame.module.scss index 30e0dec5b..8f62e757a 100644 --- a/src/components/markdown-renderer/markdown-extension/gist/gist-frame.module.scss +++ b/src/extensions/extra-integrations/gist/gist-frame.module.scss @@ -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 */ diff --git a/src/components/markdown-renderer/markdown-extension/gist/gist-frame.tsx b/src/extensions/extra-integrations/gist/gist-frame.tsx similarity index 77% rename from src/components/markdown-renderer/markdown-extension/gist/gist-frame.tsx rename to src/extensions/extra-integrations/gist/gist-frame.tsx index f5282c1f9..c8954bb96 100644 --- a/src/components/markdown-renderer/markdown-extension/gist/gist-frame.tsx +++ b/src/extensions/extra-integrations/gist/gist-frame.tsx @@ -1,15 +1,15 @@ /* - * 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 React, { useCallback } from 'react' -import { cypressId } from '../../../../utils/cypress-attribute' import styles from './gist-frame.module.scss' import { useResizeGistFrame } from './use-resize-gist-frame' -import type { IdProps } from '../../replace-components/custom-tag-with-id-component-replacer' -import { ClickShield } from '../../replace-components/click-shield/click-shield' +import type { IdProps } from '../../../components/markdown-renderer/replace-components/custom-tag-with-id-component-replacer' +import { cypressId } from '../../../utils/cypress-attribute' +import { ClickShield } from '../../../components/markdown-renderer/replace-components/click-shield/click-shield' /** * This component renders a GitHub Gist by placing the gist URL in an {@link HTMLIFrameElement iframe}. diff --git a/src/components/markdown-renderer/markdown-extension/gist/gist-markdown-extension.ts b/src/extensions/extra-integrations/gist/gist-markdown-extension.ts similarity index 67% rename from src/components/markdown-renderer/markdown-extension/gist/gist-markdown-extension.ts rename to src/extensions/extra-integrations/gist/gist-markdown-extension.ts index 56042c13a..07b044258 100644 --- a/src/components/markdown-renderer/markdown-extension/gist/gist-markdown-extension.ts +++ b/src/extensions/extra-integrations/gist/gist-markdown-extension.ts @@ -4,19 +4,19 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { MarkdownExtension } from '../markdown-extension' import markdownItRegex from 'markdown-it-regex' import type MarkdownIt from 'markdown-it' -import type { ComponentReplacer } from '../../replace-components/component-replacer' -import { CustomTagWithIdComponentReplacer } from '../../replace-components/custom-tag-with-id-component-replacer' import { replaceGistLink } from './replace-gist-link' import { replaceLegacyGistShortCode } from './replace-legacy-gist-short-code' import { GistFrame } from './gist-frame' +import type { ComponentReplacer } from '../../../components/markdown-renderer/replace-components/component-replacer' +import { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension' +import { CustomTagWithIdComponentReplacer } from '../../../components/markdown-renderer/replace-components/custom-tag-with-id-component-replacer' /** * Adds support for embeddings of GitHub Gists by detecting gist links and the legacy gist shortcode. */ -export class GistMarkdownExtension extends MarkdownExtension { +export class GistMarkdownExtension extends MarkdownRendererExtension { public static readonly tagName = 'app-gist' public configureMarkdownIt(markdownIt: MarkdownIt): void { diff --git a/src/components/markdown-renderer/markdown-extension/gist/replace-gist-link.ts b/src/extensions/extra-integrations/gist/replace-gist-link.ts similarity index 90% rename from src/components/markdown-renderer/markdown-extension/gist/replace-gist-link.ts rename to src/extensions/extra-integrations/gist/replace-gist-link.ts index 14f9eb9e4..ef1034066 100644 --- a/src/components/markdown-renderer/markdown-extension/gist/replace-gist-link.ts +++ b/src/extensions/extra-integrations/gist/replace-gist-link.ts @@ -4,8 +4,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import type { RegexOptions } from '../../../../external-types/markdown-it-regex/interface' import { GistMarkdownExtension } from './gist-markdown-extension' +import type { RegexOptions } from '../../../external-types/markdown-it-regex/interface' const protocolRegex = /(?:http(?:s)?:\/\/)?/ const domainRegex = /(?:gist\.github\.com\/)/ diff --git a/src/components/markdown-renderer/markdown-extension/gist/replace-legacy-gist-short-code.ts b/src/extensions/extra-integrations/gist/replace-legacy-gist-short-code.ts similarity index 88% rename from src/components/markdown-renderer/markdown-extension/gist/replace-legacy-gist-short-code.ts rename to src/extensions/extra-integrations/gist/replace-legacy-gist-short-code.ts index 54bd62c03..d29b36e7a 100644 --- a/src/components/markdown-renderer/markdown-extension/gist/replace-legacy-gist-short-code.ts +++ b/src/extensions/extra-integrations/gist/replace-legacy-gist-short-code.ts @@ -4,8 +4,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import type { RegexOptions } from '../../../../external-types/markdown-it-regex/interface' import { GistMarkdownExtension } from './gist-markdown-extension' +import type { RegexOptions } from '../../../external-types/markdown-it-regex/interface' const finalRegex = /^{%gist\s+(\w+\/\w+)\s*%}$/ diff --git a/src/components/markdown-renderer/markdown-extension/gist/use-resize-gist-frame.ts b/src/extensions/extra-integrations/gist/use-resize-gist-frame.ts similarity index 96% rename from src/components/markdown-renderer/markdown-extension/gist/use-resize-gist-frame.ts rename to src/extensions/extra-integrations/gist/use-resize-gist-frame.ts index 77cd4bb1f..2cc7ac59a 100644 --- a/src/components/markdown-renderer/markdown-extension/gist/use-resize-gist-frame.ts +++ b/src/extensions/extra-integrations/gist/use-resize-gist-frame.ts @@ -6,7 +6,7 @@ import type React from 'react' import { useCallback, useRef, useState } from 'react' -import { useBindPointerMovementEventOnWindow } from '../../../../hooks/common/use-bind-pointer-movement-event-on-window' +import { useBindPointerMovementEventOnWindow } from '../../../hooks/common/use-bind-pointer-movement-event-on-window' /** * Determines if the left mouse button is pressed in the given event diff --git a/src/components/markdown-renderer/markdown-extension/graphviz/__snapshots__/graphviz-markdown-extension.test.tsx.snap b/src/extensions/extra-integrations/graphviz/__snapshots__/graphviz-markdown-extension.test.tsx.snap similarity index 100% rename from src/components/markdown-renderer/markdown-extension/graphviz/__snapshots__/graphviz-markdown-extension.test.tsx.snap rename to src/extensions/extra-integrations/graphviz/__snapshots__/graphviz-markdown-extension.test.tsx.snap diff --git a/src/extensions/extra-integrations/graphviz/graphviz-app-extension.ts b/src/extensions/extra-integrations/graphviz/graphviz-app-extension.ts new file mode 100644 index 000000000..4ca5d3243 --- /dev/null +++ b/src/extensions/extra-integrations/graphviz/graphviz-app-extension.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { AppExtension } from '../../base/app-extension' +import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension' +import { GraphvizMarkdownExtension } from './graphviz-markdown-extension' + +/** + * Adds support for graphviz to the markdown rendering. + */ +export class GraphvizAppExtension extends AppExtension { + buildMarkdownRendererExtensions(): MarkdownRendererExtension[] { + return [new GraphvizMarkdownExtension()] + } +} diff --git a/src/components/markdown-renderer/markdown-extension/graphviz/graphviz-frame.tsx b/src/extensions/extra-integrations/graphviz/graphviz-frame.tsx similarity index 87% rename from src/components/markdown-renderer/markdown-extension/graphviz/graphviz-frame.tsx rename to src/extensions/extra-integrations/graphviz/graphviz-frame.tsx index 264b541ed..21fdc8622 100644 --- a/src/components/markdown-renderer/markdown-extension/graphviz/graphviz-frame.tsx +++ b/src/extensions/extra-integrations/graphviz/graphviz-frame.tsx @@ -6,11 +6,11 @@ import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react' import { Alert } from 'react-bootstrap' -import { ShowIf } from '../../../common/show-if/show-if' -import { Logger } from '../../../../utils/logger' -import { cypressId } from '../../../../utils/cypress-attribute' -import type { CodeProps } from '../../replace-components/code-block-component-replacer' import { useRouter } from 'next/router' +import type { CodeProps } from '../../../components/markdown-renderer/replace-components/code-block-component-replacer' +import { cypressId } from '../../../utils/cypress-attribute' +import { ShowIf } from '../../../components/common/show-if/show-if' +import { Logger } from '../../../utils/logger' const log = new Logger('GraphvizFrame') /** diff --git a/src/components/markdown-renderer/markdown-extension/graphviz/graphviz-markdown-extension.test.tsx b/src/extensions/extra-integrations/graphviz/graphviz-markdown-extension.test.tsx similarity index 77% rename from src/components/markdown-renderer/markdown-extension/graphviz/graphviz-markdown-extension.test.tsx rename to src/extensions/extra-integrations/graphviz/graphviz-markdown-extension.test.tsx index a64dbe0dd..e753f1385 100644 --- a/src/components/markdown-renderer/markdown-extension/graphviz/graphviz-markdown-extension.test.tsx +++ b/src/extensions/extra-integrations/graphviz/graphviz-markdown-extension.test.tsx @@ -5,12 +5,12 @@ */ import { render } from '@testing-library/react' -import { TestMarkdownRenderer } from '../../test-utils/test-markdown-renderer' import React from 'react' -import { mockI18n } from '../../test-utils/mock-i18n' import { GraphvizMarkdownExtension } from './graphviz-markdown-extension' import * as GraphvizFrameModule from '../graphviz/graphviz-frame' -import type { CodeProps } from '../../replace-components/code-block-component-replacer' +import { mockI18n } from '../../../components/markdown-renderer/test-utils/mock-i18n' +import type { CodeProps } from '../../../components/markdown-renderer/replace-components/code-block-component-replacer' +import { TestMarkdownRenderer } from '../../../components/markdown-renderer/test-utils/test-markdown-renderer' jest.mock('../graphviz/graphviz-frame') diff --git a/src/extensions/extra-integrations/graphviz/graphviz-markdown-extension.ts b/src/extensions/extra-integrations/graphviz/graphviz-markdown-extension.ts new file mode 100644 index 000000000..c565cddc5 --- /dev/null +++ b/src/extensions/extra-integrations/graphviz/graphviz-markdown-extension.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { GraphvizFrame } from './graphviz-frame' +import { CodeBlockMarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/code-block-markdown-extension/code-block-markdown-renderer-extension' +import { CodeBlockComponentReplacer } from '../../../components/markdown-renderer/replace-components/code-block-component-replacer' + +/** + * Adds support for graphviz to the markdown rendering using code fences with "graphviz" as language. + */ +export class GraphvizMarkdownExtension extends CodeBlockMarkdownRendererExtension { + public buildReplacers(): CodeBlockComponentReplacer[] { + return [new CodeBlockComponentReplacer(GraphvizFrame, 'graphviz')] + } +} diff --git a/src/components/markdown-renderer/markdown-extension/highlighted-fence/__snapshots__/highlighted-code-markdown-extension.test.tsx.snap b/src/extensions/extra-integrations/highlighted-code-fence/__snapshots__/highlighted-code-markdown-extension.test.tsx.snap similarity index 100% rename from src/components/markdown-renderer/markdown-extension/highlighted-fence/__snapshots__/highlighted-code-markdown-extension.test.tsx.snap rename to src/extensions/extra-integrations/highlighted-code-fence/__snapshots__/highlighted-code-markdown-extension.test.tsx.snap diff --git a/src/components/markdown-renderer/markdown-extension/highlighted-fence/__snapshots__/highlighted-code.test.tsx.snap b/src/extensions/extra-integrations/highlighted-code-fence/__snapshots__/highlighted-code.test.tsx.snap similarity index 100% rename from src/components/markdown-renderer/markdown-extension/highlighted-fence/__snapshots__/highlighted-code.test.tsx.snap rename to src/extensions/extra-integrations/highlighted-code-fence/__snapshots__/highlighted-code.test.tsx.snap diff --git a/src/extensions/extra-integrations/highlighted-code-fence/highlighted-code-fence-app-extension.ts b/src/extensions/extra-integrations/highlighted-code-fence/highlighted-code-fence-app-extension.ts new file mode 100644 index 000000000..37e2f8812 --- /dev/null +++ b/src/extensions/extra-integrations/highlighted-code-fence/highlighted-code-fence-app-extension.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { AppExtension } from '../../base/app-extension' +import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension' +import { HighlightedCodeMarkdownExtension } from './highlighted-code-markdown-extension' + +/** + * Adds code highlighting to the markdown rendering. + */ +export class HighlightedCodeFenceAppExtension extends AppExtension { + buildMarkdownRendererExtensions(): MarkdownRendererExtension[] { + return [new HighlightedCodeMarkdownExtension()] + } +} diff --git a/src/components/markdown-renderer/markdown-extension/highlighted-fence/highlighted-code-markdown-extension.test.tsx b/src/extensions/extra-integrations/highlighted-code-fence/highlighted-code-markdown-extension.test.tsx similarity index 95% rename from src/components/markdown-renderer/markdown-extension/highlighted-fence/highlighted-code-markdown-extension.test.tsx rename to src/extensions/extra-integrations/highlighted-code-fence/highlighted-code-markdown-extension.test.tsx index b86385baa..595c854bf 100644 --- a/src/components/markdown-renderer/markdown-extension/highlighted-fence/highlighted-code-markdown-extension.test.tsx +++ b/src/extensions/extra-integrations/highlighted-code-fence/highlighted-code-markdown-extension.test.tsx @@ -5,12 +5,12 @@ */ import { render } from '@testing-library/react' -import { TestMarkdownRenderer } from '../../test-utils/test-markdown-renderer' import React from 'react' import type { HighlightedCodeProps } from './highlighted-code' import * as HighlightedCodeModule from './highlighted-code' -import { mockI18n } from '../../test-utils/mock-i18n' import { HighlightedCodeMarkdownExtension } from './highlighted-code-markdown-extension' +import { mockI18n } from '../../../components/markdown-renderer/test-utils/mock-i18n' +import { TestMarkdownRenderer } from '../../../components/markdown-renderer/test-utils/test-markdown-renderer' jest.mock('./highlighted-code') diff --git a/src/extensions/extra-integrations/highlighted-code-fence/highlighted-code-markdown-extension.ts b/src/extensions/extra-integrations/highlighted-code-fence/highlighted-code-markdown-extension.ts new file mode 100644 index 000000000..4bcd0fc92 --- /dev/null +++ b/src/extensions/extra-integrations/highlighted-code-fence/highlighted-code-markdown-extension.ts @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { HighlightedCodeReplacer } from './highlighted-code-replacer' +import { CodeBlockMarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/code-block-markdown-extension/code-block-markdown-renderer-extension' +import type { ComponentReplacer } from '../../../components/markdown-renderer/replace-components/component-replacer' + +/** + * Adds code highlighting to the markdown rendering. + * Every code fence that is not replaced by another replacer is highlighted using highlight-js. + */ +export class HighlightedCodeMarkdownExtension extends CodeBlockMarkdownRendererExtension { + public buildReplacers(): ComponentReplacer[] { + return [new HighlightedCodeReplacer()] + } +} diff --git a/src/components/markdown-renderer/markdown-extension/highlighted-fence/highlighted-code-replacer.tsx b/src/extensions/extra-integrations/highlighted-code-fence/highlighted-code-replacer.tsx similarity index 87% rename from src/components/markdown-renderer/markdown-extension/highlighted-fence/highlighted-code-replacer.tsx rename to src/extensions/extra-integrations/highlighted-code-fence/highlighted-code-replacer.tsx index cf63d2b88..45e1932a7 100644 --- a/src/components/markdown-renderer/markdown-extension/highlighted-fence/highlighted-code-replacer.tsx +++ b/src/extensions/extra-integrations/highlighted-code-fence/highlighted-code-replacer.tsx @@ -6,8 +6,11 @@ import type { Element } 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 { + ComponentReplacer, + DO_NOT_REPLACE +} from '../../../components/markdown-renderer/replace-components/component-replacer' +import type { NodeReplacement } from '../../../components/markdown-renderer/replace-components/component-replacer' import { HighlightedCode } from './highlighted-code' /** diff --git a/src/components/markdown-renderer/markdown-extension/highlighted-fence/highlighted-code.module.scss b/src/extensions/extra-integrations/highlighted-code-fence/highlighted-code.module.scss similarity index 93% rename from src/components/markdown-renderer/markdown-extension/highlighted-fence/highlighted-code.module.scss rename to src/extensions/extra-integrations/highlighted-code-fence/highlighted-code.module.scss index b72bca772..908443205 100644 --- a/src/components/markdown-renderer/markdown-extension/highlighted-fence/highlighted-code.module.scss +++ b/src/extensions/extra-integrations/highlighted-code-fence/highlighted-code.module.scss @@ -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 */ diff --git a/src/components/markdown-renderer/markdown-extension/highlighted-fence/highlighted-code.test.tsx b/src/extensions/extra-integrations/highlighted-code-fence/highlighted-code.test.tsx similarity index 95% rename from src/components/markdown-renderer/markdown-extension/highlighted-fence/highlighted-code.test.tsx rename to src/extensions/extra-integrations/highlighted-code-fence/highlighted-code.test.tsx index 1f01b06c7..fd67802b7 100644 --- a/src/components/markdown-renderer/markdown-extension/highlighted-fence/highlighted-code.test.tsx +++ b/src/extensions/extra-integrations/highlighted-code-fence/highlighted-code.test.tsx @@ -6,7 +6,7 @@ import { render, screen } from '@testing-library/react' import HighlightedCode from './highlighted-code' -import { mockI18n } from '../../test-utils/mock-i18n' +import { mockI18n } from '../../../components/markdown-renderer/test-utils/mock-i18n' describe('Highlighted Code', () => { beforeAll(() => mockI18n()) diff --git a/src/components/markdown-renderer/markdown-extension/highlighted-fence/highlighted-code.tsx b/src/extensions/extra-integrations/highlighted-code-fence/highlighted-code.tsx similarity index 86% rename from src/components/markdown-renderer/markdown-extension/highlighted-fence/highlighted-code.tsx rename to src/extensions/extra-integrations/highlighted-code-fence/highlighted-code.tsx index 78f170d9e..f007c64d6 100644 --- a/src/components/markdown-renderer/markdown-extension/highlighted-fence/highlighted-code.tsx +++ b/src/extensions/extra-integrations/highlighted-code-fence/highlighted-code.tsx @@ -5,14 +5,14 @@ */ import React from 'react' -import { CopyToClipboardButton } from '../../../common/copyable/copy-to-clipboard-button/copy-to-clipboard-button' import styles from './highlighted-code.module.scss' -import { cypressAttribute, cypressId } from '../../../../utils/cypress-attribute' -import { AsyncLoadingBoundary } from '../../../common/async-loading-boundary' import { useAsyncHighlightJsImport } from './hooks/use-async-highlight-js-import' import { useAttachLineNumbers } from './hooks/use-attach-line-numbers' -import { testId } from '../../../../utils/test-id' import { useCodeDom } from './hooks/use-code-dom' +import { cypressAttribute, cypressId } from '../../../utils/cypress-attribute' +import { AsyncLoadingBoundary } from '../../../components/common/async-loading-boundary' +import { CopyToClipboardButton } from '../../../components/common/copyable/copy-to-clipboard-button/copy-to-clipboard-button' +import { testId } from '../../../utils/test-id' export interface HighlightedCodeProps { code: string diff --git a/src/components/markdown-renderer/markdown-extension/highlighted-fence/hooks/use-async-highlight-js-import.tsx b/src/extensions/extra-integrations/highlighted-code-fence/hooks/use-async-highlight-js-import.tsx similarity index 87% rename from src/components/markdown-renderer/markdown-extension/highlighted-fence/hooks/use-async-highlight-js-import.tsx rename to src/extensions/extra-integrations/highlighted-code-fence/hooks/use-async-highlight-js-import.tsx index 511cead54..5f54b02d3 100644 --- a/src/components/markdown-renderer/markdown-extension/highlighted-fence/hooks/use-async-highlight-js-import.tsx +++ b/src/extensions/extra-integrations/highlighted-code-fence/hooks/use-async-highlight-js-import.tsx @@ -5,9 +5,9 @@ */ import { useAsync } from 'react-use' -import { Logger } from '../../../../../utils/logger' import type { AsyncState } from 'react-use/lib/useAsyncFn' import type { HLJSApi } from 'highlight.js' +import { Logger } from '../../../../utils/logger' const log = new Logger('HighlightedCode') @@ -19,7 +19,7 @@ const log = new Logger('HighlightedCode') export const useAsyncHighlightJsImport = (): AsyncState => { return useAsync(async () => { try { - return (await import(/* webpackChunkName: "highlight.js" */ '../../../../common/hljs/hljs')).default + return (await import(/* webpackChunkName: "highlight.js" */ '../preconfigured-highlight-js')).default } catch (error) { log.error('Error while loading highlight.js', error) throw error diff --git a/src/components/markdown-renderer/markdown-extension/highlighted-fence/hooks/use-attach-line-numbers.tsx b/src/extensions/extra-integrations/highlighted-code-fence/hooks/use-attach-line-numbers.tsx similarity index 93% rename from src/components/markdown-renderer/markdown-extension/highlighted-fence/hooks/use-attach-line-numbers.tsx rename to src/extensions/extra-integrations/highlighted-code-fence/hooks/use-attach-line-numbers.tsx index a47b8d6ab..6894fd4d9 100644 --- a/src/components/markdown-renderer/markdown-extension/highlighted-fence/hooks/use-attach-line-numbers.tsx +++ b/src/extensions/extra-integrations/highlighted-code-fence/hooks/use-attach-line-numbers.tsx @@ -6,8 +6,8 @@ import type { ReactElement } from 'react' import { Fragment, useMemo } from 'react' -import { cypressId } from '../../../../../utils/cypress-attribute' import styles from '../highlighted-code.module.scss' +import { cypressId } from '../../../../utils/cypress-attribute' /** * Wraps the given {@link ReactElement elements} to attach line numbers to them. diff --git a/src/components/markdown-renderer/markdown-extension/highlighted-fence/hooks/use-code-dom.tsx b/src/extensions/extra-integrations/highlighted-code-fence/hooks/use-code-dom.tsx similarity index 100% rename from src/components/markdown-renderer/markdown-extension/highlighted-fence/hooks/use-code-dom.tsx rename to src/extensions/extra-integrations/highlighted-code-fence/hooks/use-code-dom.tsx diff --git a/src/components/common/hljs/hljs.ts b/src/extensions/extra-integrations/highlighted-code-fence/preconfigured-highlight-js.ts similarity index 99% rename from src/components/common/hljs/hljs.ts rename to src/extensions/extra-integrations/highlighted-code-fence/preconfigured-highlight-js.ts index 983fafd35..57cfceb40 100644 --- a/src/components/common/hljs/hljs.ts +++ b/src/extensions/extra-integrations/highlighted-code-fence/preconfigured-highlight-js.ts @@ -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 */ diff --git a/src/components/markdown-renderer/markdown-extension/katex/__snapshots__/katex-frame.test.tsx.snap b/src/extensions/extra-integrations/katex/__snapshots__/katex-frame.test.tsx.snap similarity index 100% rename from src/components/markdown-renderer/markdown-extension/katex/__snapshots__/katex-frame.test.tsx.snap rename to src/extensions/extra-integrations/katex/__snapshots__/katex-frame.test.tsx.snap diff --git a/src/components/markdown-renderer/markdown-extension/katex/__snapshots__/katex-markdown-extension.test.tsx.snap b/src/extensions/extra-integrations/katex/__snapshots__/katex-markdown-extension.test.tsx.snap similarity index 100% rename from src/components/markdown-renderer/markdown-extension/katex/__snapshots__/katex-markdown-extension.test.tsx.snap rename to src/extensions/extra-integrations/katex/__snapshots__/katex-markdown-extension.test.tsx.snap diff --git a/src/extensions/extra-integrations/katex/katex-app-extension.ts b/src/extensions/extra-integrations/katex/katex-app-extension.ts new file mode 100644 index 000000000..ad0a8e651 --- /dev/null +++ b/src/extensions/extra-integrations/katex/katex-app-extension.ts @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { AppExtension } from '../../base/app-extension' +import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension' +import { KatexMarkdownExtension } from './katex-markdown-extension' + +/** + * Adds support for LaTeX rendering using KaTeX to the markdown rendering. + * + * @see https://katex.org/ + */ +export class KatexAppExtension extends AppExtension { + buildMarkdownRendererExtensions(): MarkdownRendererExtension[] { + return [new KatexMarkdownExtension()] + } +} diff --git a/src/components/markdown-renderer/markdown-extension/katex/katex-frame.test.tsx b/src/extensions/extra-integrations/katex/katex-frame.test.tsx similarity index 100% rename from src/components/markdown-renderer/markdown-extension/katex/katex-frame.test.tsx rename to src/extensions/extra-integrations/katex/katex-frame.test.tsx diff --git a/src/components/markdown-renderer/markdown-extension/katex/katex-frame.tsx b/src/extensions/extra-integrations/katex/katex-frame.tsx similarity index 96% rename from src/components/markdown-renderer/markdown-extension/katex/katex-frame.tsx rename to src/extensions/extra-integrations/katex/katex-frame.tsx index c7ae3c0cf..34e95cffd 100644 --- a/src/components/markdown-renderer/markdown-extension/katex/katex-frame.tsx +++ b/src/extensions/extra-integrations/katex/katex-frame.tsx @@ -9,8 +9,8 @@ import KaTeX from 'katex' import convertHtmlToReact from '@hedgedoc/html-to-react' import 'katex/dist/katex.min.css' import { Alert } from 'react-bootstrap' -import { testId } from '../../../../utils/test-id' import { sanitize } from 'dompurify' +import { testId } from '../../../utils/test-id' interface KatexFrameProps { expression: string diff --git a/src/components/markdown-renderer/markdown-extension/katex/katex-markdown-extension.test.tsx b/src/extensions/extra-integrations/katex/katex-markdown-extension.test.tsx similarity index 95% rename from src/components/markdown-renderer/markdown-extension/katex/katex-markdown-extension.test.tsx rename to src/extensions/extra-integrations/katex/katex-markdown-extension.test.tsx index 1294d3fe2..94de9bc4b 100644 --- a/src/components/markdown-renderer/markdown-extension/katex/katex-markdown-extension.test.tsx +++ b/src/extensions/extra-integrations/katex/katex-markdown-extension.test.tsx @@ -6,10 +6,10 @@ import { render, screen } from '@testing-library/react' import { KatexMarkdownExtension } from './katex-markdown-extension' -import { TestMarkdownRenderer } from '../../test-utils/test-markdown-renderer' import { Suspense } from 'react' import type { KatexOptions } from 'katex' import { default as KatexDefault } from 'katex' +import { TestMarkdownRenderer } from '../../../components/markdown-renderer/test-utils/test-markdown-renderer' jest.mock('katex') diff --git a/src/components/markdown-renderer/markdown-extension/katex/katex-markdown-extension.ts b/src/extensions/extra-integrations/katex/katex-markdown-extension.ts similarity index 77% rename from src/components/markdown-renderer/markdown-extension/katex/katex-markdown-extension.ts rename to src/extensions/extra-integrations/katex/katex-markdown-extension.ts index c00e369e3..6f041ab4e 100644 --- a/src/components/markdown-renderer/markdown-extension/katex/katex-markdown-extension.ts +++ b/src/extensions/extra-integrations/katex/katex-markdown-extension.ts @@ -4,18 +4,18 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { MarkdownExtension } from '../markdown-extension' import mathJax from 'markdown-it-mathjax' import type MarkdownIt from 'markdown-it' -import type { ComponentReplacer } from '../../replace-components/component-replacer' import { KatexReplacer } from './katex-replacer' +import type { ComponentReplacer } from '../../../components/markdown-renderer/replace-components/component-replacer' +import { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension' /** * Adds support for rendering of LaTeX code using KaTeX. * * @see https://katex.org/ */ -export class KatexMarkdownExtension extends MarkdownExtension { +export class KatexMarkdownExtension extends MarkdownRendererExtension { public static readonly tagName = 'app-katex' public configureMarkdownIt(markdownIt: MarkdownIt): void { diff --git a/src/components/markdown-renderer/markdown-extension/katex/katex-replacer.tsx b/src/extensions/extra-integrations/katex/katex-replacer.tsx similarity index 90% rename from src/components/markdown-renderer/markdown-extension/katex/katex-replacer.tsx rename to src/extensions/extra-integrations/katex/katex-replacer.tsx index 9f7f00d30..126caf2aa 100644 --- a/src/components/markdown-renderer/markdown-extension/katex/katex-replacer.tsx +++ b/src/extensions/extra-integrations/katex/katex-replacer.tsx @@ -7,8 +7,11 @@ 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 type { NodeReplacement } from '../../../components/markdown-renderer/replace-components/component-replacer' +import { + ComponentReplacer, + DO_NOT_REPLACE +} from '../../../components/markdown-renderer/replace-components/component-replacer' import { KatexMarkdownExtension } from './katex-markdown-extension' import { Optional } from '@mrdrogdrog/optional' diff --git a/src/components/markdown-renderer/markdown-extension/legacy-short-codes/__snapshots__/legacy-shortcodes-markdown-extension.test.tsx.snap b/src/extensions/extra-integrations/legacy-short-codes/__snapshots__/legacy-shortcodes-markdown-extension.test.tsx.snap similarity index 100% rename from src/components/markdown-renderer/markdown-extension/legacy-short-codes/__snapshots__/legacy-shortcodes-markdown-extension.test.tsx.snap rename to src/extensions/extra-integrations/legacy-short-codes/__snapshots__/legacy-shortcodes-markdown-extension.test.tsx.snap diff --git a/src/extensions/extra-integrations/legacy-short-codes/legacy-shortcodes-app-extension.ts b/src/extensions/extra-integrations/legacy-short-codes/legacy-shortcodes-app-extension.ts new file mode 100644 index 000000000..fd8a0849f --- /dev/null +++ b/src/extensions/extra-integrations/legacy-short-codes/legacy-shortcodes-app-extension.ts @@ -0,0 +1,43 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { AppExtension } from '../../base/app-extension' +import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension' +import { LegacyShortcodesMarkdownExtension } from './legacy-shortcodes-markdown-extension' +import type { Linter } from '../../../components/editor-page/editor-pane/linter/linter' +import { SingleLineRegexLinter } from '../../../components/editor-page/editor-pane/linter/single-line-regex-linter' +import { legacySpeakerdeckRegex } from './replace-legacy-speakerdeck-short-code' +import { t } from 'i18next' +import { legacySlideshareRegex } from './replace-legacy-slideshare-short-code' +import { legacyPdfRegex } from './replace-legacy-pdf-short-code' + +/** + * Adds support for legacy shortcodes (pdf, slideshare and speakerdeck) from HedgeDoc 1 to the markdown renderer. + */ +export class LegacyShortcodesAppExtension extends AppExtension { + buildMarkdownRendererExtensions(): MarkdownRendererExtension[] { + return [new LegacyShortcodesMarkdownExtension()] + } + + buildCodeMirrorLinter(): Linter[] { + return [ + new SingleLineRegexLinter( + legacySpeakerdeckRegex, + t('editor.linter.shortcode', { shortcode: 'SpeakerDeck' }), + (match: string) => `https://speakerdeck.com/${match}` + ), + new SingleLineRegexLinter( + legacySlideshareRegex, + t('editor.linter.shortcode', { shortcode: 'SlideShare' }), + (match: string) => `https://www.slideshare.net/${match}` + ), + new SingleLineRegexLinter( + legacyPdfRegex, + t('editor.linter.shortcode', { shortcode: 'PDF' }), + (match: string) => match + ) + ] + } +} diff --git a/src/components/markdown-renderer/markdown-extension/legacy-short-codes/legacy-shortcodes-markdown-extension.test.tsx b/src/extensions/extra-integrations/legacy-short-codes/legacy-shortcodes-markdown-extension.test.tsx similarity index 92% rename from src/components/markdown-renderer/markdown-extension/legacy-short-codes/legacy-shortcodes-markdown-extension.test.tsx rename to src/extensions/extra-integrations/legacy-short-codes/legacy-shortcodes-markdown-extension.test.tsx index b5fb509be..9a0d4f708 100644 --- a/src/components/markdown-renderer/markdown-extension/legacy-short-codes/legacy-shortcodes-markdown-extension.test.tsx +++ b/src/extensions/extra-integrations/legacy-short-codes/legacy-shortcodes-markdown-extension.test.tsx @@ -4,9 +4,9 @@ * SPDX-License-Identifier: AGPL-3.0-only */ import { render } from '@testing-library/react' -import { TestMarkdownRenderer } from '../../test-utils/test-markdown-renderer' import React from 'react' import { LegacyShortcodesMarkdownExtension } from './legacy-shortcodes-markdown-extension' +import { TestMarkdownRenderer } from '../../../components/markdown-renderer/test-utils/test-markdown-renderer' describe('Legacy shortcodes markdown extension', () => { it('transforms a pdf short code into an URL', () => { diff --git a/src/extensions/extra-integrations/legacy-short-codes/legacy-shortcodes-markdown-extension.ts b/src/extensions/extra-integrations/legacy-short-codes/legacy-shortcodes-markdown-extension.ts new file mode 100644 index 000000000..da08ec1e6 --- /dev/null +++ b/src/extensions/extra-integrations/legacy-short-codes/legacy-shortcodes-markdown-extension.ts @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type MarkdownIt from 'markdown-it' +import { legacyPdfShortCode } from './replace-legacy-pdf-short-code' +import { legacySlideshareShortCode } from './replace-legacy-slideshare-short-code' +import { legacySpeakerdeckShortCode } from './replace-legacy-speakerdeck-short-code' +import { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension' + +/** + * Adds support for legacy shortcodes (pdf, slideshare and speakerdeck) by replacing them with anchor elements. + */ +export class LegacyShortcodesMarkdownExtension extends MarkdownRendererExtension { + public configureMarkdownIt(markdownIt: MarkdownIt): void { + legacyPdfShortCode(markdownIt) + legacySlideshareShortCode(markdownIt) + legacySpeakerdeckShortCode(markdownIt) + } +} diff --git a/src/components/markdown-renderer/markdown-extension/legacy-short-codes/replace-legacy-pdf-short-code.test.ts b/src/extensions/extra-integrations/legacy-short-codes/replace-legacy-pdf-short-code.test.ts similarity index 100% rename from src/components/markdown-renderer/markdown-extension/legacy-short-codes/replace-legacy-pdf-short-code.test.ts rename to src/extensions/extra-integrations/legacy-short-codes/replace-legacy-pdf-short-code.test.ts diff --git a/src/components/markdown-renderer/markdown-extension/legacy-short-codes/replace-legacy-pdf-short-code.ts b/src/extensions/extra-integrations/legacy-short-codes/replace-legacy-pdf-short-code.ts similarity index 88% rename from src/components/markdown-renderer/markdown-extension/legacy-short-codes/replace-legacy-pdf-short-code.ts rename to src/extensions/extra-integrations/legacy-short-codes/replace-legacy-pdf-short-code.ts index 5670e102c..fe1436053 100644 --- a/src/components/markdown-renderer/markdown-extension/legacy-short-codes/replace-legacy-pdf-short-code.ts +++ b/src/extensions/extra-integrations/legacy-short-codes/replace-legacy-pdf-short-code.ts @@ -6,7 +6,7 @@ import markdownItRegex from 'markdown-it-regex' import type MarkdownIt from 'markdown-it/lib' -import type { RegexOptions } from '../../../../external-types/markdown-it-regex/interface' +import type { RegexOptions } from '../../../external-types/markdown-it-regex/interface' export const legacyPdfRegex = /^{%pdf\s+(\S*)\s*%}$/ diff --git a/src/components/markdown-renderer/markdown-extension/legacy-short-codes/replace-legacy-slideshare-short-code.test.ts b/src/extensions/extra-integrations/legacy-short-codes/replace-legacy-slideshare-short-code.test.ts similarity index 100% rename from src/components/markdown-renderer/markdown-extension/legacy-short-codes/replace-legacy-slideshare-short-code.test.ts rename to src/extensions/extra-integrations/legacy-short-codes/replace-legacy-slideshare-short-code.test.ts diff --git a/src/components/markdown-renderer/markdown-extension/legacy-short-codes/replace-legacy-slideshare-short-code.ts b/src/extensions/extra-integrations/legacy-short-codes/replace-legacy-slideshare-short-code.ts similarity index 89% rename from src/components/markdown-renderer/markdown-extension/legacy-short-codes/replace-legacy-slideshare-short-code.ts rename to src/extensions/extra-integrations/legacy-short-codes/replace-legacy-slideshare-short-code.ts index 70771a574..2f0621c7e 100644 --- a/src/components/markdown-renderer/markdown-extension/legacy-short-codes/replace-legacy-slideshare-short-code.ts +++ b/src/extensions/extra-integrations/legacy-short-codes/replace-legacy-slideshare-short-code.ts @@ -6,7 +6,7 @@ import markdownItRegex from 'markdown-it-regex' import type MarkdownIt from 'markdown-it/lib' -import type { RegexOptions } from '../../../../external-types/markdown-it-regex/interface' +import type { RegexOptions } from '../../../external-types/markdown-it-regex/interface' export const legacySlideshareRegex = /^{%slideshare\s+(\w+\/[\w-]+)\s*%}$/ diff --git a/src/components/markdown-renderer/markdown-extension/legacy-short-codes/replace-legacy-speakerdeck-short-code.test.ts b/src/extensions/extra-integrations/legacy-short-codes/replace-legacy-speakerdeck-short-code.test.ts similarity index 100% rename from src/components/markdown-renderer/markdown-extension/legacy-short-codes/replace-legacy-speakerdeck-short-code.test.ts rename to src/extensions/extra-integrations/legacy-short-codes/replace-legacy-speakerdeck-short-code.test.ts diff --git a/src/components/markdown-renderer/markdown-extension/legacy-short-codes/replace-legacy-speakerdeck-short-code.ts b/src/extensions/extra-integrations/legacy-short-codes/replace-legacy-speakerdeck-short-code.ts similarity index 89% rename from src/components/markdown-renderer/markdown-extension/legacy-short-codes/replace-legacy-speakerdeck-short-code.ts rename to src/extensions/extra-integrations/legacy-short-codes/replace-legacy-speakerdeck-short-code.ts index d0d433e48..a779f1e9e 100644 --- a/src/components/markdown-renderer/markdown-extension/legacy-short-codes/replace-legacy-speakerdeck-short-code.ts +++ b/src/extensions/extra-integrations/legacy-short-codes/replace-legacy-speakerdeck-short-code.ts @@ -6,7 +6,7 @@ import markdownItRegex from 'markdown-it-regex' import type MarkdownIt from 'markdown-it/lib' -import type { RegexOptions } from '../../../../external-types/markdown-it-regex/interface' +import type { RegexOptions } from '../../../external-types/markdown-it-regex/interface' export const legacySpeakerdeckRegex = /^{%speakerdeck\s+(\w+\/[\w-]+)\s*%}$/ diff --git a/src/extensions/extra-integrations/mermaid/mermaid-app-extension.ts b/src/extensions/extra-integrations/mermaid/mermaid-app-extension.ts new file mode 100644 index 000000000..8b58b254a --- /dev/null +++ b/src/extensions/extra-integrations/mermaid/mermaid-app-extension.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { AppExtension } from '../../base/app-extension' +import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension' +import { MermaidMarkdownExtension } from './mermaid-markdown-extension' + +/** + * Adds support for chart rendering using mermaid to the markdown renderer. + */ +export class MermaidAppExtension extends AppExtension { + buildMarkdownRendererExtensions(): MarkdownRendererExtension[] { + return [new MermaidMarkdownExtension()] + } +} diff --git a/src/components/markdown-renderer/markdown-extension/mermaid/mermaid-chart.tsx b/src/extensions/extra-integrations/mermaid/mermaid-chart.tsx similarity index 89% rename from src/components/markdown-renderer/markdown-extension/mermaid/mermaid-chart.tsx rename to src/extensions/extra-integrations/mermaid/mermaid-chart.tsx index c0ca94012..b1f16ce91 100644 --- a/src/components/markdown-renderer/markdown-extension/mermaid/mermaid-chart.tsx +++ b/src/extensions/extra-integrations/mermaid/mermaid-chart.tsx @@ -7,11 +7,11 @@ import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react' import { Alert } from 'react-bootstrap' import { useTranslation } from 'react-i18next' -import { ShowIf } from '../../../common/show-if/show-if' import styles from './mermaid.module.scss' -import { Logger } from '../../../../utils/logger' -import type { CodeProps } from '../../replace-components/code-block-component-replacer' -import { cypressId } from '../../../../utils/cypress-attribute' +import type { CodeProps } from '../../../components/markdown-renderer/replace-components/code-block-component-replacer' +import { cypressId } from '../../../utils/cypress-attribute' +import { ShowIf } from '../../../components/common/show-if/show-if' +import { Logger } from '../../../utils/logger' const log = new Logger('MermaidChart') diff --git a/src/extensions/extra-integrations/mermaid/mermaid-markdown-extension.ts b/src/extensions/extra-integrations/mermaid/mermaid-markdown-extension.ts new file mode 100644 index 000000000..616e6e8f0 --- /dev/null +++ b/src/extensions/extra-integrations/mermaid/mermaid-markdown-extension.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { MermaidChart } from './mermaid-chart' +import { CodeBlockMarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/code-block-markdown-extension/code-block-markdown-renderer-extension' +import { CodeBlockComponentReplacer } from '../../../components/markdown-renderer/replace-components/code-block-component-replacer' + +/** + * Adds support for chart rendering using mermaid to the markdown rendering using code fences with "mermaid" as language. + */ +export class MermaidMarkdownExtension extends CodeBlockMarkdownRendererExtension { + public buildReplacers(): CodeBlockComponentReplacer[] { + return [new CodeBlockComponentReplacer(MermaidChart, 'mermaid')] + } +} diff --git a/src/components/markdown-renderer/markdown-extension/mermaid/mermaid.module.scss b/src/extensions/extra-integrations/mermaid/mermaid.module.scss similarity index 61% rename from src/components/markdown-renderer/markdown-extension/mermaid/mermaid.module.scss rename to src/extensions/extra-integrations/mermaid/mermaid.module.scss index 5fa41b32e..0b3f568fb 100644 --- a/src/components/markdown-renderer/markdown-extension/mermaid/mermaid.module.scss +++ b/src/extensions/extra-integrations/mermaid/mermaid.module.scss @@ -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 */ diff --git a/src/extensions/extra-integrations/optional-app-extensions.ts b/src/extensions/extra-integrations/optional-app-extensions.ts new file mode 100644 index 000000000..9411bb415 --- /dev/null +++ b/src/extensions/extra-integrations/optional-app-extensions.ts @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { AbcjsAppExtension } from './abcjs/abcjs-app-extension' +import { AlertAppExtension } from './alert/alert-app-extension' +import { BlockquoteAppExtension } from './blockquote/blockquote-app-extension' +import { CsvTableAppExtension } from './csv/csv-table-app-extension' +import { FlowchartAppExtension } from './flowchart/flowchart-app-extension' +import { GistAppExtension } from './gist/gist-app-extension' +import { GraphvizAppExtension } from './graphviz/graphviz-app-extension' +import { KatexAppExtension } from './katex/katex-app-extension' +import { LegacyShortcodesAppExtension } from './legacy-short-codes/legacy-shortcodes-app-extension' +import { MermaidAppExtension } from './mermaid/mermaid-app-extension' +import { PlantumlAppExtension } from './plantuml/plantuml-app-extension' +import { LegacySequenceDiagramAppExtension } from './sequence-diagram/legacy-sequence-diagram-app-extension' +import { SpoilerAppExtension } from './spoiler/spoiler-app-extension' +import { VegaLiteAppExtension } from './vega-lite/vega-lite-app-extension' +import { VimeoAppExtension } from './vimeo/vimeo-app-extension' +import { YoutubeAppExtension } from './youtube/youtube-app-extension' +import { HighlightedCodeFenceAppExtension } from './highlighted-code-fence/highlighted-code-fence-app-extension' +import type { AppExtension } from '../base/app-extension' +import { TaskListCheckboxAppExtension } from './task-list/task-list-checkbox-app-extension' + +/** + * This array defines additional app extensions that are used in the editor, read only page and slideshow. + */ +export const optionalAppExtensions: AppExtension[] = [ + new AbcjsAppExtension(), + new AlertAppExtension(), + new BlockquoteAppExtension(), + new CsvTableAppExtension(), + new FlowchartAppExtension(), + new GistAppExtension(), + new GraphvizAppExtension(), + new KatexAppExtension(), + new LegacyShortcodesAppExtension(), + new MermaidAppExtension(), + new PlantumlAppExtension(), + new LegacySequenceDiagramAppExtension(), + new SpoilerAppExtension(), + new VegaLiteAppExtension(), + new VimeoAppExtension(), + new YoutubeAppExtension(), + new TaskListCheckboxAppExtension(), + new HighlightedCodeFenceAppExtension() +] diff --git a/src/components/markdown-renderer/markdown-extension/plantuml/__snapshots__/plantuml-markdown-extension.test.tsx.snap b/src/extensions/extra-integrations/plantuml/__snapshots__/plantuml-markdown-extension.test.tsx.snap similarity index 100% rename from src/components/markdown-renderer/markdown-extension/plantuml/__snapshots__/plantuml-markdown-extension.test.tsx.snap rename to src/extensions/extra-integrations/plantuml/__snapshots__/plantuml-markdown-extension.test.tsx.snap diff --git a/src/extensions/extra-integrations/plantuml/plantuml-app-extension.ts b/src/extensions/extra-integrations/plantuml/plantuml-app-extension.ts new file mode 100644 index 000000000..fb152466a --- /dev/null +++ b/src/extensions/extra-integrations/plantuml/plantuml-app-extension.ts @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { AppExtension } from '../../base/app-extension' +import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension' +import { PlantumlMarkdownExtension } from './plantuml-markdown-extension' + +/** + * Adds support for chart rendering using plantuml to the markdown renderer. + * + * @see https://plantuml.com + */ +export class PlantumlAppExtension extends AppExtension { + buildMarkdownRendererExtensions(): MarkdownRendererExtension[] { + return [new PlantumlMarkdownExtension()] + } +} diff --git a/src/components/markdown-renderer/markdown-extension/plantuml/plantuml-markdown-extension.test.tsx b/src/extensions/extra-integrations/plantuml/plantuml-markdown-extension.test.tsx similarity index 77% rename from src/components/markdown-renderer/markdown-extension/plantuml/plantuml-markdown-extension.test.tsx rename to src/extensions/extra-integrations/plantuml/plantuml-markdown-extension.test.tsx index c352df4e5..a861822e4 100644 --- a/src/components/markdown-renderer/markdown-extension/plantuml/plantuml-markdown-extension.test.tsx +++ b/src/extensions/extra-integrations/plantuml/plantuml-markdown-extension.test.tsx @@ -5,20 +5,18 @@ */ import { render } from '@testing-library/react' -import { TestMarkdownRenderer } from '../../test-utils/test-markdown-renderer' import React from 'react' import { PlantumlMarkdownExtension } from './plantuml-markdown-extension' -import { mockI18n } from '../../test-utils/mock-i18n' -import * as reduxModule from '../../../../redux' +import * as reduxModule from '../../../redux' import { Mock } from 'ts-mockery' -import type { ApplicationState } from '../../../../redux/application-state' +import { mockI18n } from '../../../components/markdown-renderer/test-utils/mock-i18n' +import type { ApplicationState } from '../../../redux/application-state' +import { TestMarkdownRenderer } from '../../../components/markdown-renderer/test-utils/test-markdown-renderer' -jest.mock('../../../../redux') +jest.mock('../../../redux') describe('PlantUML markdown extensions', () => { - beforeAll(async () => { - await mockI18n() - }) + beforeAll(() => mockI18n()) it('renders a plantuml codeblock', () => { jest.spyOn(reduxModule, 'getGlobalState').mockReturnValue( diff --git a/src/components/markdown-renderer/markdown-extension/plantuml/plantuml-markdown-extension.ts b/src/extensions/extra-integrations/plantuml/plantuml-markdown-extension.ts similarity index 82% rename from src/components/markdown-renderer/markdown-extension/plantuml/plantuml-markdown-extension.ts rename to src/extensions/extra-integrations/plantuml/plantuml-markdown-extension.ts index ce5c22ebf..fcd1c68e4 100644 --- a/src/components/markdown-renderer/markdown-extension/plantuml/plantuml-markdown-extension.ts +++ b/src/extensions/extra-integrations/plantuml/plantuml-markdown-extension.ts @@ -4,23 +4,23 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { MarkdownExtension } from '../markdown-extension' import type MarkdownIt from 'markdown-it' import plantuml from 'markdown-it-plantuml' import type Renderer from 'markdown-it/lib/renderer' import type Token from 'markdown-it/lib/token' import type { Options } from 'markdown-it/lib' -import type { ComponentReplacer } from '../../replace-components/component-replacer' import { PlantumlNotConfiguredComponentReplacer } from './plantuml-not-configured-component-replacer' -import { getGlobalState } from '../../../../redux' import { Optional } from '@mrdrogdrog/optional' +import type { ComponentReplacer } from '../../../components/markdown-renderer/replace-components/component-replacer' +import { getGlobalState } from '../../../redux' +import { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension' /** * Adds support for chart rendering using plantuml to the markdown rendering using code fences with "plantuml" as language. * * @see https://plantuml.com */ -export class PlantumlMarkdownExtension extends MarkdownExtension { +export class PlantumlMarkdownExtension extends MarkdownRendererExtension { constructor() { super() } diff --git a/src/components/markdown-renderer/markdown-extension/plantuml/plantuml-not-configured-alert.tsx b/src/extensions/extra-integrations/plantuml/plantuml-not-configured-alert.tsx similarity index 100% rename from src/components/markdown-renderer/markdown-extension/plantuml/plantuml-not-configured-alert.tsx rename to src/extensions/extra-integrations/plantuml/plantuml-not-configured-alert.tsx diff --git a/src/components/markdown-renderer/markdown-extension/plantuml/plantuml-not-configured-component-replacer.tsx b/src/extensions/extra-integrations/plantuml/plantuml-not-configured-component-replacer.tsx similarity index 70% rename from src/components/markdown-renderer/markdown-extension/plantuml/plantuml-not-configured-component-replacer.tsx rename to src/extensions/extra-integrations/plantuml/plantuml-not-configured-component-replacer.tsx index 5d243718b..b195169b7 100644 --- a/src/components/markdown-renderer/markdown-extension/plantuml/plantuml-not-configured-component-replacer.tsx +++ b/src/extensions/extra-integrations/plantuml/plantuml-not-configured-component-replacer.tsx @@ -4,10 +4,13 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import type { NodeReplacement } from '../../replace-components/component-replacer' -import { ComponentReplacer, DO_NOT_REPLACE } from '../../replace-components/component-replacer' import { PlantumlNotConfiguredAlert } from './plantuml-not-configured-alert' import type { Element } from 'domhandler' +import type { NodeReplacement } from '../../../components/markdown-renderer/replace-components/component-replacer' +import { + ComponentReplacer, + DO_NOT_REPLACE +} from '../../../components/markdown-renderer/replace-components/component-replacer' /** * Replaces every plantuml-not-configured tag with a {@link PlantumlNotConfiguredAlert}. diff --git a/src/extensions/extra-integrations/sequence-diagram/legacy-sequence-diagram-app-extension.ts b/src/extensions/extra-integrations/sequence-diagram/legacy-sequence-diagram-app-extension.ts new file mode 100644 index 000000000..c926a7b57 --- /dev/null +++ b/src/extensions/extra-integrations/sequence-diagram/legacy-sequence-diagram-app-extension.ts @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { AppExtension } from '../../base/app-extension' +import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension' +import { LegacySequenceDiagramMarkdownExtension } from './legacy-sequence-diagram-markdown-extension' +import type { Linter } from '../../../components/editor-page/editor-pane/linter/linter' +import { SingleLineRegexLinter } from '../../../components/editor-page/editor-pane/linter/single-line-regex-linter' +import { t } from 'i18next' + +/** + * Adds legacy support for sequence diagram syntax to the markdown renderer. + */ +export class LegacySequenceDiagramAppExtension extends AppExtension { + buildMarkdownRendererExtensions(): MarkdownRendererExtension[] { + return [new LegacySequenceDiagramMarkdownExtension()] + } + + buildCodeMirrorLinter(): Linter[] { + return [new SingleLineRegexLinter(/```sequence/, t('editor.linter.sequence'), () => '```mermaid\nsequenceDiagram')] + } +} diff --git a/src/extensions/extra-integrations/sequence-diagram/legacy-sequence-diagram-markdown-extension.ts b/src/extensions/extra-integrations/sequence-diagram/legacy-sequence-diagram-markdown-extension.ts new file mode 100644 index 000000000..26bae1fe6 --- /dev/null +++ b/src/extensions/extra-integrations/sequence-diagram/legacy-sequence-diagram-markdown-extension.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { SequenceDiagram } from './sequence-diagram' +import { CodeBlockMarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/code-block-markdown-extension/code-block-markdown-renderer-extension' +import { CodeBlockComponentReplacer } from '../../../components/markdown-renderer/replace-components/code-block-component-replacer' + +/** + * Adds legacy support for sequence diagram to the markdown rendering using code fences with "sequence" as language. + */ +export class LegacySequenceDiagramMarkdownExtension extends CodeBlockMarkdownRendererExtension { + public buildReplacers(): CodeBlockComponentReplacer[] { + return [new CodeBlockComponentReplacer(SequenceDiagram, 'sequence')] + } +} diff --git a/src/components/markdown-renderer/markdown-extension/sequence-diagram/sequence-diagram.tsx b/src/extensions/extra-integrations/sequence-diagram/sequence-diagram.tsx similarity index 78% rename from src/components/markdown-renderer/markdown-extension/sequence-diagram/sequence-diagram.tsx rename to src/extensions/extra-integrations/sequence-diagram/sequence-diagram.tsx index 08f1a9d64..a19837a0a 100644 --- a/src/components/markdown-renderer/markdown-extension/sequence-diagram/sequence-diagram.tsx +++ b/src/extensions/extra-integrations/sequence-diagram/sequence-diagram.tsx @@ -5,8 +5,8 @@ */ import React from 'react' -import type { CodeProps } from '../../replace-components/code-block-component-replacer' import { MermaidChart } from '../mermaid/mermaid-chart' +import type { CodeProps } from '../../../components/markdown-renderer/replace-components/code-block-component-replacer' /** * Renders a sequence diagram with a deprecation notice. diff --git a/src/extensions/extra-integrations/spoiler/spoiler-app-extension.ts b/src/extensions/extra-integrations/spoiler/spoiler-app-extension.ts new file mode 100644 index 000000000..d09f0dcd9 --- /dev/null +++ b/src/extensions/extra-integrations/spoiler/spoiler-app-extension.ts @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { AppExtension } from '../../base/app-extension' +import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension' +import { SpoilerMarkdownExtension } from './spoiler-markdown-extension' + +/** + * Adds support for html spoiler tags. + * + * @see https://www.w3schools.com/tags/tag_details.asp + */ +export class SpoilerAppExtension extends AppExtension { + buildMarkdownRendererExtensions(): MarkdownRendererExtension[] { + return [new SpoilerMarkdownExtension()] + } +} diff --git a/src/components/markdown-renderer/markdown-extension/spoiler-markdown-extension.ts b/src/extensions/extra-integrations/spoiler/spoiler-markdown-extension.ts similarity index 84% rename from src/components/markdown-renderer/markdown-extension/spoiler-markdown-extension.ts rename to src/extensions/extra-integrations/spoiler/spoiler-markdown-extension.ts index 5509a49e5..7b96a23e0 100644 --- a/src/components/markdown-renderer/markdown-extension/spoiler-markdown-extension.ts +++ b/src/extensions/extra-integrations/spoiler/spoiler-markdown-extension.ts @@ -4,18 +4,18 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { MarkdownExtension } from './markdown-extension' import type MarkdownIt from 'markdown-it' import markdownItContainer from 'markdown-it-container' import type Token from 'markdown-it/lib/token' import { escapeHtml } from 'markdown-it/lib/common/utils' +import { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension' /** * Adds support for html spoiler tags. * * @see https://www.w3schools.com/tags/tag_details.asp */ -export class SpoilerMarkdownExtension extends MarkdownExtension { +export class SpoilerMarkdownExtension extends MarkdownRendererExtension { private static readonly spoilerRegEx = /^spoiler\s+(.*)$/ private static renderSpoilerContainer(tokens: Token[], index: number): string { diff --git a/src/extensions/extra-integrations/task-list/event-emitting-task-list-checkbox.tsx b/src/extensions/extra-integrations/task-list/event-emitting-task-list-checkbox.tsx new file mode 100644 index 000000000..5356ac9cb --- /dev/null +++ b/src/extensions/extra-integrations/task-list/event-emitting-task-list-checkbox.tsx @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import React, { useCallback } from 'react' +import type { TaskCheckedChangeHandler, TaskListProps } from './task-list-checkbox' +import { TaskListCheckbox } from './task-list-checkbox' +import { useExtensionEventEmitter } from '../../../components/markdown-renderer/hooks/use-extension-event-emitter' +import { TaskListCheckboxAppExtension } from './task-list-checkbox-app-extension' + +type EventEmittingTaskListCheckboxProps = Omit + +export interface TaskCheckedEventPayload { + lineInMarkdown: number + checked: boolean +} + +/** + * Wraps a {@link TaskListCheckbox} but sends state changes to the current {@link EventEmitter2 event emitter}. + * + * @param props Props that will be forwarded to the checkbox. + */ +export const EventEmittingTaskListCheckbox: React.FC = (props) => { + const emitter = useExtensionEventEmitter() + const sendEvent: TaskCheckedChangeHandler = useCallback( + (lineInMarkdown: number, checked: boolean) => { + emitter?.emit(TaskListCheckboxAppExtension.EVENT_NAME, { lineInMarkdown, checked } as TaskCheckedEventPayload) + }, + [emitter] + ) + + return +} diff --git a/src/extensions/extra-integrations/task-list/set-checkbox-in-editor.tsx b/src/extensions/extra-integrations/task-list/set-checkbox-in-editor.tsx new file mode 100644 index 000000000..32de0eeee --- /dev/null +++ b/src/extensions/extra-integrations/task-list/set-checkbox-in-editor.tsx @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type React from 'react' +import { useSetCheckboxInEditor } from './use-set-checkbox-in-editor' +import { useExtensionEventEmitterHandler } from '../../../components/markdown-renderer/hooks/use-extension-event-emitter' +import { TaskListCheckboxAppExtension } from './task-list-checkbox-app-extension' + +/** + * Receives task-checkbox-change events and modify the current editor content. + */ +export const SetCheckboxInEditor: React.FC = () => { + const changeCallback = useSetCheckboxInEditor() + useExtensionEventEmitterHandler(TaskListCheckboxAppExtension.EVENT_NAME, changeCallback) + return null +} diff --git a/src/extensions/extra-integrations/task-list/task-list-checkbox-app-extension.ts b/src/extensions/extra-integrations/task-list/task-list-checkbox-app-extension.ts new file mode 100644 index 000000000..bbe003956 --- /dev/null +++ b/src/extensions/extra-integrations/task-list/task-list-checkbox-app-extension.ts @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type EventEmitter2 from 'eventemitter2' +import { AppExtension } from '../../base/app-extension' +import { TaskListMarkdownExtension } from './task-list-markdown-extension' +import type React from 'react' +import { SetCheckboxInEditor } from './set-checkbox-in-editor' + +/** + * Adds support for interactive checkbox lists to the markdown renderer. + */ +export class TaskListCheckboxAppExtension extends AppExtension { + public static readonly EVENT_NAME = 'TaskListCheckbox' + + buildMarkdownRendererExtensions(eventEmitter: EventEmitter2): TaskListMarkdownExtension[] { + return [new TaskListMarkdownExtension(eventEmitter)] + } + + buildEditorExtensionComponent(): React.FC { + return SetCheckboxInEditor + } +} diff --git a/src/components/markdown-renderer/markdown-extension/task-list/task-list-checkbox.tsx b/src/extensions/extra-integrations/task-list/task-list-checkbox.tsx similarity index 62% rename from src/components/markdown-renderer/markdown-extension/task-list/task-list-checkbox.tsx rename to src/extensions/extra-integrations/task-list/task-list-checkbox.tsx index 678dd67ed..e233fff72 100644 --- a/src/components/markdown-renderer/markdown-extension/task-list/task-list-checkbox.tsx +++ b/src/extensions/extra-integrations/task-list/task-list-checkbox.tsx @@ -1,15 +1,18 @@ /* - * 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 React, { useCallback } from 'react' +export type TaskCheckedChangeHandler = (lineInMarkdown: number, checked: boolean) => void + export interface TaskListProps { - onTaskCheckedChange?: (lineInMarkdown: number, checked: boolean) => void + onTaskCheckedChange?: TaskCheckedChangeHandler checked: boolean lineInMarkdown?: number + disabled?: boolean } /** @@ -19,19 +22,24 @@ export interface TaskListProps { * @param checked Determines if the checkbox should be rendered as checked * @param lineInMarkdown Defines the line in the markdown code this checkbox is mapped to. The information is send with the onTaskCheckedChange callback. */ -export const TaskListCheckbox: React.FC = ({ onTaskCheckedChange, checked, lineInMarkdown }) => { +export const TaskListCheckbox: React.FC = ({ + onTaskCheckedChange, + checked, + lineInMarkdown, + disabled +}) => { const onChange = useCallback( (event: React.ChangeEvent): void => { - if (onTaskCheckedChange && lineInMarkdown !== undefined) { + if (onTaskCheckedChange && disabled !== true && lineInMarkdown !== undefined) { onTaskCheckedChange(lineInMarkdown, event.currentTarget.checked) } }, - [lineInMarkdown, onTaskCheckedChange] + [disabled, lineInMarkdown, onTaskCheckedChange] ) return ( + ) + } +} diff --git a/src/components/editor-page/editor-document-renderer/hooks/use-set-checkbox-in-editor.tsx b/src/extensions/extra-integrations/task-list/use-set-checkbox-in-editor.tsx similarity index 76% rename from src/components/editor-page/editor-document-renderer/hooks/use-set-checkbox-in-editor.tsx rename to src/extensions/extra-integrations/task-list/use-set-checkbox-in-editor.tsx index cab977341..4a4582dcb 100644 --- a/src/components/editor-page/editor-document-renderer/hooks/use-set-checkbox-in-editor.tsx +++ b/src/extensions/extra-integrations/task-list/use-set-checkbox-in-editor.tsx @@ -4,10 +4,12 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { useChangeEditorContentCallback } from '../../change-content-context/use-change-editor-content-callback' import { useCallback } from 'react' -import type { ContentEdits } from '../../editor-pane/tool-bar/formatters/types/changes' import { Optional } from '@mrdrogdrog/optional' +import { useChangeEditorContentCallback } from '../../../components/editor-page/change-content-context/use-change-editor-content-callback' +import type { ContentEdits } from '../../../components/editor-page/editor-pane/tool-bar/formatters/types/changes' +import type { TaskCheckedEventPayload } from './event-emitting-task-list-checkbox' +import { store } from '../../../redux' const TASK_REGEX = /(\s*(?:[-*+]|\d+[.)]) )(\[[ xX]?])/ @@ -18,14 +20,15 @@ export const useSetCheckboxInEditor = () => { const changeEditorContent = useChangeEditorContentCallback() return useCallback( - (changedLineIndex: number, checkboxChecked: boolean): void => { + ({ lineInMarkdown, checked }: TaskCheckedEventPayload): void => { changeEditorContent?.(({ markdownContent }) => { const lines = markdownContent.split('\n') - const lineStartIndex = findStartIndexOfLine(lines, changedLineIndex) - const edits = Optional.ofNullable(TASK_REGEX.exec(lines[changedLineIndex])) + const correctedLineIndex = lineInMarkdown + store.getState().noteDetails.frontmatterRendererInfo.lineOffset + const lineStartIndex = findStartIndexOfLine(lines, correctedLineIndex) + const edits = Optional.ofNullable(TASK_REGEX.exec(lines[correctedLineIndex])) .map(([, beforeCheckbox, oldCheckbox]) => { const checkboxStartIndex = lineStartIndex + beforeCheckbox.length - return createCheckboxContentEdit(checkboxStartIndex, oldCheckbox, checkboxChecked) + return createCheckboxContentEdit(checkboxStartIndex, oldCheckbox, checked) }) .orElse([]) return [edits, undefined] diff --git a/src/components/markdown-renderer/markdown-extension/vega-lite/__snapshots__/vega-lite-markdown-extension.test.tsx.snap b/src/extensions/extra-integrations/vega-lite/__snapshots__/vega-lite-markdown-extension.test.tsx.snap similarity index 100% rename from src/components/markdown-renderer/markdown-extension/vega-lite/__snapshots__/vega-lite-markdown-extension.test.tsx.snap rename to src/extensions/extra-integrations/vega-lite/__snapshots__/vega-lite-markdown-extension.test.tsx.snap diff --git a/src/extensions/extra-integrations/vega-lite/vega-lite-app-extension.ts b/src/extensions/extra-integrations/vega-lite/vega-lite-app-extension.ts new file mode 100644 index 000000000..9a8b7748d --- /dev/null +++ b/src/extensions/extra-integrations/vega-lite/vega-lite-app-extension.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { AppExtension } from '../../base/app-extension' +import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension' +import { VegaLiteMarkdownExtension } from './vega-lite-markdown-extension' + +/** + * Adds support for chart rendering using vega lite to the markdown renderer. + */ +export class VegaLiteAppExtension extends AppExtension { + buildMarkdownRendererExtensions(): MarkdownRendererExtension[] { + return [new VegaLiteMarkdownExtension()] + } +} diff --git a/src/components/markdown-renderer/markdown-extension/vega-lite/vega-lite-chart.tsx b/src/extensions/extra-integrations/vega-lite/vega-lite-chart.tsx similarity index 85% rename from src/components/markdown-renderer/markdown-extension/vega-lite/vega-lite-chart.tsx rename to src/extensions/extra-integrations/vega-lite/vega-lite-chart.tsx index 77bf02e72..5823b0930 100644 --- a/src/components/markdown-renderer/markdown-extension/vega-lite/vega-lite-chart.tsx +++ b/src/extensions/extra-integrations/vega-lite/vega-lite-chart.tsx @@ -8,11 +8,11 @@ import React, { useEffect, useRef } from 'react' import { Alert } from 'react-bootstrap' import { Trans, useTranslation } from 'react-i18next' import type { VisualizationSpec } from 'vega-embed' -import { ShowIf } from '../../../common/show-if/show-if' -import { Logger } from '../../../../utils/logger' -import type { CodeProps } from '../../replace-components/code-block-component-replacer' import { useAsync } from 'react-use' -import { AsyncLoadingBoundary } from '../../../common/async-loading-boundary' +import type { CodeProps } from '../../../components/markdown-renderer/replace-components/code-block-component-replacer' +import { AsyncLoadingBoundary } from '../../../components/common/async-loading-boundary' +import { ShowIf } from '../../../components/common/show-if/show-if' +import { Logger } from '../../../utils/logger' const log = new Logger('VegaChart') diff --git a/src/components/markdown-renderer/markdown-extension/vega-lite/vega-lite-markdown-extension.test.tsx b/src/extensions/extra-integrations/vega-lite/vega-lite-markdown-extension.test.tsx similarity index 78% rename from src/components/markdown-renderer/markdown-extension/vega-lite/vega-lite-markdown-extension.test.tsx rename to src/extensions/extra-integrations/vega-lite/vega-lite-markdown-extension.test.tsx index 5bb54a9b5..2ca055a4f 100644 --- a/src/components/markdown-renderer/markdown-extension/vega-lite/vega-lite-markdown-extension.test.tsx +++ b/src/extensions/extra-integrations/vega-lite/vega-lite-markdown-extension.test.tsx @@ -5,12 +5,12 @@ */ import { render } from '@testing-library/react' -import { TestMarkdownRenderer } from '../../test-utils/test-markdown-renderer' import React from 'react' -import { mockI18n } from '../../test-utils/mock-i18n' import { VegaLiteMarkdownExtension } from './vega-lite-markdown-extension' import * as VegaLiteChartModule from '../vega-lite/vega-lite-chart' -import type { CodeProps } from '../../replace-components/code-block-component-replacer' +import { mockI18n } from '../../../components/markdown-renderer/test-utils/mock-i18n' +import type { CodeProps } from '../../../components/markdown-renderer/replace-components/code-block-component-replacer' +import { TestMarkdownRenderer } from '../../../components/markdown-renderer/test-utils/test-markdown-renderer' jest.mock('../vega-lite/vega-lite-chart') diff --git a/src/extensions/extra-integrations/vega-lite/vega-lite-markdown-extension.ts b/src/extensions/extra-integrations/vega-lite/vega-lite-markdown-extension.ts new file mode 100644 index 000000000..1ed060e42 --- /dev/null +++ b/src/extensions/extra-integrations/vega-lite/vega-lite-markdown-extension.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { VegaLiteChart } from './vega-lite-chart' +import { CodeBlockMarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/code-block-markdown-extension/code-block-markdown-renderer-extension' +import { CodeBlockComponentReplacer } from '../../../components/markdown-renderer/replace-components/code-block-component-replacer' + +/** + * Adds support for chart rendering using vega lite to the markdown rendering using code fences with "vega-lite" as language. + */ +export class VegaLiteMarkdownExtension extends CodeBlockMarkdownRendererExtension { + public buildReplacers(): CodeBlockComponentReplacer[] { + return [new CodeBlockComponentReplacer(VegaLiteChart, 'vega-lite')] + } +} diff --git a/src/components/markdown-renderer/markdown-extension/vimeo/__snapshots__/vimeo-frame.test.tsx.snap b/src/extensions/extra-integrations/vimeo/__snapshots__/vimeo-frame.test.tsx.snap similarity index 100% rename from src/components/markdown-renderer/markdown-extension/vimeo/__snapshots__/vimeo-frame.test.tsx.snap rename to src/extensions/extra-integrations/vimeo/__snapshots__/vimeo-frame.test.tsx.snap diff --git a/src/components/markdown-renderer/markdown-extension/vimeo/replace-legacy-vimeo-short-code.test.ts b/src/extensions/extra-integrations/vimeo/replace-legacy-vimeo-short-code.test.ts similarity index 100% rename from src/components/markdown-renderer/markdown-extension/vimeo/replace-legacy-vimeo-short-code.test.ts rename to src/extensions/extra-integrations/vimeo/replace-legacy-vimeo-short-code.test.ts diff --git a/src/components/markdown-renderer/markdown-extension/vimeo/replace-legacy-vimeo-short-code.ts b/src/extensions/extra-integrations/vimeo/replace-legacy-vimeo-short-code.ts similarity index 92% rename from src/components/markdown-renderer/markdown-extension/vimeo/replace-legacy-vimeo-short-code.ts rename to src/extensions/extra-integrations/vimeo/replace-legacy-vimeo-short-code.ts index 5ce9a4c2c..4b3511cf7 100644 --- a/src/components/markdown-renderer/markdown-extension/vimeo/replace-legacy-vimeo-short-code.ts +++ b/src/extensions/extra-integrations/vimeo/replace-legacy-vimeo-short-code.ts @@ -4,10 +4,10 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import type { RegexOptions } from '../../../../external-types/markdown-it-regex/interface' import { VimeoMarkdownExtension } from './vimeo-markdown-extension' import type MarkdownIt from 'markdown-it' import markdownItRegex from 'markdown-it-regex' +import type { RegexOptions } from '../../../external-types/markdown-it-regex/interface' export const legacyVimeoRegex = /^{%vimeo\s+(\d{6,11})\s*%}$/ diff --git a/src/components/markdown-renderer/markdown-extension/vimeo/replace-vimeo-link.test.ts b/src/extensions/extra-integrations/vimeo/replace-vimeo-link.test.ts similarity index 100% rename from src/components/markdown-renderer/markdown-extension/vimeo/replace-vimeo-link.test.ts rename to src/extensions/extra-integrations/vimeo/replace-vimeo-link.test.ts diff --git a/src/components/markdown-renderer/markdown-extension/vimeo/replace-vimeo-link.ts b/src/extensions/extra-integrations/vimeo/replace-vimeo-link.ts similarity index 93% rename from src/components/markdown-renderer/markdown-extension/vimeo/replace-vimeo-link.ts rename to src/extensions/extra-integrations/vimeo/replace-vimeo-link.ts index d4245dae3..98c7fc22e 100644 --- a/src/components/markdown-renderer/markdown-extension/vimeo/replace-vimeo-link.ts +++ b/src/extensions/extra-integrations/vimeo/replace-vimeo-link.ts @@ -4,10 +4,10 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import type { RegexOptions } from '../../../../external-types/markdown-it-regex/interface' import { VimeoMarkdownExtension } from './vimeo-markdown-extension' import type MarkdownIt from 'markdown-it' import markdownItRegex from 'markdown-it-regex' +import type { RegexOptions } from '../../../external-types/markdown-it-regex/interface' const protocolRegex = /(?:http(?:s)?:\/\/)?/ const domainRegex = /(?:player\.)?(?:vimeo\.com\/)(?:(?:channels|album|ondemand|groups)\/\w+\/)?(?:video\/)?/ diff --git a/src/extensions/extra-integrations/vimeo/vimeo-app-extension.ts b/src/extensions/extra-integrations/vimeo/vimeo-app-extension.ts new file mode 100644 index 000000000..d7ad11d72 --- /dev/null +++ b/src/extensions/extra-integrations/vimeo/vimeo-app-extension.ts @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { AppExtension } from '../../base/app-extension' +import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension' +import { VimeoMarkdownExtension } from './vimeo-markdown-extension' +import type { Linter } from '../../../components/editor-page/editor-pane/linter/linter' +import { SingleLineRegexLinter } from '../../../components/editor-page/editor-pane/linter/single-line-regex-linter' +import { legacyVimeoRegex } from './replace-legacy-vimeo-short-code' +import { t } from 'i18next' + +/** + * Adds vimeo video embeddings to the markdown renderer. + */ +export class VimeoAppExtension extends AppExtension { + buildMarkdownRendererExtensions(): MarkdownRendererExtension[] { + return [new VimeoMarkdownExtension()] + } + + buildCodeMirrorLinter(): Linter[] { + return [ + new SingleLineRegexLinter( + legacyVimeoRegex, + t('editor.linter.shortcode', { shortcode: 'Vimeo' }), + (match: string) => `https://player.vimeo.com/video/${match}` + ) + ] + } +} diff --git a/src/components/markdown-renderer/markdown-extension/vimeo/vimeo-frame.test.tsx b/src/extensions/extra-integrations/vimeo/vimeo-frame.test.tsx similarity index 65% rename from src/components/markdown-renderer/markdown-extension/vimeo/vimeo-frame.test.tsx rename to src/extensions/extra-integrations/vimeo/vimeo-frame.test.tsx index 1a12c5059..f9d642afb 100644 --- a/src/components/markdown-renderer/markdown-extension/vimeo/vimeo-frame.test.tsx +++ b/src/extensions/extra-integrations/vimeo/vimeo-frame.test.tsx @@ -6,10 +6,10 @@ import { render } from '@testing-library/react' import { VimeoFrame } from './vimeo-frame' import React from 'react' -import type { ClickShieldProps } from '../../replace-components/click-shield/click-shield' -import * as ClickShieldModule from '../../replace-components/click-shield/click-shield' +import * as ClickShieldModule from '../../../components/markdown-renderer/replace-components/click-shield/click-shield' +import type { ClickShieldProps } from '../../../components/markdown-renderer/replace-components/click-shield/click-shield' -jest.mock('../../replace-components/click-shield/click-shield') +jest.mock('../../../components/markdown-renderer/replace-components/click-shield/click-shield') describe('VimeoFrame', () => { beforeEach(() => { diff --git a/src/components/markdown-renderer/markdown-extension/vimeo/vimeo-frame.tsx b/src/extensions/extra-integrations/vimeo/vimeo-frame.tsx similarity index 84% rename from src/components/markdown-renderer/markdown-extension/vimeo/vimeo-frame.tsx rename to src/extensions/extra-integrations/vimeo/vimeo-frame.tsx index 095cb97f8..f839c19a8 100644 --- a/src/components/markdown-renderer/markdown-extension/vimeo/vimeo-frame.tsx +++ b/src/extensions/extra-integrations/vimeo/vimeo-frame.tsx @@ -1,12 +1,12 @@ /* - * 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 React, { useCallback } from 'react' -import type { IdProps } from '../../replace-components/custom-tag-with-id-component-replacer' -import { ClickShield } from '../../replace-components/click-shield/click-shield' +import { ClickShield } from '../../../components/markdown-renderer/replace-components/click-shield/click-shield' +import type { IdProps } from '../../../components/markdown-renderer/replace-components/custom-tag-with-id-component-replacer' interface VimeoApiResponse { // Vimeo uses strange names for their fields. ESLint doesn't like that. diff --git a/src/extensions/extra-integrations/vimeo/vimeo-markdown-extension.ts b/src/extensions/extra-integrations/vimeo/vimeo-markdown-extension.ts new file mode 100644 index 000000000..f8466dbee --- /dev/null +++ b/src/extensions/extra-integrations/vimeo/vimeo-markdown-extension.ts @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type MarkdownIt from 'markdown-it' +import { replaceVimeoLinkMarkdownItPlugin } from './replace-vimeo-link' +import { VimeoFrame } from './vimeo-frame' +import { replaceLegacyVimeoShortCodeMarkdownItPlugin } from './replace-legacy-vimeo-short-code' +import type { ComponentReplacer } from '../../../components/markdown-renderer/replace-components/component-replacer' +import { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension' +import { CustomTagWithIdComponentReplacer } from '../../../components/markdown-renderer/replace-components/custom-tag-with-id-component-replacer' + +/** + * Adds vimeo video embeddings using link detection and the legacy vimeo short code syntax. + */ +export class VimeoMarkdownExtension extends MarkdownRendererExtension { + public static readonly tagName = 'app-vimeo' + + public configureMarkdownIt(markdownIt: MarkdownIt): void { + replaceLegacyVimeoShortCodeMarkdownItPlugin(markdownIt) + replaceVimeoLinkMarkdownItPlugin(markdownIt) + } + + public buildReplacers(): ComponentReplacer[] { + return [new CustomTagWithIdComponentReplacer(VimeoFrame, VimeoMarkdownExtension.tagName)] + } + + public buildTagNameAllowList(): string[] { + return [VimeoMarkdownExtension.tagName] + } +} diff --git a/src/components/markdown-renderer/markdown-extension/youtube/__snapshots__/youtube-frame.test.tsx.snap b/src/extensions/extra-integrations/youtube/__snapshots__/youtube-frame.test.tsx.snap similarity index 100% rename from src/components/markdown-renderer/markdown-extension/youtube/__snapshots__/youtube-frame.test.tsx.snap rename to src/extensions/extra-integrations/youtube/__snapshots__/youtube-frame.test.tsx.snap diff --git a/src/components/markdown-renderer/markdown-extension/youtube/__snapshots__/youtube-markdown-extension.test.tsx.snap b/src/extensions/extra-integrations/youtube/__snapshots__/youtube-markdown-extension.test.tsx.snap similarity index 100% rename from src/components/markdown-renderer/markdown-extension/youtube/__snapshots__/youtube-markdown-extension.test.tsx.snap rename to src/extensions/extra-integrations/youtube/__snapshots__/youtube-markdown-extension.test.tsx.snap diff --git a/src/components/markdown-renderer/markdown-extension/youtube/replace-legacy-youtube-short-code.test.ts b/src/extensions/extra-integrations/youtube/replace-legacy-youtube-short-code.test.ts similarity index 100% rename from src/components/markdown-renderer/markdown-extension/youtube/replace-legacy-youtube-short-code.test.ts rename to src/extensions/extra-integrations/youtube/replace-legacy-youtube-short-code.test.ts diff --git a/src/components/markdown-renderer/markdown-extension/youtube/replace-legacy-youtube-short-code.ts b/src/extensions/extra-integrations/youtube/replace-legacy-youtube-short-code.ts similarity index 91% rename from src/components/markdown-renderer/markdown-extension/youtube/replace-legacy-youtube-short-code.ts rename to src/extensions/extra-integrations/youtube/replace-legacy-youtube-short-code.ts index 574b75f93..e73b3e7e8 100644 --- a/src/components/markdown-renderer/markdown-extension/youtube/replace-legacy-youtube-short-code.ts +++ b/src/extensions/extra-integrations/youtube/replace-legacy-youtube-short-code.ts @@ -4,10 +4,10 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import type { RegexOptions } from '../../../../external-types/markdown-it-regex/interface' import { YoutubeMarkdownExtension } from './youtube-markdown-extension' import markdownItRegex from 'markdown-it-regex' import type MarkdownIt from 'markdown-it' +import type { RegexOptions } from '../../../external-types/markdown-it-regex/interface' export const legacyYouTubeRegex = /^{%youtube\s+([\w-]{11})\s*%}$/ diff --git a/src/components/markdown-renderer/markdown-extension/youtube/replace-youtube-link.test.ts b/src/extensions/extra-integrations/youtube/replace-youtube-link.test.ts similarity index 100% rename from src/components/markdown-renderer/markdown-extension/youtube/replace-youtube-link.test.ts rename to src/extensions/extra-integrations/youtube/replace-youtube-link.test.ts diff --git a/src/components/markdown-renderer/markdown-extension/youtube/replace-youtube-link.ts b/src/extensions/extra-integrations/youtube/replace-youtube-link.ts similarity index 93% rename from src/components/markdown-renderer/markdown-extension/youtube/replace-youtube-link.ts rename to src/extensions/extra-integrations/youtube/replace-youtube-link.ts index ffd92ecd8..0899e0e41 100644 --- a/src/components/markdown-renderer/markdown-extension/youtube/replace-youtube-link.ts +++ b/src/extensions/extra-integrations/youtube/replace-youtube-link.ts @@ -4,10 +4,10 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import type { RegexOptions } from '../../../../external-types/markdown-it-regex/interface' import { YoutubeMarkdownExtension } from './youtube-markdown-extension' import markdownItRegex from 'markdown-it-regex' import type MarkdownIt from 'markdown-it' +import type { RegexOptions } from '../../../external-types/markdown-it-regex/interface' const protocolRegex = /(?:http(?:s)?:\/\/)?/ const subdomainRegex = /(?:www.)?/ diff --git a/src/extensions/extra-integrations/youtube/youtube-app-extension.ts b/src/extensions/extra-integrations/youtube/youtube-app-extension.ts new file mode 100644 index 000000000..050b8dac5 --- /dev/null +++ b/src/extensions/extra-integrations/youtube/youtube-app-extension.ts @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { AppExtension } from '../../base/app-extension' +import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension' +import { YoutubeMarkdownExtension } from './youtube-markdown-extension' +import type { Linter } from '../../../components/editor-page/editor-pane/linter/linter' +import { SingleLineRegexLinter } from '../../../components/editor-page/editor-pane/linter/single-line-regex-linter' +import { legacyYouTubeRegex } from './replace-legacy-youtube-short-code' +import { t } from 'i18next' + +/** + * Adds YouTube video embeddings to the markdown renderer. + */ +export class YoutubeAppExtension extends AppExtension { + buildMarkdownRendererExtensions(): MarkdownRendererExtension[] { + return [new YoutubeMarkdownExtension()] + } + + public buildCodeMirrorLinter(): Linter[] { + return [ + new SingleLineRegexLinter( + legacyYouTubeRegex, + t('editor.linter.shortcode', { shortcode: 'YouTube' }), + (match: string) => `https://www.youtube.com/watch?v=${match}` + ) + ] + } +} diff --git a/src/components/markdown-renderer/markdown-extension/youtube/youtube-frame.test.tsx b/src/extensions/extra-integrations/youtube/youtube-frame.test.tsx similarity index 66% rename from src/components/markdown-renderer/markdown-extension/youtube/youtube-frame.test.tsx rename to src/extensions/extra-integrations/youtube/youtube-frame.test.tsx index 992e338b0..3eedbcd42 100644 --- a/src/components/markdown-renderer/markdown-extension/youtube/youtube-frame.test.tsx +++ b/src/extensions/extra-integrations/youtube/youtube-frame.test.tsx @@ -6,10 +6,10 @@ import { render } from '@testing-library/react' import { YouTubeFrame } from './youtube-frame' import React from 'react' -import type { ClickShieldProps } from '../../replace-components/click-shield/click-shield' -import * as ClickShieldModule from '../../replace-components/click-shield/click-shield' +import * as ClickShieldModule from '../../../components/markdown-renderer/replace-components/click-shield/click-shield' +import type { ClickShieldProps } from '../../../components/markdown-renderer/replace-components/click-shield/click-shield' -jest.mock('../../replace-components/click-shield/click-shield') +jest.mock('../../../components/markdown-renderer/replace-components/click-shield/click-shield') describe('YoutubeFrame', () => { beforeEach(() => { diff --git a/src/components/markdown-renderer/markdown-extension/youtube/youtube-frame.tsx b/src/extensions/extra-integrations/youtube/youtube-frame.tsx similarity index 75% rename from src/components/markdown-renderer/markdown-extension/youtube/youtube-frame.tsx rename to src/extensions/extra-integrations/youtube/youtube-frame.tsx index c80f710b8..0d36cddc4 100644 --- a/src/components/markdown-renderer/markdown-extension/youtube/youtube-frame.tsx +++ b/src/extensions/extra-integrations/youtube/youtube-frame.tsx @@ -1,12 +1,12 @@ /* - * 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 React from 'react' -import type { IdProps } from '../../replace-components/custom-tag-with-id-component-replacer' -import { ClickShield } from '../../replace-components/click-shield/click-shield' +import { ClickShield } from '../../../components/markdown-renderer/replace-components/click-shield/click-shield' +import type { IdProps } from '../../../components/markdown-renderer/replace-components/custom-tag-with-id-component-replacer' /** * Renders a video player embedding for https://youtube.com diff --git a/src/components/markdown-renderer/markdown-extension/youtube/youtube-markdown-extension.test.tsx b/src/extensions/extra-integrations/youtube/youtube-markdown-extension.test.tsx similarity index 82% rename from src/components/markdown-renderer/markdown-extension/youtube/youtube-markdown-extension.test.tsx rename to src/extensions/extra-integrations/youtube/youtube-markdown-extension.test.tsx index fdca4b9e9..f64bf277c 100644 --- a/src/components/markdown-renderer/markdown-extension/youtube/youtube-markdown-extension.test.tsx +++ b/src/extensions/extra-integrations/youtube/youtube-markdown-extension.test.tsx @@ -6,11 +6,11 @@ import { YoutubeMarkdownExtension } from './youtube-markdown-extension' import { render } from '@testing-library/react' -import { TestMarkdownRenderer } from '../../test-utils/test-markdown-renderer' import * as YouTubeFrameModule from './youtube-frame' import React from 'react' -import type { IdProps } from '../../replace-components/custom-tag-with-id-component-replacer' -import { mockI18n } from '../../test-utils/mock-i18n' +import { mockI18n } from '../../../components/markdown-renderer/test-utils/mock-i18n' +import { TestMarkdownRenderer } from '../../../components/markdown-renderer/test-utils/test-markdown-renderer' +import type { IdProps } from '../../../components/markdown-renderer/replace-components/custom-tag-with-id-component-replacer' jest.mock('./youtube-frame') diff --git a/src/extensions/extra-integrations/youtube/youtube-markdown-extension.ts b/src/extensions/extra-integrations/youtube/youtube-markdown-extension.ts new file mode 100644 index 000000000..1013e18ac --- /dev/null +++ b/src/extensions/extra-integrations/youtube/youtube-markdown-extension.ts @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { replaceYouTubeLinkMarkdownItPlugin } from './replace-youtube-link' +import { replaceLegacyYoutubeShortCodeMarkdownItPlugin } from './replace-legacy-youtube-short-code' +import type MarkdownIt from 'markdown-it' +import { YouTubeFrame } from './youtube-frame' +import { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension' +import { CustomTagWithIdComponentReplacer } from '../../../components/markdown-renderer/replace-components/custom-tag-with-id-component-replacer' +import type { ComponentReplacer } from '../../../components/markdown-renderer/replace-components/component-replacer' + +/** + * Adds YouTube video embeddings using link detection and the legacy YouTube short code syntax. + */ +export class YoutubeMarkdownExtension extends MarkdownRendererExtension { + public static readonly tagName = 'app-youtube' + + public configureMarkdownIt(markdownIt: MarkdownIt): void { + replaceYouTubeLinkMarkdownItPlugin(markdownIt) + replaceLegacyYoutubeShortCodeMarkdownItPlugin(markdownIt) + } + + public buildReplacers(): ComponentReplacer[] { + return [new CustomTagWithIdComponentReplacer(YouTubeFrame, YoutubeMarkdownExtension.tagName)] + } + + public buildTagNameAllowList(): string[] { + return [YoutubeMarkdownExtension.tagName] + } +}