|
|
@@ -5,11 +5,11 @@ import React, {
|
|
|
import EventEmitter from 'events';
|
|
|
import nodePath from 'path';
|
|
|
|
|
|
-import { Compartment } from '@codemirror/state';
|
|
|
import { keymap } from '@codemirror/view';
|
|
|
import type { IPageHasId } from '@growi/core';
|
|
|
import { pathUtils } from '@growi/core/dist/utils';
|
|
|
import { CodeMirrorEditorContainer, useCodeMirrorEditorMain } from '@growi/editor';
|
|
|
+import { ReactCodeMirrorProps } from '@uiw/react-codemirror';
|
|
|
import detectIndent from 'detect-indent';
|
|
|
import { useTranslation } from 'next-i18next';
|
|
|
import { useRouter } from 'next/router';
|
|
|
@@ -18,7 +18,6 @@ import { throttle, debounce } from 'throttle-debounce';
|
|
|
import { useUpdateStateAfterSave, useSaveOrUpdate } from '~/client/services/page-operation';
|
|
|
import { apiGet, apiPostForm } from '~/client/util/apiv1-client';
|
|
|
import { toastError, toastSuccess } from '~/client/util/toastr';
|
|
|
-import { IEditorMethods } from '~/interfaces/editor-methods';
|
|
|
import { OptionsToSave } from '~/interfaces/page-operation';
|
|
|
import { SocketEventName } from '~/interfaces/websocket';
|
|
|
import {
|
|
|
@@ -58,7 +57,6 @@ import loggerFactory from '~/utils/logger';
|
|
|
import Preview from './Preview';
|
|
|
import scrollSyncHelper from './ScrollSyncHelper';
|
|
|
|
|
|
-
|
|
|
import '@growi/editor/dist/style.css';
|
|
|
|
|
|
|
|
|
@@ -116,19 +114,16 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
|
|
|
const { mutate: mutateRemoteRevisionLastUpdatedAt } = useRemoteRevisionLastUpdatedAt();
|
|
|
const { mutate: mutateRemoteRevisionLastUpdateUser } = useRemoteRevisionLastUpdateUser();
|
|
|
|
|
|
- const { data: codemirrorEditor } = useCodeMirrorEditorMain(codeMirrorEditorContainerRef.current);
|
|
|
- const {
|
|
|
- appendExtension,
|
|
|
- initDoc, getDoc,
|
|
|
- focus: focusToEditor, setCaretLine,
|
|
|
- } = codemirrorEditor ?? {};
|
|
|
+ const { data: socket } = useGlobalSocket();
|
|
|
|
|
|
const { data: rendererOptions } = usePreviewOptions();
|
|
|
const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
|
|
|
- const saveOrUpdate = useSaveOrUpdate();
|
|
|
+ const { mutate: mutateIsConflict } = useIsConflict();
|
|
|
|
|
|
+ const saveOrUpdate = useSaveOrUpdate();
|
|
|
const updateStateAfterSave = useUpdateStateAfterSave(pageId, { supressEditingMarkdownMutation: true });
|
|
|
|
|
|
+
|
|
|
// TODO: remove workaround
|
|
|
// for https://redmine.weseek.co.jp/issues/125923
|
|
|
const [createdPageRevisionIdWithAttachment, setCreatedPageRevisionIdWithAttachment] = useState();
|
|
|
@@ -155,10 +150,26 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
|
|
|
}, [isNotFound, currentPathname, editingMarkdown, isEnabledAttachTitleHeader, templateBodyData]);
|
|
|
|
|
|
const [markdownToPreview, setMarkdownToPreview] = useState<string>(initialValue);
|
|
|
-
|
|
|
- const { data: socket } = useGlobalSocket();
|
|
|
-
|
|
|
- const { mutate: mutateIsConflict } = useIsConflict();
|
|
|
+ const setMarkdownPreviewWithDebounce = useMemo(() => debounce(100, throttle(150, (value: string) => {
|
|
|
+ setMarkdownToPreview(value);
|
|
|
+ })), []);
|
|
|
+ const mutateIsEnabledUnsavedWarningWithDebounce = useMemo(() => debounce(600, throttle(900, (value: string) => {
|
|
|
+ // Displays an unsaved warning alert
|
|
|
+ mutateIsEnabledUnsavedWarning(value !== initialValue);
|
|
|
+ })), [initialValue, mutateIsEnabledUnsavedWarning]);
|
|
|
+
|
|
|
+ const useCodeMirrorEditorMainProps = useMemo<ReactCodeMirrorProps>(() => {
|
|
|
+ return {
|
|
|
+ onChange: (value) => {
|
|
|
+ setMarkdownPreviewWithDebounce(value);
|
|
|
+ mutateIsEnabledUnsavedWarningWithDebounce(value);
|
|
|
+ },
|
|
|
+ };
|
|
|
+ }, [mutateIsEnabledUnsavedWarningWithDebounce, setMarkdownPreviewWithDebounce]);
|
|
|
+ const { data: codeMirrorEditor } = useCodeMirrorEditorMain(
|
|
|
+ codeMirrorEditorContainerRef.current,
|
|
|
+ useCodeMirrorEditorMainProps,
|
|
|
+ );
|
|
|
|
|
|
|
|
|
const checkIsConflict = useCallback((data) => {
|
|
|
@@ -202,17 +213,6 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
|
|
|
return optionsToSave;
|
|
|
}, [grantData, isSlackEnabled, pageTags]);
|
|
|
|
|
|
- const setMarkdownWithDebounce = useMemo(() => debounce(100, throttle(150, (value: string, isClean: boolean) => {
|
|
|
- setMarkdownToPreview(value);
|
|
|
-
|
|
|
- // Displays an unsaved warning alert
|
|
|
- mutateIsEnabledUnsavedWarning(!isClean);
|
|
|
- })), [mutateIsEnabledUnsavedWarning]);
|
|
|
-
|
|
|
-
|
|
|
- const markdownChangedHandler = useCallback((value: string, isClean: boolean): void => {
|
|
|
- setMarkdownWithDebounce(value, isClean);
|
|
|
- }, [setMarkdownWithDebounce]);
|
|
|
|
|
|
const save = useCallback(async(opts?: {slackChannels: string, overwriteScopesOfDescendants?: boolean}): Promise<IPageHasId | null> => {
|
|
|
if (currentPathname == null || optionsToSave == null) {
|
|
|
@@ -226,7 +226,7 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
|
|
|
mutateWaitingSaveProcessing(true);
|
|
|
|
|
|
const { page } = await saveOrUpdate(
|
|
|
- getDoc?.() ?? '',
|
|
|
+ codeMirrorEditor?.getDoc() ?? '',
|
|
|
{ pageId, path: currentPagePath || currentPathname, revisionId: currentRevisionId },
|
|
|
options,
|
|
|
);
|
|
|
@@ -252,7 +252,7 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
|
|
|
}
|
|
|
|
|
|
}, [
|
|
|
- getDoc,
|
|
|
+ codeMirrorEditor,
|
|
|
currentPathname, optionsToSave, grantData, isSlackEnabled, saveOrUpdate, pageId,
|
|
|
currentPagePath, currentRevisionId,
|
|
|
mutateWaitingSaveProcessing, mutateRemotePageId, mutateRemoteRevisionId, mutateRemoteRevisionLastUpdatedAt, mutateRemoteRevisionLastUpdateUser,
|
|
|
@@ -316,7 +316,7 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
|
|
|
formData.append('page_id', pageId);
|
|
|
}
|
|
|
if (pageId == null) {
|
|
|
- formData.append('page_body', getDoc?.() ?? '');
|
|
|
+ formData.append('page_body', codeMirrorEditor?.getDoc() ?? '');
|
|
|
}
|
|
|
|
|
|
res = await apiPostForm('/attachments.add', formData);
|
|
|
@@ -353,7 +353,7 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
|
|
|
// refs: https://redmine.weseek.co.jp/issues/126528
|
|
|
// editorRef.current.terminateUploadingState();
|
|
|
}
|
|
|
- }, [currentPagePath, mutateCurrentPage, mutateCurrentPageId, mutateIsLatestRevision, pageId]);
|
|
|
+ }, [codeMirrorEditor, currentPagePath, mutateCurrentPage, mutateCurrentPageId, mutateIsLatestRevision, pageId]);
|
|
|
|
|
|
|
|
|
const scrollPreviewByEditorLine = useCallback((line: number) => {
|
|
|
@@ -472,20 +472,20 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
|
|
|
return;
|
|
|
}
|
|
|
// markdownToSave.current = initialValue;
|
|
|
- initDoc?.(initialValue);
|
|
|
+ codeMirrorEditor?.initDoc(initialValue);
|
|
|
setMarkdownToPreview(initialValue);
|
|
|
mutateIsEnabledUnsavedWarning(false);
|
|
|
- }, [initDoc, initialValue, mutateIsEnabledUnsavedWarning]);
|
|
|
+ }, [codeMirrorEditor, initialValue, mutateIsEnabledUnsavedWarning]);
|
|
|
|
|
|
// initial caret line
|
|
|
useEffect(() => {
|
|
|
- setCaretLine?.();
|
|
|
- }, [setCaretLine]);
|
|
|
+ codeMirrorEditor?.setCaretLine();
|
|
|
+ }, [codeMirrorEditor]);
|
|
|
|
|
|
// set handler to set caret line
|
|
|
useEffect(() => {
|
|
|
const handler = (line) => {
|
|
|
- setCaretLine?.(line);
|
|
|
+ codeMirrorEditor?.setCaretLine(line);
|
|
|
|
|
|
if (previewRef.current != null) {
|
|
|
scrollSyncHelper.scrollPreview(previewRef.current, line);
|
|
|
@@ -496,7 +496,7 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
|
|
|
return function cleanup() {
|
|
|
globalEmitter.removeListener('setCaretLine', handler);
|
|
|
};
|
|
|
- }, [setCaretLine]);
|
|
|
+ }, [codeMirrorEditor]);
|
|
|
|
|
|
// set handler to save and return to View
|
|
|
useEffect(() => {
|
|
|
@@ -509,29 +509,29 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
|
|
|
|
|
|
// set handler to save with shortcut key
|
|
|
useEffect(() => {
|
|
|
- const compartment = new Compartment();
|
|
|
const extension = keymap.of([
|
|
|
{
|
|
|
key: 'Mod-s',
|
|
|
preventDefault: true,
|
|
|
run: () => {
|
|
|
saveWithShortcut();
|
|
|
- return false;
|
|
|
+ return true;
|
|
|
},
|
|
|
},
|
|
|
]);
|
|
|
|
|
|
- const cleanupFunction = appendExtension?.(extension, compartment);
|
|
|
+ const cleanupFunction = codeMirrorEditor?.appendExtension(extension);
|
|
|
|
|
|
return cleanupFunction;
|
|
|
- }, [appendExtension, saveWithShortcut]);
|
|
|
+
|
|
|
+ }, [codeMirrorEditor, saveWithShortcut]);
|
|
|
|
|
|
// set handler to focus
|
|
|
useLayoutEffect(() => {
|
|
|
if (editorMode === EditorMode.Editor) {
|
|
|
- focusToEditor?.();
|
|
|
+ codeMirrorEditor?.focus();
|
|
|
}
|
|
|
- }, [editorMode, focusToEditor]);
|
|
|
+ }, [codeMirrorEditor, editorMode]);
|
|
|
|
|
|
// Detect indent size from contents (only when users are allowed to change it)
|
|
|
useEffect(() => {
|
|
|
@@ -552,9 +552,9 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
|
|
|
// when transitioning to a different page, if the initialValue is the same,
|
|
|
// UnControlled CodeMirror value does not reset, so explicitly set the value to initialValue
|
|
|
const onRouterChangeComplete = useCallback(() => {
|
|
|
- initDoc?.(initialValue);
|
|
|
- setCaretLine?.();
|
|
|
- }, [initDoc, initialValue, setCaretLine]);
|
|
|
+ codeMirrorEditor?.initDoc(initialValue);
|
|
|
+ codeMirrorEditor?.setCaretLine();
|
|
|
+ }, [codeMirrorEditor, initialValue]);
|
|
|
|
|
|
useEffect(() => {
|
|
|
router.events.on('routeChangeComplete', onRouterChangeComplete);
|