From 5f27996ed0e7e60b984ced4bb99e021ca68e423b Mon Sep 17 00:00:00 2001 From: Tilman Vatteroth Date: Sun, 16 Jul 2023 21:27:32 +0200 Subject: [PATCH] wip Signed-off-by: Tilman Vatteroth --- .../use-sync-to-redux-extension.ts | 27 +++++++++++++ .../editor-page/editor-pane/editor-pane.tsx | 13 ++++--- .../hooks/yjs/use-bind-y-text-to-redux.ts | 22 ----------- .../src/hooks/common/use-deferred-state.ts | 39 +++++++++++++++++++ ...te-markdown-content-without-frontmatter.ts | 8 ++-- 5 files changed, 79 insertions(+), 30 deletions(-) create mode 100644 frontend/src/components/editor-page/editor-pane/codemirror-extensions/use-sync-to-redux-extension.ts delete mode 100644 frontend/src/components/editor-page/editor-pane/hooks/yjs/use-bind-y-text-to-redux.ts create mode 100644 frontend/src/hooks/common/use-deferred-state.ts diff --git a/frontend/src/components/editor-page/editor-pane/codemirror-extensions/use-sync-to-redux-extension.ts b/frontend/src/components/editor-page/editor-pane/codemirror-extensions/use-sync-to-redux-extension.ts new file mode 100644 index 000000000..3a8a250ea --- /dev/null +++ b/frontend/src/components/editor-page/editor-pane/codemirror-extensions/use-sync-to-redux-extension.ts @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { useMemo } from 'react' +import { EditorView } from '@codemirror/view' +import { setNoteContent } from '../../../../redux/note-details/methods' + +/** + * Syncs the CodeMirror content to the redux store. + * + * @return the codemirror extension that updates the redux state + */ +export const useSyncToReduxExtension = () => { + return useMemo( + () => + EditorView.updateListener.of((update) => { + if (!update.docChanged) { + return + } + setNoteContent(update.state.sliceDoc()) + }), + [] + ) +} diff --git a/frontend/src/components/editor-page/editor-pane/editor-pane.tsx b/frontend/src/components/editor-page/editor-pane/editor-pane.tsx index eea665e24..808695f2a 100644 --- a/frontend/src/components/editor-page/editor-pane/editor-pane.tsx +++ b/frontend/src/components/editor-page/editor-pane/editor-pane.tsx @@ -24,7 +24,6 @@ import { useApplyScrollState } from './hooks/use-apply-scroll-state' import { useCursorActivityCallback } from './hooks/use-cursor-activity-callback' import { useDisconnectOnUserLoginStatusChange } from './hooks/use-disconnect-on-user-login-status-change' import { useUpdateCodeMirrorReference } from './hooks/use-update-code-mirror-reference' -import { useBindYTextToRedux } from './hooks/yjs/use-bind-y-text-to-redux' import { useCodeMirrorYjsExtension } from './hooks/yjs/use-code-mirror-yjs-extension' import { useOnMetadataUpdated } from './hooks/yjs/use-on-metadata-updated' import { useOnNoteDeleted } from './hooks/yjs/use-on-note-deleted' @@ -43,6 +42,7 @@ import { lintGutter } from '@codemirror/lint' import { oneDark } from '@codemirror/theme-one-dark' import ReactCodeMirror from '@uiw/react-codemirror' import React, { useEffect, useMemo } from 'react' +import { useSyncToReduxExtension } from './codemirror-extensions/use-sync-to-redux-extension' export type EditorPaneProps = ScrollProps @@ -83,12 +83,14 @@ export const EditorPane: React.FC = ({ scrollState, onScroll, o useOnMetadataUpdated(messageTransporter) useOnNoteDeleted(messageTransporter) - useBindYTextToRedux(realtimeDoc) useReceiveRealtimeUsers(messageTransporter) useSendRealtimeActivity(messageTransporter) + const syncToReduxExtension = useSyncToReduxExtension() + const extensions = useMemo( () => [ + syncToReduxExtension, linterExtension, lintGutter(), markdown({ @@ -107,17 +109,18 @@ export const EditorPane: React.FC = ({ scrollState, onScroll, o spellCheckExtension ], [ + syncToReduxExtension, linterExtension, remoteCursorsExtension, - autoCompletionExtension, + lineWrappingExtension, editorScrollExtension, tablePasteExtensions, fileInsertExtension, + autoCompletionExtension, cursorActivityExtension, updateViewContextExtension, yjsExtension, - spellCheckExtension, - lineWrappingExtension + spellCheckExtension ] ) diff --git a/frontend/src/components/editor-page/editor-pane/hooks/yjs/use-bind-y-text-to-redux.ts b/frontend/src/components/editor-page/editor-pane/hooks/yjs/use-bind-y-text-to-redux.ts deleted file mode 100644 index 6b4518a00..000000000 --- a/frontend/src/components/editor-page/editor-pane/hooks/yjs/use-bind-y-text-to-redux.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { setNoteContent } from '../../../../../redux/note-details/methods' -import type { RealtimeDoc } from '@hedgedoc/commons' -import { useEffect } from 'react' - -/** - * One-Way-synchronizes the text of the markdown content channel from the given {@link RealtimeDoc realtime doc} into the global application state. - * - * @param realtimeDoc The {@link RealtimeDoc realtime doc} that contains the markdown content - */ -export const useBindYTextToRedux = (realtimeDoc: RealtimeDoc): void => { - useEffect(() => { - const yText = realtimeDoc.getMarkdownContentChannel() - const yTextCallback = () => setNoteContent(yText.toString()) - yText.observe(yTextCallback) - return () => yText.unobserve(yTextCallback) - }, [realtimeDoc]) -} diff --git a/frontend/src/hooks/common/use-deferred-state.ts b/frontend/src/hooks/common/use-deferred-state.ts new file mode 100644 index 000000000..df27305b8 --- /dev/null +++ b/frontend/src/hooks/common/use-deferred-state.ts @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { useCallback, useEffect, useRef, useState } from 'react' +import { useInterval } from 'react-use' + +/** + * Takes a value that changes often and outputs the last value that hasn't been changed in the last interval. + * + * @param value The value to defer + * @param initialValue The initial value that is used until the first update + * @param checkInterval The interval in ms that is used to check for updates. Default is 200ms. + * @return The slowed down value + */ +export const useDeferredState = (value: T, initialValue: T, checkInterval = 200): T => { + const valueRef = useRef(initialValue) + const lastTimestamp = useRef(0) + const [deferredValue, setDeferredValue] = useState(initialValue) + + useEffect(() => { + valueRef.current = value + lastTimestamp.current = new Date().getTime() + }, [value]) + + useInterval( + useCallback(() => { + const currentTimeStamp = new Date().getTime() + if (currentTimeStamp - lastTimestamp.current >= checkInterval) { + setDeferredValue(valueRef.current) + } + }, [checkInterval]), + checkInterval + ) + + return deferredValue +} diff --git a/frontend/src/hooks/common/use-trimmed-note-markdown-content-without-frontmatter.ts b/frontend/src/hooks/common/use-trimmed-note-markdown-content-without-frontmatter.ts index 26f27a21b..94db5b473 100644 --- a/frontend/src/hooks/common/use-trimmed-note-markdown-content-without-frontmatter.ts +++ b/frontend/src/hooks/common/use-trimmed-note-markdown-content-without-frontmatter.ts @@ -6,6 +6,7 @@ import { useFrontendConfig } from '../../components/common/frontend-config-context/use-frontend-config' import { useApplicationState } from './use-application-state' import { useMemo } from 'react' +import { useDeferredState } from './use-deferred-state' /** * Returns the markdown content from the global application state trimmed to the maximal note length and without the frontmatter lines. @@ -28,7 +29,8 @@ export const useTrimmedNoteMarkdownContentWithoutFrontmatter = (): string[] => { } }, [markdownContent, maxLength]) - return useMemo(() => { - return trimmedLines.slice(lineOffset) - }, [lineOffset, trimmedLines]) + return useDeferredState( + useMemo(() => trimmedLines.slice(lineOffset), [lineOffset, trimmedLines]), + [] + ) }