mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-25 12:34:45 -04:00
Fix smooth scroll and other bugs (#1861)
This PR fixes: - The wrong clean up of window post message communicators - The smooth scroll bug in chrome (Fixes Anchor navigation in render view not working #1770) - Scroll by using touch devices in renderer - Lazy loading of the editor (code mirror doesn't need to be lazy loaded any more)
This commit is contained in:
parent
0f3f7a82b5
commit
8b4e9191e5
15 changed files with 260 additions and 213 deletions
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import type React from 'react'
|
||||
import { useCallback, useState } 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'
|
||||
|
@ -17,7 +17,7 @@ export const useDocumentSyncScrolling = (
|
|||
numberOfLines: number,
|
||||
scrollState?: ScrollState,
|
||||
onScroll?: (scrollState: ScrollState) => void
|
||||
): [(lineMarkers: LineMarkerPosition[]) => void, () => void] => {
|
||||
): [(lineMarkers: LineMarkerPosition[]) => void, React.UIEventHandler<HTMLElement>] => {
|
||||
const [lineMarks, setLineMarks] = useState<LineMarkerPosition[]>()
|
||||
|
||||
const onLineMarkerPositionChanged = useCallback(
|
||||
|
@ -40,5 +40,5 @@ export const useDocumentSyncScrolling = (
|
|||
const onUserScroll = useOnUserScroll(lineMarks, outerContainerRef, onScroll)
|
||||
useScrollToLineMark(scrollState, lineMarks, numberOfLines, outerContainerRef)
|
||||
|
||||
return [onLineMarkerPositionChanged, onUserScroll]
|
||||
return useMemo(() => [onLineMarkerPositionChanged, onUserScroll], [onLineMarkerPositionChanged, onUserScroll])
|
||||
}
|
||||
|
|
|
@ -4,16 +4,16 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import type { RefObject } from 'react'
|
||||
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'
|
||||
|
||||
export const useOnUserScroll = (
|
||||
lineMarks: LineMarkerPosition[] | undefined,
|
||||
scrollContainer: RefObject<HTMLElement>,
|
||||
scrollContainer: React.RefObject<HTMLElement>,
|
||||
onScroll: ((newScrollState: ScrollState) => void) | undefined
|
||||
): (() => void) => {
|
||||
): React.UIEventHandler<HTMLElement> => {
|
||||
return useCallback(() => {
|
||||
if (!scrollContainer.current || !lineMarks || lineMarks.length === 0 || !onScroll) {
|
||||
return
|
||||
|
|
|
@ -84,7 +84,9 @@ export const MarkdownDocument: React.FC<MarkdownDocumentProps> = ({
|
|||
className={`${styles['markdown-document']} ${additionalOuterContainerClasses ?? ''}`}
|
||||
ref={internalDocumentRenderPaneRef}
|
||||
onScroll={onUserScroll}
|
||||
onMouseEnter={onMakeScrollSource}>
|
||||
data-scroll-element={true}
|
||||
onMouseEnter={onMakeScrollSource}
|
||||
onTouchStart={onMakeScrollSource}>
|
||||
<div className={styles['markdown-document-side']} />
|
||||
<div className={styles['markdown-document-content']}>
|
||||
<InvalidYamlAlert show={!!frontmatterInfo?.frontmatterInvalid} />
|
||||
|
|
|
@ -7,11 +7,11 @@
|
|||
import { useCallback } from 'react'
|
||||
import type { CommunicationMessages, EditorToRendererMessageType } from '../rendering-message'
|
||||
import { useEditorToRendererCommunicator } from '../../../editor-page/render-context/editor-to-renderer-communicator-context-provider'
|
||||
import type { PostMessage } from '../window-post-message-communicator'
|
||||
import type { MessagePayload } from '../window-post-message-communicator'
|
||||
import { useEffectOnRendererReady } from './use-effect-on-renderer-ready'
|
||||
|
||||
export const useSendToRenderer = (
|
||||
message: undefined | Extract<CommunicationMessages, PostMessage<EditorToRendererMessageType>>
|
||||
message: undefined | Extract<CommunicationMessages, MessagePayload<EditorToRendererMessageType>>
|
||||
): void => {
|
||||
const iframeCommunicator = useEditorToRendererCommunicator()
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import type { Logger } from '../../../utils/logger'
|
||||
import Optional from 'optional-js'
|
||||
|
||||
/**
|
||||
* Error that will be thrown if a message couldn't be sent.
|
||||
|
@ -12,7 +13,7 @@ import type { Logger } from '../../../utils/logger'
|
|||
export class IframeCommunicatorSendingError extends Error {}
|
||||
|
||||
export type Handler<MESSAGES, MESSAGE_TYPE extends string> = (
|
||||
values: Extract<MESSAGES, PostMessage<MESSAGE_TYPE>>
|
||||
values: Extract<MESSAGES, MessagePayload<MESSAGE_TYPE>>
|
||||
) => void
|
||||
|
||||
export type MaybeHandler<MESSAGES, MESSAGE_TYPE extends string> = Handler<MESSAGES, MESSAGE_TYPE> | undefined
|
||||
|
@ -21,7 +22,7 @@ export type HandlerMap<MESSAGES, MESSAGE_TYPE extends string> = Partial<{
|
|||
[key in MESSAGE_TYPE]: MaybeHandler<MESSAGES, MESSAGE_TYPE>
|
||||
}>
|
||||
|
||||
export interface PostMessage<MESSAGE_TYPE extends string> {
|
||||
export interface MessagePayload<MESSAGE_TYPE extends string> {
|
||||
type: MESSAGE_TYPE
|
||||
}
|
||||
|
||||
|
@ -31,16 +32,17 @@ export interface PostMessage<MESSAGE_TYPE extends string> {
|
|||
export abstract class WindowPostMessageCommunicator<
|
||||
RECEIVE_TYPE extends string,
|
||||
SEND_TYPE extends string,
|
||||
MESSAGES extends PostMessage<RECEIVE_TYPE | SEND_TYPE>
|
||||
MESSAGES extends MessagePayload<RECEIVE_TYPE | SEND_TYPE>
|
||||
> {
|
||||
private messageTarget?: Window
|
||||
private targetOrigin?: string
|
||||
private communicationEnabled: boolean
|
||||
private handlers: HandlerMap<MESSAGES, RECEIVE_TYPE> = {}
|
||||
private log
|
||||
private readonly handlers: HandlerMap<MESSAGES, RECEIVE_TYPE> = {}
|
||||
private readonly log: Logger
|
||||
private readonly boundListener: (event: MessageEvent) => void
|
||||
|
||||
constructor() {
|
||||
window.addEventListener('message', this.handleEvent.bind(this))
|
||||
public constructor() {
|
||||
this.boundListener = this.handleEvent.bind(this)
|
||||
this.communicationEnabled = false
|
||||
this.log = this.createLogger()
|
||||
}
|
||||
|
@ -48,10 +50,17 @@ export abstract class WindowPostMessageCommunicator<
|
|||
protected abstract createLogger(): Logger
|
||||
|
||||
/**
|
||||
* Removes the message event listener from the {@link window}
|
||||
* Registers the event listener on the current global {@link window}.
|
||||
*/
|
||||
public registerEventListener(): void {
|
||||
window.addEventListener('message', this.boundListener, { passive: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the message event listener from the {@link window}.
|
||||
*/
|
||||
public unregisterEventListener(): void {
|
||||
window.removeEventListener('message', this.handleEvent.bind(this))
|
||||
window.removeEventListener('message', this.boundListener)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -90,7 +99,7 @@ export abstract class WindowPostMessageCommunicator<
|
|||
*
|
||||
* @param message The message to send.
|
||||
*/
|
||||
public sendMessageToOtherSide(message: Extract<MESSAGES, PostMessage<SEND_TYPE>>): void {
|
||||
public sendMessageToOtherSide(message: Extract<MESSAGES, MessagePayload<SEND_TYPE>>): void {
|
||||
if (this.messageTarget === undefined || this.targetOrigin === undefined) {
|
||||
throw new IframeCommunicatorSendingError(`Other side is not set.\nMessage was: ${JSON.stringify(message)}`)
|
||||
}
|
||||
|
@ -121,18 +130,22 @@ export abstract class WindowPostMessageCommunicator<
|
|||
* @param event The received event
|
||||
* @return {@code true} if the event was processed.
|
||||
*/
|
||||
protected handleEvent(event: MessageEvent<PostMessage<RECEIVE_TYPE>>): boolean | undefined {
|
||||
const data = event.data
|
||||
protected handleEvent(event: MessageEvent<MessagePayload<RECEIVE_TYPE>>): void {
|
||||
Optional.ofNullable(event.data).ifPresent((payload) => {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
this.processPayload(payload)
|
||||
})
|
||||
}
|
||||
|
||||
if (!(data.type in this.handlers)) {
|
||||
return true
|
||||
}
|
||||
const handler = this.handlers[data.type]
|
||||
if (!handler) {
|
||||
return true
|
||||
}
|
||||
this.log.debug('Received event', data)
|
||||
handler(data as Extract<MESSAGES, PostMessage<RECEIVE_TYPE>>)
|
||||
return false
|
||||
/**
|
||||
* Processes a {@link MessagePayload message payload} using the correct {@link Handler handler}.
|
||||
* @param payload The payload that should be processed
|
||||
*/
|
||||
private processPayload(payload: MessagePayload<RECEIVE_TYPE>): void {
|
||||
return Optional.ofNullable<Handler<MESSAGES, RECEIVE_TYPE>>(this.handlers[payload.type]).ifPresent((handler) => {
|
||||
this.log.debug('Received event', payload)
|
||||
handler(payload as Extract<MESSAGES, MessagePayload<RECEIVE_TYPE>>)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue