mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-14 07:04:45 -04:00
modularize renderer (#552)
This commit is contained in:
parent
ac00bc98c0
commit
a86d4cbc58
15 changed files with 580 additions and 455 deletions
|
@ -0,0 +1,63 @@
|
|||
import equal from 'fast-deep-equal'
|
||||
import { RefObject, useCallback, useEffect, useRef } from 'react'
|
||||
import useResizeObserver from 'use-resize-observer'
|
||||
import { LineMarkerPosition } from '../types'
|
||||
import { LineMarkers } from '../markdown-it-plugins/line-number-marker'
|
||||
|
||||
export const calculateLineMarkerPositions = (documentElement: HTMLDivElement, currentLineMarkers: LineMarkers[], offset?: number): LineMarkerPosition[] => {
|
||||
const lineMarkers = currentLineMarkers
|
||||
const children: HTMLCollection = documentElement.children
|
||||
const lineMarkerPositions:LineMarkerPosition[] = []
|
||||
|
||||
Array.from(children).forEach((child, childIndex) => {
|
||||
const htmlChild = (child as HTMLElement)
|
||||
if (htmlChild.offsetTop === undefined) {
|
||||
return
|
||||
}
|
||||
const currentLineMarker = lineMarkers[childIndex]
|
||||
if (currentLineMarker === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const lastPosition = lineMarkerPositions[lineMarkerPositions.length - 1]
|
||||
if (!lastPosition || lastPosition.line !== currentLineMarker.startLine) {
|
||||
lineMarkerPositions.push({
|
||||
line: currentLineMarker.startLine,
|
||||
position: htmlChild.offsetTop + (offset ?? 0)
|
||||
})
|
||||
}
|
||||
|
||||
lineMarkerPositions.push({
|
||||
line: currentLineMarker.endLine,
|
||||
position: htmlChild.offsetTop + htmlChild.offsetHeight + (offset ?? 0)
|
||||
})
|
||||
})
|
||||
|
||||
return lineMarkerPositions
|
||||
}
|
||||
|
||||
export const useCalculateLineMarkerPosition = (documentElement: RefObject<HTMLDivElement>, lineMarkers?: LineMarkers[], onLineMarkerPositionChanged?: (lineMarkerPosition: LineMarkerPosition[]) => void, offset?: number) : void => {
|
||||
const lastLineMarkerPositions = useRef<LineMarkerPosition[]>()
|
||||
|
||||
const calculateNewLineMarkerPositions = useCallback(() => {
|
||||
if (!documentElement.current || !onLineMarkerPositionChanged || !lineMarkers) {
|
||||
return
|
||||
}
|
||||
|
||||
const newLines = calculateLineMarkerPositions(documentElement.current, lineMarkers, offset)
|
||||
|
||||
if (!equal(newLines, lastLineMarkerPositions)) {
|
||||
lastLineMarkerPositions.current = newLines
|
||||
onLineMarkerPositionChanged(newLines)
|
||||
}
|
||||
}, [documentElement, lineMarkers, offset, onLineMarkerPositionChanged])
|
||||
|
||||
useEffect(() => {
|
||||
calculateNewLineMarkerPositions()
|
||||
}, [calculateNewLineMarkerPositions])
|
||||
|
||||
useResizeObserver({
|
||||
ref: documentElement,
|
||||
onResize: () => calculateNewLineMarkerPositions()
|
||||
})
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
import { DomElement } from 'domhandler'
|
||||
import React, { Fragment, ReactElement } from 'react'
|
||||
import { convertNodeToElement, Transform } from 'react-html-parser'
|
||||
import {
|
||||
ComponentReplacer,
|
||||
SubNodeTransform
|
||||
} from '../replace-components/ComponentReplacer'
|
||||
import { LineKeys } from '../types'
|
||||
|
||||
export interface TextDifferenceResult {
|
||||
lines: LineKeys[],
|
||||
lastUsedLineId: number
|
||||
}
|
||||
|
||||
export const calculateKeyFromLineMarker = (node: DomElement, lineKeys?: LineKeys[]): number|undefined => {
|
||||
if (!node.attribs || lineKeys === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const key = node.attribs['data-key']
|
||||
if (key) {
|
||||
return Number(key)
|
||||
}
|
||||
|
||||
const lineMarker = node.prev
|
||||
if (!lineMarker || !lineMarker.attribs) {
|
||||
return
|
||||
}
|
||||
|
||||
const lineInMarkdown = lineMarker.attribs['data-start-line']
|
||||
if (lineInMarkdown === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const line = Number(lineInMarkdown)
|
||||
if (lineKeys[line] === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
return lineKeys[line].id
|
||||
}
|
||||
|
||||
export const findNodeReplacement = (node: DomElement, index: number, allReplacers: ComponentReplacer[], subNodeTransform: SubNodeTransform): ReactElement|null|undefined => {
|
||||
return allReplacers
|
||||
.map((componentReplacer) => componentReplacer.getReplacement(node, subNodeTransform))
|
||||
.find((replacement) => replacement !== undefined)
|
||||
}
|
||||
|
||||
export const renderNativeNode = (node: DomElement, key: number, transform: Transform): ReactElement => {
|
||||
if (node.attribs === undefined) {
|
||||
node.attribs = {}
|
||||
}
|
||||
|
||||
delete node.attribs['data-key']
|
||||
return convertNodeToElement(node, key, transform)
|
||||
}
|
||||
|
||||
export const buildTransformer = (lineKeys: (LineKeys[] | undefined), allReplacers: ComponentReplacer[]):Transform => {
|
||||
const transform: Transform = (node, index) => {
|
||||
const nativeRenderer = (subNode: DomElement, subKey: number) => renderNativeNode(subNode, subKey, transform)
|
||||
const subNodeTransform:SubNodeTransform = (subNode, subIndex) => transform(subNode, subIndex, transform)
|
||||
|
||||
const key = calculateKeyFromLineMarker(node, lineKeys) ?? -index
|
||||
const tryReplacement = findNodeReplacement(node, key, allReplacers, subNodeTransform)
|
||||
if (tryReplacement === null) {
|
||||
return null
|
||||
} else if (tryReplacement === undefined) {
|
||||
return nativeRenderer(node, key)
|
||||
} else {
|
||||
return <Fragment key={key}>{tryReplacement}</Fragment>
|
||||
}
|
||||
}
|
||||
return transform
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import { diffArrays } from 'diff'
|
||||
import { TextDifferenceResult } from './html-react-transformer'
|
||||
import { LineKeys } from '../types'
|
||||
|
||||
export const calculateNewLineNumberMapping = (newMarkdownLines: string[], oldLineKeys: LineKeys[], lastUsedLineId: number): TextDifferenceResult => {
|
||||
const lineDifferences = diffArrays<string, LineKeys>(newMarkdownLines, oldLineKeys, {
|
||||
comparator: (left:string|LineKeys, right:string|LineKeys) => {
|
||||
const leftLine = (left as LineKeys).line ?? (left as string)
|
||||
const rightLine = (right as LineKeys).line ?? (right as string)
|
||||
return leftLine === rightLine
|
||||
}
|
||||
})
|
||||
|
||||
const newLines: LineKeys[] = []
|
||||
|
||||
lineDifferences
|
||||
.filter((change) => change.added === undefined || !change.added)
|
||||
.forEach((value) => {
|
||||
if (value.removed) {
|
||||
(value.value as string[])
|
||||
.forEach(line => {
|
||||
lastUsedLineId += 1
|
||||
newLines.push({ line: line, id: lastUsedLineId })
|
||||
})
|
||||
} else {
|
||||
(value.value as LineKeys[])
|
||||
.forEach((line) => newLines.push(line))
|
||||
}
|
||||
})
|
||||
|
||||
return { lines: newLines, lastUsedLineId: lastUsedLineId }
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
import equal from 'fast-deep-equal'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { RawYAMLMetadata, YAMLMetaData } from '../../editor/yaml-metadata/yaml-metadata'
|
||||
|
||||
export const usePostMetaDataOnChange = (
|
||||
rawMetaRef: RawYAMLMetadata|undefined,
|
||||
firstHeadingRef: string|undefined,
|
||||
onMetaDataChange?: (yamlMetaData: YAMLMetaData | undefined) => void,
|
||||
onFirstHeadingChange?: (firstHeading: string | undefined) => void
|
||||
): void => {
|
||||
const oldMetaRef = useRef<RawYAMLMetadata>()
|
||||
const oldFirstHeadingRef = useRef<string>()
|
||||
|
||||
useEffect(() => {
|
||||
if (onMetaDataChange && !equal(oldMetaRef.current, rawMetaRef)) {
|
||||
if (rawMetaRef) {
|
||||
const newMetaData = new YAMLMetaData(rawMetaRef)
|
||||
onMetaDataChange(newMetaData)
|
||||
} else {
|
||||
onMetaDataChange(undefined)
|
||||
}
|
||||
oldMetaRef.current = rawMetaRef
|
||||
}
|
||||
if (onFirstHeadingChange && !equal(firstHeadingRef, oldFirstHeadingRef.current)) {
|
||||
onFirstHeadingChange(firstHeadingRef || undefined)
|
||||
oldFirstHeadingRef.current = firstHeadingRef
|
||||
}
|
||||
})
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import equal from 'fast-deep-equal'
|
||||
import { RefObject, useEffect, useRef } from 'react'
|
||||
import { TocAst } from '../../../external-types/markdown-it-toc-done-right/interface'
|
||||
|
||||
export const usePostTocAstOnChange = (tocAst: RefObject<TocAst|undefined>, onTocChange?: (ast: TocAst) => void): void => {
|
||||
const lastTocAst = useRef<TocAst>()
|
||||
useEffect(() => {
|
||||
if (onTocChange && tocAst.current && !equal(tocAst, lastTocAst.current)) {
|
||||
lastTocAst.current = tocAst.current
|
||||
onTocChange(tocAst.current)
|
||||
}
|
||||
}, [onTocChange, tocAst])
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue