Просмотр исходного кода

Merge branch 'master' into imprv/gfm-table-performance

Yuki Takei 2 лет назад
Родитель
Сommit
9f0beb3459

+ 2 - 2
apps/app/src/components/Common/Dropdown/PageItemControl.tsx

@@ -1,6 +1,6 @@
 import React, { useState, useCallback, useEffect } from 'react';
 
-import { modifiersForRightAlign } from '@growi/ui/dist/utils';
+import { getCustomModifiers } from '@growi/ui/dist/utils';
 import { useTranslation } from 'next-i18next';
 import {
   Dropdown, DropdownMenu, DropdownToggle, DropdownItem,
@@ -250,7 +250,7 @@ const PageItemControlDropdownMenu = React.memo((props: DropdownMenuProps): JSX.E
     <DropdownMenu
       data-testid="page-item-control-menu"
       right={alignRight}
-      modifiers={modifiersForRightAlign}
+      modifiers={getCustomModifiers(alignRight)}
       container="body"
       persist={!!alignRight}
       style={{ zIndex: 1055 }} /* make it larger than $zindex-modal of bootstrap */

+ 25 - 7
apps/app/src/components/PageComment/CommentEditor.tsx

@@ -11,19 +11,18 @@ import * as toastr from 'toastr';
 
 import { apiPostForm } from '~/client/util/apiv1-client';
 import { IEditorMethods } from '~/interfaces/editor-methods';
-import { useSWRxPageComment } from '~/stores/comment';
+import { useSWRxPageComment, useSWRxEditingCommentsNum } from '~/stores/comment';
 import {
   useCurrentUser, useIsSlackConfigured,
   useIsUploadableFile, useIsUploadableImage,
 } from '~/stores/context';
-import { useSWRxSlackChannels, useIsSlackEnabled } from '~/stores/editor';
+import { useSWRxSlackChannels, useIsSlackEnabled, useIsEnabledUnsavedWarning } from '~/stores/editor';
 import { useCurrentPagePath } from '~/stores/page';
 
 import { CustomNavTab } from '../CustomNavigation/CustomNav';
 import { NotAvailableForGuest } from '../NotAvailableForGuest';
 import Editor from '../PageEditor/Editor';
 
-
 import { CommentPreview } from './CommentPreview';
 
 import styles from './CommentEditor.module.scss';
@@ -70,12 +69,18 @@ export const CommentEditor = (props: CommentEditorProps): JSX.Element => {
   const { data: isSlackConfigured } = useIsSlackConfigured();
   const { data: isUploadableFile } = useIsUploadableFile();
   const { data: isUploadableImage } = useIsUploadableImage();
+  const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
+  const {
+    increment: incrementEditingCommentsNum,
+    decrement: decrementEditingCommentsNum,
+  } = useSWRxEditingCommentsNum();
 
   const [isReadyToUse, setIsReadyToUse] = useState(!isForNewComment);
   const [comment, setComment] = useState(commentBody ?? '');
   const [activeTab, setActiveTab] = useState('comment_editor');
   const [error, setError] = useState();
   const [slackChannels, setSlackChannels] = useState<string>('');
+  const [incremented, setIncremented] = useState(false);
 
   const editorRef = useRef<IEditorMethods>(null);
 
@@ -102,7 +107,7 @@ export const CommentEditor = (props: CommentEditorProps): JSX.Element => {
     setSlackChannels(slackChannels);
   }, []);
 
-  const initializeEditor = useCallback(() => {
+  const initializeEditor = useCallback(async() => {
     setComment('');
     setActiveTab('comment_editor');
     setError(undefined);
@@ -110,7 +115,11 @@ export const CommentEditor = (props: CommentEditorProps): JSX.Element => {
     // reset value
     if (editorRef.current == null) { return }
     editorRef.current.setValue('');
-  }, [initializeSlackEnabled]);
+    const editingCommentsNum = await decrementEditingCommentsNum();
+    if (editingCommentsNum === 0) {
+      mutateIsEnabledUnsavedWarning(false); // must be after clearing comment or else onChange will override bool
+    }
+  }, [initializeSlackEnabled, mutateIsEnabledUnsavedWarning, decrementEditingCommentsNum]);
 
   const cancelButtonClickedHandler = useCallback(() => {
     // change state to not ready
@@ -119,10 +128,12 @@ export const CommentEditor = (props: CommentEditorProps): JSX.Element => {
       setIsReadyToUse(false);
     }
 
+    initializeEditor();
+
     if (onCancelButtonClicked != null) {
       onCancelButtonClicked();
     }
-  }, [isForNewComment, onCancelButtonClicked]);
+  }, [isForNewComment, onCancelButtonClicked, initializeEditor]);
 
   const postCommentHandler = useCallback(async() => {
     try {
@@ -237,7 +248,14 @@ export const CommentEditor = (props: CommentEditorProps): JSX.Element => {
     );
   }, []);
 
-  const onChangeHandler = useCallback((newValue: string) => setComment(newValue), []);
+  const onChangeHandler = useCallback((newValue: string, isClean: boolean) => {
+    setComment(newValue);
+    if (!isClean && !incremented) {
+      incrementEditingCommentsNum();
+      setIncremented(true);
+    }
+    mutateIsEnabledUnsavedWarning(!isClean);
+  }, [mutateIsEnabledUnsavedWarning, incrementEditingCommentsNum, incremented]);
 
   const renderReady = () => {
     const commentPreview = getCommentHtml();

+ 20 - 0
apps/app/src/stores/comment.tsx

@@ -5,6 +5,8 @@ import { apiGet, apiPost } from '~/client/util/apiv1-client';
 
 import { ICommentHasIdList, ICommentPostArgs } from '../interfaces/comment';
 
+import { useStaticSWR } from './use-static-swr';
+
 type IResponseComment = {
   comments: ICommentHasIdList,
   ok: boolean,
@@ -62,3 +64,21 @@ export const useSWRxPageComment = (pageId: Nullable<string>): SWRResponse<IComme
     post,
   };
 };
+
+type EditingCommentsNumOperation = {
+  increment(): Promise<number | undefined>,
+  decrement(): Promise<number | undefined>,
+}
+
+export const useSWRxEditingCommentsNum = (): SWRResponse<number, Error> & EditingCommentsNumOperation => {
+  const swrResponse = useStaticSWR<number, Error>('editingCommentsNum', undefined, { fallbackData: 0 });
+
+  return {
+    ...swrResponse,
+    increment: () => swrResponse.mutate((swrResponse.data ?? 0) + 1),
+    decrement: () => {
+      const newValue = (swrResponse.data ?? 0) - 1;
+      return swrResponse.mutate(Math.max(0, newValue));
+    },
+  };
+};

+ 3 - 3
packages/ui/src/interfaces/popper-data.ts

@@ -14,11 +14,11 @@ export interface PopperData {
   };
 }
 
-export interface Modifiers {
-  applyStyle: {
+export interface CustomModifiers {
+  applyStyle?: {
     enabled: boolean
   }
-  computeStyle: {
+  computeStyle?: {
     enabled: boolean,
     fn: (data: PopperData) => PopperData
   }

+ 10 - 2
packages/ui/src/utils/reactstrap-modifiers.ts

@@ -1,8 +1,8 @@
-import { PopperData, Modifiers } from '~/interfaces/popper-data';
+import { PopperData, CustomModifiers } from '~/interfaces/popper-data';
 
 // Conditional modifiers
 // To prevent flickering. only happened when `right` is true and persist props should be enabled
-export const modifiersForRightAlign: Modifiers = {
+const modifiersForRightAlign: CustomModifiers = {
   applyStyle: {
     enabled: true,
   },
@@ -24,3 +24,11 @@ export const modifiersForRightAlign: Modifiers = {
   },
   preventOverflow: { boundariesElement: 'viewport' },
 };
+
+export const getCustomModifiers = (alignRight?: boolean): CustomModifiers => {
+  return (
+    alignRight
+      ? modifiersForRightAlign
+      : { preventOverflow: { boundariesElement: 'viewport' } }
+  );
+};