mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-15 15:44:45 -04:00
Added synced scrolling (#386)
Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de>
This commit is contained in:
parent
164b5436ae
commit
73007ef597
10 changed files with 413 additions and 38 deletions
|
@ -1,12 +1,14 @@
|
|||
import React, { useRef, useState } from 'react'
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { Dropdown } from 'react-bootstrap'
|
||||
import useResizeObserver from 'use-resize-observer'
|
||||
import { TocAst } from '../../../external-types/markdown-it-toc-done-right/interface'
|
||||
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
||||
import { ShowIf } from '../../common/show-if/show-if'
|
||||
import { MarkdownRenderer } from '../../markdown-renderer/markdown-renderer'
|
||||
import { YAMLMetaData } from '../yaml-metadata/yaml-metadata'
|
||||
import { LineMarkerPosition, MarkdownRenderer } from '../../markdown-renderer/markdown-renderer'
|
||||
import { ScrollProps, ScrollState } from '../scroll/scroll-props'
|
||||
import { findLineMarks } from '../scroll/utils'
|
||||
import { TableOfContents } from '../table-of-contents/table-of-contents'
|
||||
import { YAMLMetaData } from '../yaml-metadata/yaml-metadata'
|
||||
|
||||
interface DocumentRenderPaneProps {
|
||||
content: string
|
||||
|
@ -15,15 +17,69 @@ interface DocumentRenderPaneProps {
|
|||
wide?: boolean
|
||||
}
|
||||
|
||||
export const DocumentRenderPane: React.FC<DocumentRenderPaneProps> = ({ content, onMetadataChange, onFirstHeadingChange, wide }) => {
|
||||
export const DocumentRenderPane: React.FC<DocumentRenderPaneProps & ScrollProps> = ({ content, onMetadataChange, onFirstHeadingChange, wide, scrollState, onScroll, onMakeScrollSource }) => {
|
||||
const [tocAst, setTocAst] = useState<TocAst>()
|
||||
const renderer = useRef<HTMLDivElement>(null)
|
||||
const { width } = useResizeObserver({ ref: renderer })
|
||||
const lastScrollPosition = useRef<number>()
|
||||
const [lineMarks, setLineMarks] = useState<LineMarkerPosition[]>()
|
||||
|
||||
const realWidth = width || 0
|
||||
|
||||
useEffect(() => {
|
||||
if (!renderer.current || !lineMarks || !scrollState) {
|
||||
return
|
||||
}
|
||||
const { lastMarkBefore, firstMarkAfter } = findLineMarks(lineMarks, scrollState.firstLineInView)
|
||||
const positionBefore = lastMarkBefore ? lastMarkBefore.position : 0
|
||||
const positionAfter = firstMarkAfter ? firstMarkAfter.position : renderer.current.offsetHeight
|
||||
const lastMarkBeforeLine = lastMarkBefore ? lastMarkBefore.line : 1
|
||||
const firstMarkAfterLine = firstMarkAfter ? firstMarkAfter.line : content.split('\n').length
|
||||
const lineCount = firstMarkAfterLine - lastMarkBeforeLine
|
||||
const blockHeight = positionAfter - positionBefore
|
||||
const lineHeight = blockHeight / lineCount
|
||||
const position = positionBefore + (scrollState.firstLineInView - lastMarkBeforeLine) * lineHeight + scrollState.scrolledPercentage / 100 * lineHeight
|
||||
const correctedPosition = Math.floor(position)
|
||||
if (correctedPosition !== lastScrollPosition.current) {
|
||||
lastScrollPosition.current = correctedPosition
|
||||
renderer.current.scrollTo({
|
||||
top: correctedPosition
|
||||
})
|
||||
}
|
||||
}, [content, lineMarks, scrollState])
|
||||
|
||||
const userScroll = useCallback(() => {
|
||||
if (!renderer.current || !lineMarks || !onScroll) {
|
||||
return
|
||||
}
|
||||
const resyncedScroll = Math.ceil(renderer.current.scrollTop) === lastScrollPosition.current
|
||||
if (resyncedScroll) {
|
||||
return
|
||||
}
|
||||
|
||||
const scrollTop = renderer.current.scrollTop
|
||||
|
||||
const beforeLineMark = lineMarks
|
||||
.filter(lineMark => lineMark.position <= scrollTop)
|
||||
.reduce((prevLineMark, currentLineMark) =>
|
||||
prevLineMark.line >= currentLineMark.line ? prevLineMark : currentLineMark)
|
||||
|
||||
const afterLineMark = lineMarks
|
||||
.filter(lineMark => lineMark.position > scrollTop)
|
||||
.reduce((prevLineMark, currentLineMark) =>
|
||||
prevLineMark.line < currentLineMark.line ? prevLineMark : currentLineMark)
|
||||
|
||||
const blockHeight = afterLineMark.position - beforeLineMark.position
|
||||
const distanceToBefore = scrollTop - beforeLineMark.position
|
||||
const percentageRaw = (distanceToBefore / blockHeight)
|
||||
const percentage = Math.floor(percentageRaw * 100)
|
||||
const newScrollState: ScrollState = { firstLineInView: beforeLineMark.line, scrolledPercentage: percentage }
|
||||
onScroll(newScrollState)
|
||||
}, [lineMarks, onScroll])
|
||||
|
||||
return (
|
||||
<div className={'bg-light flex-fill pb-5 flex-row d-flex w-100 h-100 overflow-y-scroll'} ref={renderer}>
|
||||
<div className={'bg-light flex-fill pb-5 flex-row d-flex w-100 h-100 overflow-y-scroll'}
|
||||
ref={renderer} onScroll={userScroll} onMouseEnter={onMakeScrollSource} >
|
||||
<div className={'col-md'}/>
|
||||
<MarkdownRenderer
|
||||
className={'flex-fill'}
|
||||
|
@ -32,11 +88,12 @@ export const DocumentRenderPane: React.FC<DocumentRenderPaneProps> = ({ content,
|
|||
onTocChange={(tocAst) => setTocAst(tocAst)}
|
||||
onMetaDataChange={onMetadataChange}
|
||||
onFirstHeadingChange={onFirstHeadingChange}
|
||||
onLineMarkerPositionChanged={setLineMarks}
|
||||
/>
|
||||
|
||||
<div className={'col-md'}>
|
||||
<ShowIf condition={realWidth >= 1280 && !!tocAst}>
|
||||
<TableOfContents ast={tocAst as TocAst} sticky={true}/>
|
||||
<TableOfContents ast={tocAst as TocAst} className={'position-fixed'}/>
|
||||
</ShowIf>
|
||||
<ShowIf condition={realWidth < 1280 && !!tocAst}>
|
||||
<div className={'markdown-toc-sidebar-button'}>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue