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

refactor LinkEditModal with swr hook

yukendev 2 лет назад
Родитель
Сommit
6c5f636e98

+ 28 - 8
apps/app/src/components/PageEditor/CodeMirrorEditor.jsx

@@ -9,7 +9,9 @@ import { throttle, debounce } from 'throttle-debounce';
 import urljoin from 'url-join';
 import urljoin from 'url-join';
 
 
 import InterceptorManager from '~/services/interceptor-manager';
 import InterceptorManager from '~/services/interceptor-manager';
-import { useHandsontableModal, useDrawioModal, useTemplateModal } from '~/stores/modal';
+import {
+  useHandsontableModal, useDrawioModal, useTemplateModal, useLinkEditModal,
+} from '~/stores/modal';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 import { UncontrolledCodeMirror } from '../UncontrolledCodeMirror';
 import { UncontrolledCodeMirror } from '../UncontrolledCodeMirror';
@@ -149,13 +151,14 @@ class CodeMirrorEditor extends AbstractEditor {
     this.makeHeaderHandler = this.makeHeaderHandler.bind(this);
     this.makeHeaderHandler = this.makeHeaderHandler.bind(this);
     // TODO: re-impl with https://redmine.weseek.co.jp/issues/107248
     // TODO: re-impl with https://redmine.weseek.co.jp/issues/107248
     // this.showGridEditorHandler = this.showGridEditorHandler.bind(this);
     // this.showGridEditorHandler = this.showGridEditorHandler.bind(this);
-    this.showLinkEditHandler = this.showLinkEditHandler.bind(this);
+    // this.showLinkEditHandler = this.showLinkEditHandler.bind(this);
 
 
     this.foldDrawioSection = this.foldDrawioSection.bind(this);
     this.foldDrawioSection = this.foldDrawioSection.bind(this);
     this.clickDrawioIconHandler = this.clickDrawioIconHandler.bind(this);
     this.clickDrawioIconHandler = this.clickDrawioIconHandler.bind(this);
     this.clickTableIconHandler = this.clickTableIconHandler.bind(this);
     this.clickTableIconHandler = this.clickTableIconHandler.bind(this);
 
 
     this.showTemplateModal = this.showTemplateModal.bind(this);
     this.showTemplateModal = this.showTemplateModal.bind(this);
+    this.showLinkEditModal = this.showLinkEditModal.bind(this);
 
 
   }
   }
 
 
@@ -846,15 +849,26 @@ class CodeMirrorEditor extends AbstractEditor {
   //   this.gridEditModal.current.show(geu.getGridHtml(this.getCodeMirror()));
   //   this.gridEditModal.current.show(geu.getGridHtml(this.getCodeMirror()));
   // }
   // }
 
 
-  showLinkEditHandler() {
-    this.linkEditModal.current.show(markdownLinkUtil.getMarkdownLink(this.getCodeMirror()));
-  }
+  // showLinkEditHandler() {
+  //   this.linkEditModal.current.show(markdownLinkUtil.getMarkdownLink(this.getCodeMirror()));
+  // }
 
 
   showTemplateModal() {
   showTemplateModal() {
     const onSubmit = templateText => this.setValue(templateText);
     const onSubmit = templateText => this.setValue(templateText);
     this.props.onClickTemplateBtn(onSubmit);
     this.props.onClickTemplateBtn(onSubmit);
   }
   }
 
 
+  showLinkEditModal() {
+    // TODO: 詳細をあとで実装
+    const onSubmit = (linkText) => {
+      return markdownLinkUtil.replaceFocusedMarkdownLinkWithEditor(this.getCodeMirror(), linkText);
+    };
+
+    const defaultMarkdownLink = markdownLinkUtil.getMarkdownLink(this.getCodeMirror());
+
+    this.props.onClickLinkEditBtn(defaultMarkdownLink, onSubmit);
+  }
+
   // fold draw.io section (``` drawio ~ ```)
   // fold draw.io section (``` drawio ~ ```)
   foldDrawioSection() {
   foldDrawioSection() {
     const editor = this.getCodeMirror();
     const editor = this.getCodeMirror();
@@ -985,7 +999,7 @@ class CodeMirrorEditor extends AbstractEditor {
         color={null}
         color={null}
         size="sm"
         size="sm"
         title="Link"
         title="Link"
-        onClick={this.showLinkEditHandler}
+        onClick={this.showLinkEditModal} // ここでLinkEditModalを表示させている
       >
       >
         <EditorIcon icon="Link" />
         <EditorIcon icon="Link" />
       </Button>,
       </Button>,
@@ -1126,10 +1140,10 @@ class CodeMirrorEditor extends AbstractEditor {
         />
         />
          */}
          */}
 
 
-        <LinkEditModal
+        {/* <LinkEditModal
           ref={this.linkEditModal}
           ref={this.linkEditModal}
           onSave={(linkText) => { return markdownLinkUtil.replaceFocusedMarkdownLinkWithEditor(this.getCodeMirror(), linkText) }}
           onSave={(linkText) => { return markdownLinkUtil.replaceFocusedMarkdownLinkWithEditor(this.getCodeMirror(), linkText) }}
-        />
+        /> */}
       </div>
       </div>
     );
     );
   }
   }
@@ -1154,6 +1168,7 @@ const CodeMirrorEditorFc = React.forwardRef((props, ref) => {
   const { open: openDrawioModal } = useDrawioModal();
   const { open: openDrawioModal } = useDrawioModal();
   const { open: openHandsontableModal } = useHandsontableModal();
   const { open: openHandsontableModal } = useHandsontableModal();
   const { open: openTemplateModal } = useTemplateModal();
   const { open: openTemplateModal } = useTemplateModal();
+  const { open: openLinkEditModal } = useLinkEditModal();
 
 
   const openDrawioModalHandler = useCallback((drawioMxFile, onSave) => {
   const openDrawioModalHandler = useCallback((drawioMxFile, onSave) => {
     openDrawioModal(drawioMxFile, onSave);
     openDrawioModal(drawioMxFile, onSave);
@@ -1167,12 +1182,17 @@ const CodeMirrorEditorFc = React.forwardRef((props, ref) => {
     openTemplateModal(onSubmit);
     openTemplateModal(onSubmit);
   }, [openTemplateModal]);
   }, [openTemplateModal]);
 
 
+  const openLinkEditModalHandler = useCallback((defaultMarkdownLink, onSubmit) => {
+    openLinkEditModal(defaultMarkdownLink, onSubmit);
+  }, [openLinkEditModal]);
+
   return (
   return (
     <CodeMirrorEditorMemoized
     <CodeMirrorEditorMemoized
       ref={ref}
       ref={ref}
       onClickDrawioBtn={openDrawioModalHandler}
       onClickDrawioBtn={openDrawioModalHandler}
       onClickTableBtn={openTableModalHandler}
       onClickTableBtn={openTableModalHandler}
       onClickTemplateBtn={openTemplateModalHandler}
       onClickTemplateBtn={openTemplateModalHandler}
+      onClickLinkEditBtn={openLinkEditModalHandler}
       {...props}
       {...props}
     />
     />
   );
   );

+ 49 - 34
apps/app/src/components/PageEditor/LinkEditModal.tsx

@@ -1,4 +1,4 @@
-import React, { forwardRef, useImperativeHandle, useState } from 'react';
+import React, { useEffect, useState, useCallback } from 'react';
 
 
 import path from 'path';
 import path from 'path';
 
 
@@ -16,6 +16,7 @@ import validator from 'validator';
 
 
 import Linker from '~/client/models/Linker';
 import Linker from '~/client/models/Linker';
 import { apiv3Get } from '~/client/util/apiv3-client';
 import { apiv3Get } from '~/client/util/apiv3-client';
+import { useLinkEditModal } from '~/stores/modal';
 import { useCurrentPagePath } from '~/stores/page';
 import { useCurrentPagePath } from '~/stores/page';
 import { usePreviewOptions } from '~/stores/renderer';
 import { usePreviewOptions } from '~/stores/renderer';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
@@ -31,23 +32,20 @@ import styles from './LinkEditPreview.module.scss';
 
 
 const logger = loggerFactory('growi:components:LinkEditModal');
 const logger = loggerFactory('growi:components:LinkEditModal');
 
 
-type Props = {
-  onSave: (linkText: string) => void
-}
-
-export const LinkEditModal = forwardRef((props: Props, ref): JSX.Element => {
+export const LinkEditModal = (): JSX.Element => {
   const { t } = useTranslation();
   const { t } = useTranslation();
   const { data: currentPath } = useCurrentPagePath();
   const { data: currentPath } = useCurrentPagePath();
   const { data: rendererOptions } = usePreviewOptions();
   const { data: rendererOptions } = usePreviewOptions();
+  const { data: linkEditModalStatus, close } = useLinkEditModal();
 
 
-  useImperativeHandle(ref, () => ({
-    show: (defaultMarkdownLink: Linker) => {
-      // eslint-disable-next-line @typescript-eslint/no-use-before-define
-      show(defaultMarkdownLink);
-    },
-  }));
+  // useImperativeHandle(ref, () => ({
+  //   show: (defaultMarkdownLink: Linker) => {
+  //     // eslint-disable-next-line @typescript-eslint/no-use-before-define
+  //     show(defaultMarkdownLink);
+  //   },
+  // }));
 
 
-  const [isOpen, setIsOpen] = useState<boolean>(false);
+  // const [isOpen, setIsOpen] = useState<boolean>(false);
   const [isUseRelativePath, setIsUseRelativePath] = useState<boolean>(false);
   const [isUseRelativePath, setIsUseRelativePath] = useState<boolean>(false);
   const [isUsePermanentLink, setIsUsePermanentLink] = useState<boolean>(false);
   const [isUsePermanentLink, setIsUsePermanentLink] = useState<boolean>(false);
   const [linkInputValue, setLinkInputValue] = useState<string>('');
   const [linkInputValue, setLinkInputValue] = useState<string>('');
@@ -59,11 +57,11 @@ export const LinkEditModal = forwardRef((props: Props, ref): JSX.Element => {
   const [permalink, setPermalink] = useState<string>('');
   const [permalink, setPermalink] = useState<string>('');
   const [isPreviewOpen, setIsPreviewOpen] = useState<boolean>(false);
   const [isPreviewOpen, setIsPreviewOpen] = useState<boolean>(false);
 
 
-  const getRootPath = (type: string) => {
+  const getRootPath = useCallback((type: string) => {
     // rootPaths of md link and pukiwiki link are different
     // rootPaths of md link and pukiwiki link are different
     if (currentPath == null) return '';
     if (currentPath == null) return '';
     return type === Linker.types.markdownLink ? path.dirname(currentPath) : currentPath;
     return type === Linker.types.markdownLink ? path.dirname(currentPath) : currentPath;
-  };
+  }, [currentPath]);
 
 
   // parse link, link is ...
   // parse link, link is ...
   // case-1. url of this growi's page (ex. 'http://localhost:3000/hoge/fuga')
   // case-1. url of this growi's page (ex. 'http://localhost:3000/hoge/fuga')
@@ -71,7 +69,7 @@ export const LinkEditModal = forwardRef((props: Props, ref): JSX.Element => {
   // case-3. relative path of this growi's page (ex. '../fuga', 'hoge')
   // case-3. relative path of this growi's page (ex. '../fuga', 'hoge')
   // case-4. external link (ex. 'https://growi.org')
   // case-4. external link (ex. 'https://growi.org')
   // case-5. the others (ex. '')
   // case-5. the others (ex. '')
-  const parseLinkAndSetState = (link: string, type: string) => {
+  const parseLinkAndSetState = useCallback((link: string, type: string) => {
     // create url from link, add dummy origin if link is not valid url.
     // create url from link, add dummy origin if link is not valid url.
     // ex-1. link = 'https://growi.org/' -> url = 'https://growi.org/' (case-1,4)
     // ex-1. link = 'https://growi.org/' -> url = 'https://growi.org/' (case-1,4)
     // ex-2. link = 'hoge' -> url = 'http://example.com/hoge' (case-2,3,5)
     // ex-2. link = 'hoge' -> url = 'http://example.com/hoge' (case-2,3,5)
@@ -100,25 +98,38 @@ export const LinkEditModal = forwardRef((props: Props, ref): JSX.Element => {
 
 
     setLinkInputValue(reshapedLink);
     setLinkInputValue(reshapedLink);
     setIsUseRelativePath(isUseRelativePath);
     setIsUseRelativePath(isUseRelativePath);
-  };
+  }, [getRootPath]);
 
 
-  const show = (defaultMarkdownLink: Linker) => {
-    // if defaultMarkdownLink is null, set default value in inputs.
-    const { label = '', link = '' } = defaultMarkdownLink;
-    const { type = Linker.types.markdownLink } = defaultMarkdownLink;
+  useEffect(() => {
+    if (linkEditModalStatus?.defaultMarkdownLink == null) { return }
+    const { label = '', link = '' } = linkEditModalStatus.defaultMarkdownLink;
+    const { type = Linker.types.markdownLink } = linkEditModalStatus.defaultMarkdownLink;
 
 
     parseLinkAndSetState(link, type);
     parseLinkAndSetState(link, type);
-
-    setIsOpen(true);
     setLabelInputValue(label);
     setLabelInputValue(label);
     setIsUsePermanentLink(false);
     setIsUsePermanentLink(false);
     setPermalink('');
     setPermalink('');
     setLinkerType(type);
     setLinkerType(type);
-  };
 
 
-  const hide = () => {
-    setIsOpen(false);
-  };
+  }, [linkEditModalStatus?.defaultMarkdownLink, parseLinkAndSetState]);
+
+  // const show = (defaultMarkdownLink: Linker) => {
+  //   // if defaultMarkdownLink is null, set default value in inputs.
+  //   const { label = '', link = '' } = defaultMarkdownLink;
+  //   const { type = Linker.types.markdownLink } = defaultMarkdownLink;
+
+  //   parseLinkAndSetState(link, type);
+
+  //   // setIsOpen(true);
+  //   setLabelInputValue(label);
+  //   setIsUsePermanentLink(false);
+  //   setPermalink('');
+  //   setLinkerType(type);
+  // };
+
+  // const hide = () => {
+  //   setIsOpen(false);
+  // };
 
 
   const toggleIsUseRelativePath = () => {
   const toggleIsUseRelativePath = () => {
     if (!linkInputValue.startsWith('/') || linkerType === Linker.types.growiLink) {
     if (!linkInputValue.startsWith('/') || linkerType === Linker.types.growiLink) {
@@ -238,11 +249,12 @@ export const LinkEditModal = forwardRef((props: Props, ref): JSX.Element => {
   const save = () => {
   const save = () => {
     const linker = generateLink();
     const linker = generateLink();
 
 
-    if (props.onSave != null) {
-      props.onSave(linker.generateMarkdownText() ?? '');
+    if (linkEditModalStatus?.onSave != null) {
+      linkEditModalStatus.onSave(linker.generateMarkdownText() ?? '');
     }
     }
 
 
-    hide();
+    // hide();
+    close();
   };
   };
 
 
   const toggleIsPreviewOpen = async() => {
   const toggleIsPreviewOpen = async() => {
@@ -347,10 +359,13 @@ export const LinkEditModal = forwardRef((props: Props, ref): JSX.Element => {
     );
     );
   };
   };
 
 
+  if (linkEditModalStatus == null) {
+    return <></>;
+  }
 
 
   return (
   return (
-    <Modal className="link-edit-modal" isOpen={isOpen} toggle={hide} size="lg" autoFocus={false}>
-      <ModalHeader tag="h4" toggle={hide} className="bg-primary text-light">
+    <Modal className="link-edit-modal" isOpen={linkEditModalStatus.isOpened} toggle={close} size="lg" autoFocus={false}>
+      <ModalHeader tag="h4" toggle={close} className="bg-primary text-light">
         {t('link_edit.edit_link')}
         {t('link_edit.edit_link')}
       </ModalHeader>
       </ModalHeader>
 
 
@@ -370,7 +385,7 @@ export const LinkEditModal = forwardRef((props: Props, ref): JSX.Element => {
       </ModalBody>
       </ModalBody>
       <ModalFooter>
       <ModalFooter>
         { previewError && <span className='text-danger'>{previewError}</span>}
         { previewError && <span className='text-danger'>{previewError}</span>}
-        <button type="button" className="btn btn-sm btn-outline-secondary mx-1" onClick={hide}>
+        <button type="button" className="btn btn-sm btn-outline-secondary mx-1" onClick={close}>
           {t('Cancel')}
           {t('Cancel')}
         </button>
         </button>
         <button type="submit" className="btn btn-sm btn-primary mx-1" onClick={save}>
         <button type="submit" className="btn btn-sm btn-primary mx-1" onClick={save}>
@@ -379,6 +394,6 @@ export const LinkEditModal = forwardRef((props: Props, ref): JSX.Element => {
       </ModalFooter>
       </ModalFooter>
     </Modal>
     </Modal>
   );
   );
-});
+};
 
 
 LinkEditModal.displayName = 'LinkEditModal';
 LinkEditModal.displayName = 'LinkEditModal';

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

@@ -21,6 +21,7 @@ import superjson from 'superjson';
 
 
 import { useCurrentGrowiLayoutFluidClassName, useEditorModeClassName } from '~/client/services/layout';
 import { useCurrentGrowiLayoutFluidClassName, useEditorModeClassName } from '~/client/services/layout';
 import { PageView } from '~/components/Page/PageView';
 import { PageView } from '~/components/Page/PageView';
+import { LinkEditModal } from '~/components/PageEditor/LinkEditModal';
 import { DrawioViewerScript } from '~/components/Script/DrawioViewerScript';
 import { DrawioViewerScript } from '~/components/Script/DrawioViewerScript';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { EditorConfig } from '~/interfaces/editor-settings';
 import type { EditorConfig } from '~/interfaces/editor-settings';
@@ -379,6 +380,7 @@ Page.getLayout = function getLayout(page: React.ReactElement<Props>) {
       <DrawioModal />
       <DrawioModal />
       <HandsontableModal />
       <HandsontableModal />
       <TemplateModal />
       <TemplateModal />
+      <LinkEditModal />
     </>
     </>
   );
   );
 };
 };