Introduce Markdown extensions (#1614)

* Introduce markdown extensions

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2021-11-15 17:04:49 +01:00 committed by GitHub
parent e9defd60dc
commit 8a8bacc0aa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
148 changed files with 1878 additions and 1128 deletions

View file

@ -0,0 +1,44 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { TravelerNodeProcessor } from '../../node-preprocessors/traveler-node-processor'
import type { Node } from 'domhandler'
import { isTag } from 'domhandler'
export class AnchorNodePreprocessor extends TravelerNodeProcessor {
constructor(private baseUrl: string) {
super()
}
protected processNode(node: Node): void {
if (!isTag(node) || node.name !== 'a' || !node.attribs || !node.attribs.href) {
return
}
const url = node.attribs.href.trim()
// eslint-disable-next-line no-script-url
if (url.startsWith('data:') || url.startsWith('javascript:') || url.startsWith('vbscript:')) {
delete node.attribs.href
return
}
const isJumpMark = url.substr(0, 1) === '#'
if (isJumpMark) {
node.attribs['data-jump-target-id'] = url.substr(1)
} else {
node.attribs.rel = 'noreferer noopener'
node.attribs.target = '_blank'
}
try {
node.attribs.href = new URL(url, this.baseUrl).toString()
} catch (e) {
node.attribs.href = url
}
}
}

View file

@ -0,0 +1,31 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { Element } from 'domhandler'
import type { AllHTMLAttributes } from 'react'
import React from 'react'
import type { NativeRenderer, NodeReplacement, SubNodeTransform } from '../../replace-components/component-replacer'
import { ComponentReplacer, DO_NOT_REPLACE } from '../../replace-components/component-replacer'
import { JumpAnchor } from './jump-anchor'
/**
* Detects anchors that should jump to scroll to another element.
*/
export class JumpAnchorReplacer extends ComponentReplacer {
public replace(node: Element, subNodeTransform: SubNodeTransform, nativeRenderer: NativeRenderer): NodeReplacement {
if (node.name !== 'a' || !node.attribs || !node.attribs['data-jump-target-id']) {
return DO_NOT_REPLACE
}
const jumpId = node.attribs['data-jump-target-id']
delete node.attribs['data-jump-target-id']
const replacement = nativeRenderer()
if (replacement === null || typeof replacement === 'string') {
return replacement
} else {
return <JumpAnchor {...(replacement.props as AllHTMLAttributes<HTMLAnchorElement>)} jumpTargetId={jumpId} />
}
}
}

View file

@ -0,0 +1,28 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { AllHTMLAttributes } from 'react'
import React, { useCallback } from 'react'
export interface JumpAnchorProps extends AllHTMLAttributes<HTMLAnchorElement> {
jumpTargetId: string
}
export const JumpAnchor: React.FC<JumpAnchorProps> = ({ jumpTargetId, children, ...props }) => {
const jumpToTargetId = useCallback(
(event: React.MouseEvent<HTMLElement, MouseEvent>): void => {
document.getElementById(jumpTargetId)?.scrollIntoView({ behavior: 'smooth' })
event.preventDefault()
},
[jumpTargetId]
)
return (
<a {...props} onClick={jumpToTargetId}>
{children}
</a>
)
}

View file

@ -0,0 +1,28 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { MarkdownExtension } from '../markdown-extension'
import { JumpAnchorReplacer } from './jump-anchor-replacer'
import type { ComponentReplacer } from '../../replace-components/component-replacer'
import type { NodeProcessor } from '../../node-preprocessors/node-processor'
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 {
constructor(private baseUrl: string) {
super()
}
public buildNodeProcessors(): NodeProcessor[] {
return [new AnchorNodePreprocessor(this.baseUrl)]
}
public buildReplacers(): ComponentReplacer[] {
return [new JumpAnchorReplacer()]
}
}