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:
Tilman Vatteroth 2022-02-14 17:37:34 +01:00 committed by GitHub
parent 0f3f7a82b5
commit 8b4e9191e5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 260 additions and 213 deletions

View file

@ -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])
}

View file

@ -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

View file

@ -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} />

View file

@ -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()

View file

@ -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>>)
})
}
}