Răsfoiți Sursa

Merge branch 'master' into imprv/116499-determine-page-grant-considering-wiki-mode-when-creating-a-new-page-by-uploading-attachments

Shun Miyazawa 3 ani în urmă
părinte
comite
a76ba8aec2

+ 19 - 9
packages/app/src/components/PageEditor.tsx

@@ -16,7 +16,7 @@ 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, toastWarning } from '~/client/util/toastr';
+import { toastError, toastSuccess } from '~/client/util/toastr';
 import { IEditorMethods } from '~/interfaces/editor-methods';
 import { OptionsToSave } from '~/interfaces/page-operation';
 import { SocketEventName } from '~/interfaces/websocket';
@@ -35,6 +35,12 @@ import {
   useCurrentPagePath, useSWRMUTxCurrentPage, useSWRxCurrentPage, useSWRxTagsInfo,
 } from '~/stores/page';
 import { mutatePageTree } from '~/stores/page-listing';
+import {
+  useRemoteRevisionId,
+  useRemoteRevisionBody,
+  useRemoteRevisionLastUpdatedAt,
+  useRemoteRevisionLastUpdateUser,
+} from '~/stores/remote-latest-page';
 import { usePreviewOptions } from '~/stores/renderer';
 import {
   EditorMode,
@@ -92,6 +98,10 @@ const PageEditor = React.memo((): JSX.Element => {
   const { data: isUploadableFile } = useIsUploadableFile();
   const { data: isUploadableImage } = useIsUploadableImage();
   const { data: conflictDiffModalStatus, close: closeConflictDiffModal } = useConflictDiffModal();
+  const { mutate: mutateRemotePageId } = useRemoteRevisionId();
+  const { mutate: mutateRemoteRevisionId } = useRemoteRevisionBody();
+  const { mutate: mutateRemoteRevisionLastUpdatedAt } = useRemoteRevisionLastUpdatedAt();
+  const { mutate: mutateRemoteRevisionLastUpdateUser } = useRemoteRevisionLastUpdateUser();
 
   const { data: rendererOptions, mutate: mutateRendererOptions } = usePreviewOptions();
   const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
@@ -218,18 +228,18 @@ const PageEditor = React.memo((): JSX.Element => {
       logger.error('failed to save', error);
       toastError(error);
       if (error.code === 'conflict') {
-        toastWarning('(TBD) resolve conflict');
-        // pageContainer.setState({
-        //   remoteRevisionId: error.data.revisionId,
-        //   remoteRevisionBody: error.data.revisionBody,
-        //   remoteRevisionUpdateAt: error.data.createdAt,
-        //   lastUpdateUser: error.data.user,
-        // });
+        mutateRemotePageId(error.data.revisionId);
+        mutateRemoteRevisionId(error.data.revisionBody);
+        mutateRemoteRevisionLastUpdatedAt(error.data.createdAt);
+        mutateRemoteRevisionLastUpdateUser(error.data.user);
       }
       return null;
     }
 
-  }, [currentPathname, optionsToSave, grantData, isSlackEnabled, saveOrUpdate, pageId, currentPagePath, currentRevisionId]);
+  }, [
+    currentPathname, optionsToSave, grantData, isSlackEnabled, saveOrUpdate, pageId,
+    currentPagePath, currentRevisionId, mutateRemotePageId, mutateRemoteRevisionId, mutateRemoteRevisionLastUpdatedAt, mutateRemoteRevisionLastUpdateUser,
+  ]);
 
   const saveAndReturnToViewHandler = useCallback(async(opts: {slackChannels: string, overwriteScopesOfDescendants?: boolean}) => {
     if (editorMode !== EditorMode.Editor) {

+ 9 - 8
packages/app/src/components/PageEditor/CodeMirrorEditor.jsx

@@ -11,10 +11,9 @@ import { throttle, debounce } from 'throttle-debounce';
 import urljoin from 'url-join';
 
 import InterceptorManager from '~/services/interceptor-manager';
-import { useHandsontableModal, useDrawioModal } from '~/stores/modal';
+import { useHandsontableModal, useDrawioModal, useTemplateModal } from '~/stores/modal';
 import loggerFactory from '~/utils/logger';
 
-import { TemplateModal } from '../TemplateModal';
 import { UncontrolledCodeMirror } from '../UncontrolledCodeMirror';
 
 import AbstractEditor from './AbstractEditor';
@@ -873,7 +872,8 @@ class CodeMirrorEditor extends AbstractEditor {
   }
 
   showTemplateModal() {
-    this.setState({ isTemplateModalOpened: true });
+    const onSubmit = templateText => this.setValue(templateText);
+    this.props.onClickTemplateBtn(onSubmit);
   }
 
   // fold draw.io section (``` drawio ~ ```)
@@ -1158,11 +1158,6 @@ class CodeMirrorEditor extends AbstractEditor {
           ref={this.linkEditModal}
           onSave={(linkText) => { return markdownLinkUtil.replaceFocusedMarkdownLinkWithEditor(this.getCodeMirror(), linkText) }}
         />
-        <TemplateModal
-          isOpen={this.state.isTemplateModalOpened}
-          onClose={() => this.setState({ isTemplateModalOpened: false })}
-          onSubmit={templateText => this.setValue(templateText) }
-        />
       </div>
     );
   }
@@ -1187,6 +1182,7 @@ const CodeMirrorEditorMemoized = memo(CodeMirrorEditor);
 const CodeMirrorEditorFc = React.forwardRef((props, ref) => {
   const { open: openDrawioModal } = useDrawioModal();
   const { open: openHandsontableModal } = useHandsontableModal();
+  const { open: openTemplateModal } = useTemplateModal();
 
   const openDrawioModalHandler = useCallback((drawioMxFile, onSave) => {
     openDrawioModal(drawioMxFile, onSave);
@@ -1196,11 +1192,16 @@ const CodeMirrorEditorFc = React.forwardRef((props, ref) => {
     openHandsontableModal(markdownTable, editor, autoFormatMarkdownTable);
   }, [openHandsontableModal]);
 
+  const openTemplateModalHandler = useCallback((onSubmit) => {
+    openTemplateModal(onSubmit);
+  }, [openTemplateModal]);
+
   return (
     <CodeMirrorEditorMemoized
       ref={ref}
       onClickDrawioBtn={openDrawioModalHandler}
       onClickTableBtn={openTableModalHandler}
+      onClickTemplateBtn={openTemplateModalHandler}
       {...props}
     />
   );

+ 13 - 18
packages/app/src/components/TemplateModal.tsx

@@ -9,6 +9,7 @@ import {
   ModalFooter,
 } from 'reactstrap';
 
+import { useTemplateModal } from '~/stores/modal';
 import { usePreviewOptions } from '~/stores/renderer';
 import { useTemplates } from '~/stores/template';
 
@@ -40,17 +41,10 @@ const TemplateRadioButton = ({ template, onChange, isSelected }: TemplateRadioBu
   );
 };
 
-
-type Props = {
-  isOpen: boolean,
-  onClose: () => void,
-  onSubmit?: (markdown: string) => void,
-}
-
-export const TemplateModal = (props: Props): JSX.Element => {
+export const TemplateModal = (): JSX.Element => {
   const { t } = useTranslation();
 
-  const { isOpen, onClose, onSubmit } = props;
+  const { data: templateModalStatus, close } = useTemplateModal();
 
   const { data: rendererOptions } = usePreviewOptions();
   const { data: templates } = useTemplates();
@@ -58,22 +52,23 @@ export const TemplateModal = (props: Props): JSX.Element => {
   const [selectedTemplate, setSelectedTemplate] = useState<ITemplate>();
 
   const submitHandler = useCallback((template?: ITemplate) => {
-    if (onSubmit == null || template == null) {
-      onClose();
+    if (templateModalStatus == null) { return }
+    if (templateModalStatus.onSubmit == null || template == null) {
+      close();
       return;
     }
 
-    onSubmit(template.markdown);
-    onClose();
-  }, [onClose, onSubmit]);
+    templateModalStatus.onSubmit(template.markdown);
+    close();
+  }, [close, templateModalStatus]);
 
-  if (templates == null) {
+  if (templates == null || templateModalStatus == null) {
     return <></>;
   }
 
   return (
-    <Modal className="link-edit-modal" isOpen={isOpen} toggle={onClose} size="lg" autoFocus={false}>
-      <ModalHeader tag="h4" toggle={onClose} className="bg-primary text-light">
+    <Modal className="link-edit-modal" isOpen={templateModalStatus.isOpened} toggle={close} size="lg" autoFocus={false}>
+      <ModalHeader tag="h4" toggle={close} className="bg-primary text-light">
         Template
       </ModalHeader>
 
@@ -105,7 +100,7 @@ export const TemplateModal = (props: Props): JSX.Element => {
 
       </ModalBody>
       <ModalFooter>
-        <button type="button" className="btn btn-sm btn-outline-secondary mx-1" onClick={onClose}>
+        <button type="button" className="btn btn-sm btn-outline-secondary mx-1" onClick={close}>
           {t('Cancel')}
         </button>
         <button type="submit" className="btn btn-sm btn-primary mx-1" onClick={() => submitHandler(selectedTemplate)} disabled={selectedTemplate == null}>

+ 2 - 0
packages/app/src/pages/[[...path]].page.tsx

@@ -73,6 +73,7 @@ const GrowiSubNavigationSwitcher = dynamic<GrowiSubNavigationSwitcherProps>(() =
   .then(mod => mod.GrowiSubNavigationSwitcher), { ssr: false });
 const DrawioModal = dynamic(() => import('../components/PageEditor/DrawioModal').then(mod => mod.DrawioModal), { ssr: false });
 const HandsontableModal = dynamic(() => import('../components/PageEditor/HandsontableModal').then(mod => mod.HandsontableModal), { ssr: false });
+const TemplateModal = dynamic(() => import('../components/TemplateModal').then(mod => mod.TemplateModal), { ssr: false });
 const PageStatusAlert = dynamic(() => import('../components/PageStatusAlert').then(mod => mod.PageStatusAlert), { ssr: false });
 
 const logger = loggerFactory('growi:pages:all');
@@ -353,6 +354,7 @@ Page.getLayout = function getLayout(page: React.ReactElement<Props>) {
       <DescendantsPageListModal />
       <DrawioModal />
       <HandsontableModal />
+      <TemplateModal />
     </>
   );
 };

+ 29 - 0
packages/app/src/stores/modal.tsx

@@ -579,3 +579,32 @@ export const useConflictDiffModal = (): SWRResponse<ConflictDiffModalStatus, Err
     },
   });
 };
+
+
+/*
+ * TemplateModal
+ */
+type TemplateModalStatus = {
+  isOpened: boolean,
+  onSubmit?: (templateText: string) => void
+}
+
+type TemplateModalUtils = {
+  open(onSubmit: (templateText: string) => void): void,
+  close(): void,
+}
+
+export const useTemplateModal = (): SWRResponse<TemplateModalStatus, Error> & TemplateModalUtils => {
+
+  const initialStatus: TemplateModalStatus = { isOpened: false };
+  const swrResponse = useStaticSWR<TemplateModalStatus, Error>('templateModal', undefined, { fallbackData: initialStatus });
+
+  return Object.assign(swrResponse, {
+    open: (onSubmit: (templateText: string) => void) => {
+      swrResponse.mutate({ isOpened: true, onSubmit });
+    },
+    close: () => {
+      swrResponse.mutate({ isOpened: false });
+    },
+  });
+};

+ 8 - 0
packages/app/src/styles/theme/_apply-colors.scss

@@ -350,6 +350,14 @@ ul.pagination {
   }
 }
 
+.grw-page-accessories-modal,.grw-descendants-page-list-modal {
+  .modal-header {
+    .close {
+      color: #{hsl.contrast(var(--bgcolor-global))};
+    }
+  }
+}
+
 .grw-custom-nav-tab {
   .nav-item {
     &:hover,