diff --git a/src/components/editor-page/editor-page-content.tsx b/src/components/editor-page/editor-page-content.tsx new file mode 100644 index 000000000..c97f2fdff --- /dev/null +++ b/src/components/editor-page/editor-page-content.tsx @@ -0,0 +1,168 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import React, { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useApplyDarkMode } from '../../hooks/common/use-apply-dark-mode' +import { setCheckboxInMarkdownContent, updateNoteTitleByFirstHeading } from '../../redux/note-details/methods' +import { MotdModal } from '../common/motd-modal/motd-modal' +import { ShowIf } from '../common/show-if/show-if' +import { ErrorWhileLoadingNoteAlert } from '../document-read-only-page/ErrorWhileLoadingNoteAlert' +import { LoadingNoteAlert } from '../document-read-only-page/LoadingNoteAlert' +import { AppBar, AppBarMode } from './app-bar/app-bar' +import { EditorMode } from './app-bar/editor-view-mode' +import { useLoadNoteFromServer } from './hooks/useLoadNoteFromServer' +import { useViewModeShortcuts } from './hooks/useViewModeShortcuts' +import { Sidebar } from './sidebar/sidebar' +import { Splitter } from './splitter/splitter' +import type { DualScrollState, ScrollState } from './synced-scroll/scroll-props' +import { RendererType } from '../render-page/window-post-message-communicator/rendering-message' +import { useEditorModeFromUrl } from './hooks/useEditorModeFromUrl' +import { UiNotifications } from '../notifications/ui-notifications' +import { useUpdateLocalHistoryEntry } from './hooks/useUpdateLocalHistoryEntry' +import { useApplicationState } from '../../hooks/common/use-application-state' +import { EditorDocumentRenderer } from './editor-document-renderer/editor-document-renderer' +import { Logger } from '../../utils/logger' +import { NoteType } from '../../redux/note-details/types/note-details' +import { NoteAndAppTitleHead } from '../layout/note-and-app-title-head' +import equal from 'fast-deep-equal' +import { EditorPane } from './editor-pane/editor-pane' + +export interface EditorPagePathParams { + id: string +} + +export enum ScrollSource { + EDITOR = 'editor', + RENDERER = 'renderer' +} + +const log = new Logger('EditorPage') + +/** + * This is the content of the actual editor page. + */ +export const EditorPageContent: React.FC = () => { + useTranslation() + const scrollSource = useRef(ScrollSource.EDITOR) + const editorMode: EditorMode = useApplicationState((state) => state.editorConfig.editorMode) + const editorSyncScroll: boolean = useApplicationState((state) => state.editorConfig.syncScroll) + + const [scrollState, setScrollState] = useState(() => ({ + editorScrollState: { firstLineInView: 1, scrolledPercentage: 0 }, + rendererScrollState: { firstLineInView: 1, scrolledPercentage: 0 } + })) + + const onMarkdownRendererScroll = useCallback( + (newScrollState: ScrollState) => { + if (scrollSource.current === ScrollSource.RENDERER && editorSyncScroll) { + setScrollState((old) => { + const newState: DualScrollState = { + editorScrollState: newScrollState, + rendererScrollState: old.rendererScrollState + } + return equal(newState, old) ? old : newState + }) + } + }, + [editorSyncScroll] + ) + + useEffect(() => { + log.debug('New scroll state', scrollState, scrollSource.current) + }, [scrollState]) + + const onEditorScroll = useCallback( + (newScrollState: ScrollState) => { + if (scrollSource.current === ScrollSource.EDITOR && editorSyncScroll) { + setScrollState((old) => { + const newState: DualScrollState = { + rendererScrollState: newScrollState, + editorScrollState: old.editorScrollState + } + return equal(newState, old) ? old : newState + }) + } + }, + [editorSyncScroll] + ) + + useViewModeShortcuts() + useApplyDarkMode() + useEditorModeFromUrl() + + const [error, loading] = useLoadNoteFromServer() + + useUpdateLocalHistoryEntry(!error && !loading) + + const setRendererToScrollSource = useCallback(() => { + if (scrollSource.current !== ScrollSource.RENDERER) { + scrollSource.current = ScrollSource.RENDERER + log.debug('Make renderer scroll source') + } + }, []) + + const setEditorToScrollSource = useCallback(() => { + if (scrollSource.current !== ScrollSource.EDITOR) { + scrollSource.current = ScrollSource.EDITOR + log.debug('Make editor scroll source') + } + }, []) + + const leftPane = useMemo( + () => ( + + ), + [onEditorScroll, scrollState.editorScrollState, setEditorToScrollSource] + ) + const noteType: NoteType = useApplicationState((state) => state.noteDetails.frontmatter.type) + + const rightPane = useMemo( + () => ( + + ), + [noteType, onMarkdownRendererScroll, scrollState.rendererScrollState, setRendererToScrollSource] + ) + + return ( + + + + +
+ +
+ + +
+ +
+ + +
+
+
+
+ ) +} diff --git a/src/components/editor-page/editor-pane/editor-pane.tsx b/src/components/editor-page/editor-pane/editor-pane.tsx index eaa95a7a9..2a70d6ee3 100644 --- a/src/components/editor-page/editor-pane/editor-pane.tsx +++ b/src/components/editor-page/editor-pane/editor-pane.tsx @@ -91,7 +91,10 @@ export const EditorPane: React.FC = ({ scrollState, onScroll, onMak const { t } = useTranslation() return ( -
+
= ({ scrollState, onScroll, onMak
) } - -export default EditorPane diff --git a/src/components/editor-page/editor-pane/hooks/use-cursor-activity-callback.ts b/src/components/editor-page/editor-pane/hooks/use-cursor-activity-callback.ts index 60a84b436..a1ce7c06a 100644 --- a/src/components/editor-page/editor-pane/hooks/use-cursor-activity-callback.ts +++ b/src/components/editor-page/editor-pane/hooks/use-cursor-activity-callback.ts @@ -5,12 +5,12 @@ */ import type { RefObject } from 'react' -import { useMemo } from 'react' +import { useMemo, useRef } from 'react' import { updateCursorPositions } from '../../../../redux/note-details/methods' import type { ViewUpdate } from '@codemirror/view' import { EditorView } from '@codemirror/view' import { Logger } from '../../../../utils/logger' -import type { Extension } from '@codemirror/state' +import type { Extension, SelectionRange } from '@codemirror/state' const logger = new Logger('useCursorActivityCallback') @@ -20,14 +20,20 @@ const logger = new Logger('useCursorActivityCallback') * @return the generated callback */ export const useCursorActivityCallback = (editorFocused: RefObject): Extension => { + const lastMainSelection = useRef() + return useMemo( () => EditorView.updateListener.of((viewUpdate: ViewUpdate): void => { + const firstSelection = viewUpdate.state.selection.main + if (lastMainSelection.current === firstSelection) { + return + } + lastMainSelection.current = firstSelection if (!editorFocused.current) { logger.debug("Don't post updated cursor because editor isn't focused") return } - const firstSelection = viewUpdate.state.selection.main const newCursorPos = { from: firstSelection.from, to: firstSelection.to === firstSelection.from ? undefined : firstSelection.to diff --git a/src/components/editor-page/editor-pane/tool-bar/emoji-picker/emoji-picker-button.tsx b/src/components/editor-page/editor-pane/tool-bar/emoji-picker/emoji-picker-button.tsx index 86f989478..4aed09abd 100644 --- a/src/components/editor-page/editor-pane/tool-bar/emoji-picker/emoji-picker-button.tsx +++ b/src/components/editor-page/editor-pane/tool-bar/emoji-picker/emoji-picker-button.tsx @@ -38,3 +38,5 @@ export const EmojiPickerButton: React.FC = () => { ) } + +export default EmojiPickerButton diff --git a/src/components/editor-page/editor-pane/tool-bar/tool-bar.tsx b/src/components/editor-page/editor-pane/tool-bar/tool-bar.tsx index d46bdd451..f4e6c77ae 100644 --- a/src/components/editor-page/editor-pane/tool-bar/tool-bar.tsx +++ b/src/components/editor-page/editor-pane/tool-bar/tool-bar.tsx @@ -4,15 +4,16 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import React from 'react' +import React, { Fragment, Suspense } from 'react' import { ButtonGroup, ButtonToolbar } from 'react-bootstrap' -import { EmojiPickerButton } from './emoji-picker/emoji-picker-button' import { TablePickerButton } from './table-picker/table-picker-button' import styles from './tool-bar.module.scss' import { UploadImageButton } from './upload-image-button' import { ToolbarButton } from './toolbar-button' import { FormatType } from '../../../../redux/note-details/types' +const EmojiPickerButton = React.lazy(() => import('./emoji-picker/emoji-picker-button')) + export const ToolBar: React.FC = () => { return ( @@ -43,7 +44,9 @@ export const ToolBar: React.FC = () => { - + }> + + ) diff --git a/src/components/editor-page/render-context/editor-to-renderer-communicator-context-provider.tsx b/src/components/editor-page/render-context/editor-to-renderer-communicator-context-provider.tsx index 034cf6b93..85f2c7495 100644 --- a/src/components/editor-page/render-context/editor-to-renderer-communicator-context-provider.tsx +++ b/src/components/editor-page/render-context/editor-to-renderer-communicator-context-provider.tsx @@ -4,7 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import React, { createContext, useContext, useMemo } from 'react' +import React, { createContext, useContext, useEffect, useMemo } from 'react' import { EditorToRendererCommunicator } from '../../render-page/window-post-message-communicator/editor-to-renderer-communicator' const EditorToRendererCommunicatorContext = createContext(undefined) @@ -29,6 +29,14 @@ export const useEditorToRendererCommunicator: () => EditorToRendererCommunicator export const EditorToRendererCommunicatorContextProvider: React.FC = ({ children }) => { const communicator = useMemo(() => new EditorToRendererCommunicator(), []) + useEffect(() => { + const currentCommunicator = communicator + currentCommunicator.registerEventListener() + return () => { + currentCommunicator.unregisterEventListener() + } + }, [communicator]) + return ( {children} diff --git a/src/components/editor-page/render-context/renderer-to-editor-communicator-context-provider.tsx b/src/components/editor-page/render-context/renderer-to-editor-communicator-context-provider.tsx index ba82b37d8..636394ad9 100644 --- a/src/components/editor-page/render-context/renderer-to-editor-communicator-context-provider.tsx +++ b/src/components/editor-page/render-context/renderer-to-editor-communicator-context-provider.tsx @@ -27,20 +27,18 @@ export const useRendererToEditorCommunicator: () => RendererToEditorCommunicator export const RendererToEditorCommunicatorContextProvider: React.FC = ({ children }) => { const editorOrigin = useOriginFromConfig(ORIGIN_TYPE.EDITOR) - const communicator = useMemo(() => { - const newCommunicator = new RendererToEditorCommunicator() - newCommunicator.setMessageTarget(window.parent, editorOrigin) - return newCommunicator - }, [editorOrigin]) + const communicator = useMemo(() => new RendererToEditorCommunicator(), []) useEffect(() => { const currentCommunicator = communicator + currentCommunicator.setMessageTarget(window.parent, editorOrigin) + currentCommunicator.registerEventListener() currentCommunicator.enableCommunication() currentCommunicator.sendMessageToOtherSide({ type: CommunicationMessageType.RENDERER_READY }) return () => currentCommunicator?.unregisterEventListener() - }, [communicator]) + }, [communicator, editorOrigin]) /** * Provides a {@link RendererToEditorCommunicator renderer to editor communicator} for the child components via Context. diff --git a/src/components/editor-page/renderer-pane/render-iframe.tsx b/src/components/editor-page/renderer-pane/render-iframe.tsx index db2657267..3de12e0f8 100644 --- a/src/components/editor-page/renderer-pane/render-iframe.tsx +++ b/src/components/editor-page/renderer-pane/render-iframe.tsx @@ -58,13 +58,7 @@ export const RenderIframe: React.FC = ({ const onIframeLoad = useForceRenderPageUrlOnIframeLoadCallback(frameReference, rendererOrigin, resetRendererReady) const [frameHeight, setFrameHeight] = useState(0) - useEffect( - () => () => { - iframeCommunicator.unregisterEventListener() - setRendererStatus(false) - }, - [iframeCommunicator] - ) + useEffect(() => () => setRendererStatus(false), [iframeCommunicator]) useEffect(() => { if (!rendererReady) { diff --git a/src/components/markdown-renderer/markdown-extension/link-replacer/jump-anchor.tsx b/src/components/markdown-renderer/markdown-extension/link-replacer/jump-anchor.tsx index e90b29340..6c8c8a12c 100644 --- a/src/components/markdown-renderer/markdown-extension/link-replacer/jump-anchor.tsx +++ b/src/components/markdown-renderer/markdown-extension/link-replacer/jump-anchor.tsx @@ -14,7 +14,14 @@ export interface JumpAnchorProps extends AllHTMLAttributes { export const JumpAnchor: React.FC = ({ jumpTargetId, children, ...props }) => { const jumpToTargetId = useCallback( (event: React.MouseEvent): void => { - document.getElementById(jumpTargetId)?.scrollIntoView({ behavior: 'smooth' }) + const intoViewElement = document.getElementById(jumpTargetId) + const scrollElement = document.querySelector('[data-scroll-element]') + if (!intoViewElement || !scrollElement) { + return + } + //It would be much easier to use scrollIntoView here but since the code mirror also uses smooth scroll and bugs like + // https://stackoverflow.com/a/63563437/13103995 exist, we must use scrollTo. + scrollElement.scrollTo({ behavior: 'smooth', top: intoViewElement.offsetTop }) event.preventDefault() }, [jumpTargetId] diff --git a/src/components/render-page/hooks/sync-scroll/use-document-sync-scrolling.ts b/src/components/render-page/hooks/sync-scroll/use-document-sync-scrolling.ts index d06584860..89f4948a3 100644 --- a/src/components/render-page/hooks/sync-scroll/use-document-sync-scrolling.ts +++ b/src/components/render-page/hooks/sync-scroll/use-document-sync-scrolling.ts @@ -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] => { const [lineMarks, setLineMarks] = useState() 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]) } diff --git a/src/components/render-page/hooks/sync-scroll/use-on-user-scroll.ts b/src/components/render-page/hooks/sync-scroll/use-on-user-scroll.ts index c868aa6f6..32e460b45 100644 --- a/src/components/render-page/hooks/sync-scroll/use-on-user-scroll.ts +++ b/src/components/render-page/hooks/sync-scroll/use-on-user-scroll.ts @@ -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, + scrollContainer: React.RefObject, onScroll: ((newScrollState: ScrollState) => void) | undefined -): (() => void) => { +): React.UIEventHandler => { return useCallback(() => { if (!scrollContainer.current || !lineMarks || lineMarks.length === 0 || !onScroll) { return diff --git a/src/components/render-page/markdown-document.tsx b/src/components/render-page/markdown-document.tsx index be69dd96a..52be031cd 100644 --- a/src/components/render-page/markdown-document.tsx +++ b/src/components/render-page/markdown-document.tsx @@ -84,7 +84,9 @@ export const MarkdownDocument: React.FC = ({ className={`${styles['markdown-document']} ${additionalOuterContainerClasses ?? ''}`} ref={internalDocumentRenderPaneRef} onScroll={onUserScroll} - onMouseEnter={onMakeScrollSource}> + data-scroll-element={true} + onMouseEnter={onMakeScrollSource} + onTouchStart={onMakeScrollSource}>
diff --git a/src/components/render-page/window-post-message-communicator/hooks/use-send-to-renderer.ts b/src/components/render-page/window-post-message-communicator/hooks/use-send-to-renderer.ts index 2037ef98c..caf9d5529 100644 --- a/src/components/render-page/window-post-message-communicator/hooks/use-send-to-renderer.ts +++ b/src/components/render-page/window-post-message-communicator/hooks/use-send-to-renderer.ts @@ -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> + message: undefined | Extract> ): void => { const iframeCommunicator = useEditorToRendererCommunicator() diff --git a/src/components/render-page/window-post-message-communicator/window-post-message-communicator.ts b/src/components/render-page/window-post-message-communicator/window-post-message-communicator.ts index b0cef7678..f2e2f5c66 100644 --- a/src/components/render-page/window-post-message-communicator/window-post-message-communicator.ts +++ b/src/components/render-page/window-post-message-communicator/window-post-message-communicator.ts @@ -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 = ( - values: Extract> + values: Extract> ) => void export type MaybeHandler = Handler | undefined @@ -21,7 +22,7 @@ export type HandlerMap = Partial<{ [key in MESSAGE_TYPE]: MaybeHandler }> -export interface PostMessage { +export interface MessagePayload { type: MESSAGE_TYPE } @@ -31,16 +32,17 @@ export interface PostMessage { export abstract class WindowPostMessageCommunicator< RECEIVE_TYPE extends string, SEND_TYPE extends string, - MESSAGES extends PostMessage + MESSAGES extends MessagePayload > { private messageTarget?: Window private targetOrigin?: string private communicationEnabled: boolean - private handlers: HandlerMap = {} - private log + private readonly handlers: HandlerMap = {} + 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>): void { + public sendMessageToOtherSide(message: Extract>): 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>): boolean | undefined { - const data = event.data + protected handleEvent(event: MessageEvent>): 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>) - 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): void { + return Optional.ofNullable>(this.handlers[payload.type]).ifPresent((handler) => { + this.log.debug('Received event', payload) + handler(payload as Extract>) + }) } } diff --git a/src/pages/n/[id].tsx b/src/pages/n/[id].tsx index b47c205ff..0e3158137 100644 --- a/src/pages/n/[id].tsx +++ b/src/pages/n/[id].tsx @@ -4,173 +4,18 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import React, { Suspense, useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { useTranslation } from 'react-i18next' -import { useApplyDarkMode } from '../../hooks/common/use-apply-dark-mode' -import { setCheckboxInMarkdownContent, updateNoteTitleByFirstHeading } from '../../redux/note-details/methods' -import { MotdModal } from '../../components/common/motd-modal/motd-modal' -import { ShowIf } from '../../components/common/show-if/show-if' -import { ErrorWhileLoadingNoteAlert } from '../../components/document-read-only-page/ErrorWhileLoadingNoteAlert' -import { LoadingNoteAlert } from '../../components/document-read-only-page/LoadingNoteAlert' -import { AppBar, AppBarMode } from '../../components/editor-page/app-bar/app-bar' -import { EditorMode } from '../../components/editor-page/app-bar/editor-view-mode' -import { useLoadNoteFromServer } from '../../components/editor-page/hooks/useLoadNoteFromServer' -import { useViewModeShortcuts } from '../../components/editor-page/hooks/useViewModeShortcuts' -import { Sidebar } from '../../components/editor-page/sidebar/sidebar' -import { Splitter } from '../../components/editor-page/splitter/splitter' -import type { DualScrollState, ScrollState } from '../../components/editor-page/synced-scroll/scroll-props' -import { RendererType } from '../../components/render-page/window-post-message-communicator/rendering-message' -import { useEditorModeFromUrl } from '../../components/editor-page/hooks/useEditorModeFromUrl' -import { UiNotifications } from '../../components/notifications/ui-notifications' -import { useUpdateLocalHistoryEntry } from '../../components/editor-page/hooks/useUpdateLocalHistoryEntry' -import { useApplicationState } from '../../hooks/common/use-application-state' -import { EditorDocumentRenderer } from '../../components/editor-page/editor-document-renderer/editor-document-renderer' +import React from 'react' import { EditorToRendererCommunicatorContextProvider } from '../../components/editor-page/render-context/editor-to-renderer-communicator-context-provider' -import { Logger } from '../../utils/logger' -import { NoteType } from '../../redux/note-details/types/note-details' import type { NextPage } from 'next' -import { isClientSideRendering } from '../../utils/is-client-side-rendering' -import { LoadingScreen } from '../../components/application-loader/loading-screen/loading-screen' -import { NoteAndAppTitleHead } from '../../components/layout/note-and-app-title-head' -import equal from 'fast-deep-equal' - -const EditorPane = React.lazy(() => import('../../components/editor-page/editor-pane/editor-pane')) - -export interface EditorPagePathParams { - id: string -} - -export enum ScrollSource { - EDITOR, - RENDERER -} - -const log = new Logger('EditorPage') +import { EditorPageContent } from '../../components/editor-page/editor-page-content' /** * Renders a page that is used by the user to edit markdown notes. It contains the editor and a renderer. */ export const EditorPage: NextPage = () => { - useTranslation() - const scrollSource = useRef(ScrollSource.EDITOR) - const editorMode: EditorMode = useApplicationState((state) => state.editorConfig.editorMode) - const editorSyncScroll: boolean = useApplicationState((state) => state.editorConfig.syncScroll) - - const [scrollState, setScrollState] = useState(() => ({ - editorScrollState: { firstLineInView: 1, scrolledPercentage: 0 }, - rendererScrollState: { firstLineInView: 1, scrolledPercentage: 0 } - })) - - const onMarkdownRendererScroll = useCallback( - (newScrollState: ScrollState) => { - if (scrollSource.current === ScrollSource.RENDERER && editorSyncScroll) { - setScrollState((old) => { - const newState: DualScrollState = { - editorScrollState: newScrollState, - rendererScrollState: old.rendererScrollState - } - return equal(newState, old) ? old : newState - }) - } - }, - [editorSyncScroll] - ) - - useEffect(() => { - log.debug('New scroll state', scrollState, scrollSource) - }, [scrollState]) - - const onEditorScroll = useCallback( - (newScrollState: ScrollState) => { - if (scrollSource.current === ScrollSource.EDITOR && editorSyncScroll) { - setScrollState((old) => { - const newState: DualScrollState = { - rendererScrollState: newScrollState, - editorScrollState: old.editorScrollState - } - return equal(newState, old) ? old : newState - }) - } - }, - [editorSyncScroll] - ) - - useViewModeShortcuts() - useApplyDarkMode() - useEditorModeFromUrl() - - const [error, loading] = useLoadNoteFromServer() - - useUpdateLocalHistoryEntry(!error && !loading) - - const setRendererToScrollSource = useCallback(() => { - if (scrollSource.current !== ScrollSource.RENDERER) { - scrollSource.current = ScrollSource.RENDERER - log.debug('Make renderer scroll source') - } - }, []) - - const setEditorToScrollSource = useCallback(() => { - if (scrollSource.current !== ScrollSource.EDITOR) { - scrollSource.current = ScrollSource.EDITOR - log.debug('Make editor scroll source') - } - }, []) - - const leftPane = useMemo( - () => - isClientSideRendering() ? ( - }> - - - ) : undefined, - [onEditorScroll, scrollState.editorScrollState, setEditorToScrollSource] - ) - const noteType: NoteType = useApplicationState((state) => state.noteDetails.frontmatter.type) - - const rightPane = useMemo( - () => ( - - ), - [noteType, onMarkdownRendererScroll, scrollState.rendererScrollState, setRendererToScrollSource] - ) - return ( - - - -
- -
- - -
- -
- - -
-
-
+
) }