mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-13 22:54:42 -04:00
Move frontmatter extraction from renderer to redux (#1413)
This commit is contained in:
parent
7fb7c55877
commit
04e16d8880
34 changed files with 680 additions and 589 deletions
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React, { Ref, useCallback, useMemo, useRef, useState } from 'react'
|
||||
import React, { Ref, useCallback, useMemo, useRef } from 'react'
|
||||
import { DocumentLengthLimitReachedAlert } from './document-length-limit-reached-alert'
|
||||
import { useConvertMarkdownToReactDom } from './hooks/use-convert-markdown-to-react-dom'
|
||||
import './markdown-renderer.scss'
|
||||
|
@ -12,7 +12,6 @@ import { ComponentReplacer } from './replace-components/ComponentReplacer'
|
|||
import { AdditionalMarkdownRendererProps, LineMarkerPosition } from './types'
|
||||
import { useComponentReplacers } from './hooks/use-component-replacers'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { NoteFrontmatter, RawNoteFrontmatter } from '../editor-page/note-frontmatter/note-frontmatter'
|
||||
import { LineMarkers } from './replace-components/linemarker/line-number-marker'
|
||||
import { useCalculateLineMarkerPosition } from './utils/calculate-line-marker-positions'
|
||||
import { useExtractFirstHeadline } from './hooks/use-extract-first-headline'
|
||||
|
@ -20,7 +19,6 @@ import { TocAst } from 'markdown-it-toc-done-right'
|
|||
import { useOnRefChange } from './hooks/use-on-ref-change'
|
||||
import { BasicMarkdownItConfigurator } from './markdown-it-configurator/BasicMarkdownItConfigurator'
|
||||
import { ImageClickHandler } from './replace-components/image/image-replacer'
|
||||
import { InvalidYamlAlert } from './invalid-yaml-alert'
|
||||
import { useTrimmedContent } from './hooks/use-trimmed-content'
|
||||
|
||||
export interface BasicMarkdownRendererProps {
|
||||
|
@ -29,79 +27,57 @@ export interface BasicMarkdownRendererProps {
|
|||
onAfterRendering?: () => void
|
||||
onFirstHeadingChange?: (firstHeading: string | undefined) => void
|
||||
onLineMarkerPositionChanged?: (lineMarkerPosition: LineMarkerPosition[]) => void
|
||||
onFrontmatterChange?: (frontmatter: NoteFrontmatter | undefined) => void
|
||||
onTaskCheckedChange?: (lineInMarkdown: number, checked: boolean) => void
|
||||
onTocChange?: (ast?: TocAst) => void
|
||||
baseUrl?: string
|
||||
onImageClick?: ImageClickHandler
|
||||
outerContainerRef?: Ref<HTMLDivElement>
|
||||
useAlternativeBreaks?: boolean
|
||||
frontmatterLineOffset?: number
|
||||
}
|
||||
|
||||
export const BasicMarkdownRenderer: React.FC<BasicMarkdownRendererProps & AdditionalMarkdownRendererProps> = ({
|
||||
className,
|
||||
content,
|
||||
additionalReplacers,
|
||||
onBeforeRendering,
|
||||
onAfterRendering,
|
||||
onFirstHeadingChange,
|
||||
onLineMarkerPositionChanged,
|
||||
onFrontmatterChange,
|
||||
onTaskCheckedChange,
|
||||
onTocChange,
|
||||
baseUrl,
|
||||
onImageClick,
|
||||
outerContainerRef,
|
||||
useAlternativeBreaks
|
||||
useAlternativeBreaks,
|
||||
frontmatterLineOffset
|
||||
}) => {
|
||||
const rawMetaRef = useRef<RawNoteFrontmatter>()
|
||||
const markdownBodyRef = useRef<HTMLDivElement>(null)
|
||||
const currentLineMarkers = useRef<LineMarkers[]>()
|
||||
const hasNewYamlError = useRef(false)
|
||||
const tocAst = useRef<TocAst>()
|
||||
const [showYamlError, setShowYamlError] = useState(false)
|
||||
const [trimmedContent, contentExceedsLimit] = useTrimmedContent(content)
|
||||
|
||||
const markdownIt = useMemo(
|
||||
() =>
|
||||
new BasicMarkdownItConfigurator({
|
||||
useFrontmatter: !!onFrontmatterChange,
|
||||
onParseError: (errorState) => (hasNewYamlError.current = errorState),
|
||||
onRawMetaChange: (rawMeta) => (rawMetaRef.current = rawMeta),
|
||||
onToc: (toc) => (tocAst.current = toc),
|
||||
onLineMarkers:
|
||||
onLineMarkerPositionChanged === undefined
|
||||
? undefined
|
||||
: (lineMarkers) => (currentLineMarkers.current = lineMarkers),
|
||||
useAlternativeBreaks
|
||||
useAlternativeBreaks,
|
||||
offsetLines: frontmatterLineOffset
|
||||
}).buildConfiguredMarkdownIt(),
|
||||
[onFrontmatterChange, onLineMarkerPositionChanged, useAlternativeBreaks]
|
||||
[onLineMarkerPositionChanged, useAlternativeBreaks, frontmatterLineOffset]
|
||||
)
|
||||
|
||||
const clearFrontmatter = useCallback(() => {
|
||||
hasNewYamlError.current = false
|
||||
rawMetaRef.current = undefined
|
||||
onBeforeRendering?.()
|
||||
}, [onBeforeRendering])
|
||||
|
||||
const checkYamlErrorState = useCallback(() => {
|
||||
setShowYamlError(hasNewYamlError.current)
|
||||
onAfterRendering?.()
|
||||
}, [onAfterRendering])
|
||||
|
||||
const baseReplacers = useComponentReplacers(onTaskCheckedChange, onImageClick, baseUrl)
|
||||
const baseReplacers = useComponentReplacers(onTaskCheckedChange, onImageClick, baseUrl, frontmatterLineOffset)
|
||||
const replacers = useCallback(
|
||||
() => baseReplacers().concat(additionalReplacers ? additionalReplacers() : []),
|
||||
[additionalReplacers, baseReplacers]
|
||||
)
|
||||
|
||||
const markdownReactDom = useConvertMarkdownToReactDom(
|
||||
trimmedContent,
|
||||
markdownIt,
|
||||
replacers,
|
||||
clearFrontmatter,
|
||||
checkYamlErrorState
|
||||
)
|
||||
const markdownReactDom = useConvertMarkdownToReactDom(trimmedContent, markdownIt, replacers)
|
||||
|
||||
useTranslation()
|
||||
useCalculateLineMarkerPosition(
|
||||
|
@ -112,17 +88,9 @@ export const BasicMarkdownRenderer: React.FC<BasicMarkdownRendererProps & Additi
|
|||
)
|
||||
useExtractFirstHeadline(markdownBodyRef, content, onFirstHeadingChange)
|
||||
useOnRefChange(tocAst, onTocChange)
|
||||
useOnRefChange(rawMetaRef, (newValue) => {
|
||||
if (!newValue) {
|
||||
onFrontmatterChange?.(undefined)
|
||||
} else {
|
||||
onFrontmatterChange?.(new NoteFrontmatter(newValue))
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div ref={outerContainerRef} className={'position-relative'}>
|
||||
<InvalidYamlAlert show={showYamlError} />
|
||||
<DocumentLengthLimitReachedAlert show={contentExceedsLimit} />
|
||||
<div
|
||||
ref={markdownBodyRef}
|
||||
|
|
|
@ -32,13 +32,15 @@ import { YoutubeReplacer } from '../replace-components/youtube/youtube-replacer'
|
|||
* @param onTaskCheckedChange A callback that gets executed if a task checkbox gets clicked
|
||||
* @param onImageClick A callback that should be executed if an image gets clicked
|
||||
* @param baseUrl The base url for relative links
|
||||
* @param frontmatterLinesToSkip The number of lines of the frontmatter part to add this as offset to line-numbers.
|
||||
*
|
||||
* @return the created list
|
||||
*/
|
||||
export const useComponentReplacers = (
|
||||
onTaskCheckedChange?: TaskCheckedChangeHandler,
|
||||
onImageClick?: ImageClickHandler,
|
||||
baseUrl?: string
|
||||
baseUrl?: string,
|
||||
frontmatterLinesToSkip?: number
|
||||
): (() => ComponentReplacer[]) =>
|
||||
useCallback(
|
||||
() => [
|
||||
|
@ -59,8 +61,8 @@ export const useComponentReplacers = (
|
|||
new HighlightedCodeReplacer(),
|
||||
new ColoredBlockquoteReplacer(),
|
||||
new KatexReplacer(),
|
||||
new TaskListReplacer(onTaskCheckedChange),
|
||||
new TaskListReplacer(onTaskCheckedChange, frontmatterLinesToSkip),
|
||||
new LinkReplacer(baseUrl)
|
||||
],
|
||||
[onImageClick, onTaskCheckedChange, baseUrl]
|
||||
[onImageClick, onTaskCheckedChange, baseUrl, frontmatterLinesToSkip]
|
||||
)
|
||||
|
|
|
@ -19,7 +19,6 @@ import { MarkdownItParserDebugger } from '../markdown-it-plugins/parser-debugger
|
|||
import { spoilerContainer } from '../markdown-it-plugins/spoiler-container'
|
||||
import { tasksLists } from '../markdown-it-plugins/tasks-lists'
|
||||
import { twitterEmojis } from '../markdown-it-plugins/twitter-emojis'
|
||||
import { RawNoteFrontmatter } from '../../editor-page/note-frontmatter/note-frontmatter'
|
||||
import { TocAst } from 'markdown-it-toc-done-right'
|
||||
import { LineMarkers, lineNumberMarker } from '../replace-components/linemarker/line-number-marker'
|
||||
import { plantumlWithError } from '../markdown-it-plugins/plantuml'
|
||||
|
@ -36,15 +35,13 @@ import { highlightedCode } from '../markdown-it-plugins/highlighted-code'
|
|||
import { quoteExtraColor } from '../markdown-it-plugins/quote-extra-color'
|
||||
import { quoteExtra } from '../markdown-it-plugins/quote-extra'
|
||||
import { documentTableOfContents } from '../markdown-it-plugins/document-table-of-contents'
|
||||
import { frontmatterExtract } from '../markdown-it-plugins/frontmatter'
|
||||
|
||||
export interface ConfiguratorDetails {
|
||||
useFrontmatter: boolean
|
||||
onParseError: (error: boolean) => void
|
||||
onRawMetaChange: (rawMeta: RawNoteFrontmatter) => void
|
||||
onToc: (toc: TocAst) => void
|
||||
onLineMarkers?: (lineMarkers: LineMarkers[]) => void
|
||||
useAlternativeBreaks?: boolean
|
||||
offsetLines?: number
|
||||
}
|
||||
|
||||
export class BasicMarkdownItConfigurator<T extends ConfiguratorDetails> {
|
||||
|
@ -105,17 +102,8 @@ export class BasicMarkdownItConfigurator<T extends ConfiguratorDetails> {
|
|||
spoilerContainer
|
||||
)
|
||||
|
||||
if (this.options.useFrontmatter) {
|
||||
this.configurations.push(
|
||||
frontmatterExtract({
|
||||
onParseError: this.options.onParseError,
|
||||
onRawMetaChange: this.options.onRawMetaChange
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (this.options.onLineMarkers) {
|
||||
this.configurations.push(lineNumberMarker(this.options.onLineMarkers))
|
||||
this.configurations.push(lineNumberMarker(this.options.onLineMarkers, this.options.offsetLines ?? 0))
|
||||
}
|
||||
|
||||
this.postConfigurations.push(linkifyExtra, MarkdownItParserDebugger)
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import yaml from 'js-yaml'
|
||||
import MarkdownIt from 'markdown-it'
|
||||
import frontmatter from 'markdown-it-front-matter'
|
||||
import { RawNoteFrontmatter } from '../../editor-page/note-frontmatter/note-frontmatter'
|
||||
|
||||
interface FrontmatterPluginOptions {
|
||||
onParseError: (error: boolean) => void
|
||||
onRawMetaChange: (rawMeta: RawNoteFrontmatter) => void
|
||||
}
|
||||
|
||||
export const frontmatterExtract: (options: FrontmatterPluginOptions) => MarkdownIt.PluginSimple =
|
||||
(options) => (markdownIt) => {
|
||||
frontmatter(markdownIt, (rawMeta: string) => {
|
||||
try {
|
||||
const meta: RawNoteFrontmatter = yaml.load(rawMeta) as RawNoteFrontmatter
|
||||
options.onParseError(false)
|
||||
options.onRawMetaChange(meta)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
options.onParseError(true)
|
||||
options.onRawMetaChange({} as RawNoteFrontmatter)
|
||||
}
|
||||
})
|
||||
}
|
|
@ -18,12 +18,13 @@ export type LineNumberMarkerOptions = (lineMarkers: LineMarkers[]) => void
|
|||
* This plugin adds markers to the dom, that are used to map line numbers to dom elements.
|
||||
* It also provides a list of line numbers for the top level dom elements.
|
||||
*/
|
||||
export const lineNumberMarker: (options: LineNumberMarkerOptions) => MarkdownIt.PluginSimple =
|
||||
(options) => (md: MarkdownIt) => {
|
||||
export const lineNumberMarker: (options: LineNumberMarkerOptions, offsetLines: number) => MarkdownIt.PluginSimple =
|
||||
(options, offsetLines = 0) =>
|
||||
(md: MarkdownIt) => {
|
||||
// add app_linemarker token before each opening or self-closing level-0 tag
|
||||
md.core.ruler.push('line_number_marker', (state) => {
|
||||
const lineMarkers: LineMarkers[] = []
|
||||
tagTokens(state.tokens, lineMarkers)
|
||||
tagTokens(state.tokens, lineMarkers, offsetLines)
|
||||
if (options) {
|
||||
options(lineMarkers)
|
||||
}
|
||||
|
@ -56,7 +57,7 @@ export const lineNumberMarker: (options: LineNumberMarkerOptions) => MarkdownIt.
|
|||
tokens.splice(tokenPosition, 0, startToken)
|
||||
}
|
||||
|
||||
const tagTokens = (tokens: Token[], lineMarkers: LineMarkers[]) => {
|
||||
const tagTokens = (tokens: Token[], lineMarkers: LineMarkers[], offsetLines: number) => {
|
||||
for (let tokenPosition = 0; tokenPosition < tokens.length; tokenPosition++) {
|
||||
const token = tokens[tokenPosition]
|
||||
if (token.hidden) {
|
||||
|
@ -71,14 +72,14 @@ export const lineNumberMarker: (options: LineNumberMarkerOptions) => MarkdownIt.
|
|||
const endLineNumber = token.map[1] + 1
|
||||
|
||||
if (token.level === 0) {
|
||||
lineMarkers.push({ startLine: startLineNumber, endLine: endLineNumber })
|
||||
lineMarkers.push({ startLine: startLineNumber + offsetLines, endLine: endLineNumber + offsetLines })
|
||||
}
|
||||
|
||||
insertNewLineMarker(startLineNumber, endLineNumber, tokenPosition, token.level, tokens)
|
||||
tokenPosition += 1
|
||||
|
||||
if (token.children) {
|
||||
tagTokens(token.children, lineMarkers)
|
||||
tagTokens(token.children, lineMarkers, offsetLines)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,16 +15,18 @@ export type TaskCheckedChangeHandler = (lineInMarkdown: number, checked: boolean
|
|||
*/
|
||||
export class TaskListReplacer extends ComponentReplacer {
|
||||
onTaskCheckedChange?: (lineInMarkdown: number, checked: boolean) => void
|
||||
private readonly frontmatterLinesOffset
|
||||
|
||||
constructor(onTaskCheckedChange?: TaskCheckedChangeHandler) {
|
||||
constructor(onTaskCheckedChange?: TaskCheckedChangeHandler, frontmatterLinesOffset?: number) {
|
||||
super()
|
||||
this.onTaskCheckedChange = onTaskCheckedChange
|
||||
this.frontmatterLinesOffset = frontmatterLinesOffset ?? 0
|
||||
}
|
||||
|
||||
handleCheckboxChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
|
||||
const lineNum = Number(event.currentTarget.dataset.line)
|
||||
if (this.onTaskCheckedChange) {
|
||||
this.onTaskCheckedChange(lineNum, event.currentTarget.checked)
|
||||
this.onTaskCheckedChange(lineNum + this.frontmatterLinesOffset, event.currentTarget.checked)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue