Sfoglia il codice sorgente

Merge branch 'master' into imprv/88175-show-delete-modal-with-swr

* master: (60 commits)
  improve null check
  fix lint errors
  move swr related to modal to stores/modal file
  refactoring PageDeleteModal
  refactoring pageCreateeModal
  add title comments for each modal
  refactoring for pageDuplicateModal
  refactoring for PageRenameModal
  add white space
  refactor methods
  move react code back into RFC
  add type
  rename usePagePresentationModalStatus to usePagePresentationModal and use it instead of usePagePresentationModalOpened
  moce codes otu of FC
  refs #88095: fix pagetitle links to parmanent url - If the page path URL is not available due to duplicate page names, use persistent links to process pages with the same name.
  Added limitation
  rename
  imprv
  fix fb
  fix cond
  ...
Mao 4 anni fa
parent
commit
bab4f9d797
32 ha cambiato i file con 400 aggiunte e 338 eliminazioni
  1. 2 0
      packages/app/src/client/base.jsx
  2. 5 0
      packages/app/src/client/services/PageContainer.js
  3. 1 1
      packages/app/src/components/DescendantsPageListModal.tsx
  4. 2 2
      packages/app/src/components/Fab.jsx
  5. 2 2
      packages/app/src/components/Hotkeys/Subscribers/CreatePage.jsx
  6. 1 1
      packages/app/src/components/IdenticalPathPage.tsx
  7. 17 9
      packages/app/src/components/Navbar/GrowiContextualSubNavigation.tsx
  8. 6 4
      packages/app/src/components/Navbar/GrowiNavbar.tsx
  9. 3 2
      packages/app/src/components/Navbar/GrowiNavbarBottom.jsx
  10. 1 1
      packages/app/src/components/Navbar/SubNavButtons.tsx
  11. 2 1
      packages/app/src/components/Page/DisplaySwitcher.tsx
  12. 1 1
      packages/app/src/components/Page/PageManagement.jsx
  13. 1 0
      packages/app/src/components/Page/RevisionBody.jsx
  14. 1 1
      packages/app/src/components/Page/TrashPageAlert.jsx
  15. 1 1
      packages/app/src/components/PageAccessoriesModal.tsx
  16. 4 5
      packages/app/src/components/PageCreateModal.jsx
  17. 13 14
      packages/app/src/components/PageDeleteModal.tsx
  18. 3 4
      packages/app/src/components/PageDuplicateModal.jsx
  19. 8 0
      packages/app/src/components/PageEditor/AbstractEditor.tsx
  20. 2 10
      packages/app/src/components/PageEditor/CodeMirrorEditor.jsx
  21. 15 3
      packages/app/src/components/PageList/PageListItemL.tsx
  22. 6 16
      packages/app/src/components/PagePresentationModal.jsx
  23. 5 4
      packages/app/src/components/PageRenameModal.jsx
  24. 48 5
      packages/app/src/components/SearchPage/SearchResultContent.tsx
  25. 1 1
      packages/app/src/components/Sidebar/PageTree/Item.tsx
  26. 4 5
      packages/app/src/components/Sidebar/PageTree/ItemsTree.tsx
  27. 1 3
      packages/app/src/components/UncontrolledCodeMirror.tsx
  28. 6 0
      packages/app/src/server/service/page-grant.ts
  29. 8 1
      packages/app/src/server/service/page.ts
  30. 1 0
      packages/app/src/server/views/layout/layout.html
  31. 229 0
      packages/app/src/stores/modal.tsx
  32. 0 241
      packages/app/src/stores/ui.tsx

+ 2 - 0
packages/app/src/client/base.jsx

@@ -10,6 +10,7 @@ import PageCreateModal from '../components/PageCreateModal';
 import PageDeleteModal from '../components/PageDeleteModal';
 import PageDeleteModal from '../components/PageDeleteModal';
 import PageDuplicateModal from '../components/PageDuplicateModal';
 import PageDuplicateModal from '../components/PageDuplicateModal';
 import PageRenameModal from '../components/PageRenameModal';
 import PageRenameModal from '../components/PageRenameModal';
+import PagePresentationModal from '../components/PagePresentationModal';
 import PageAccessoriesModal from '../components/PageAccessoriesModal';
 import PageAccessoriesModal from '../components/PageAccessoriesModal';
 
 
 import AppContainer from '~/client/services/AppContainer';
 import AppContainer from '~/client/services/AppContainer';
@@ -48,6 +49,7 @@ const componentMappings = {
   'page-delete-modal': <PageDeleteModal />,
   'page-delete-modal': <PageDeleteModal />,
   'page-duplicate-modal': <PageDuplicateModal />,
   'page-duplicate-modal': <PageDuplicateModal />,
   'page-rename-modal': <PageRenameModal />,
   'page-rename-modal': <PageRenameModal />,
+  'page-presentation-modal': <PagePresentationModal />,
   'page-accessories-modal': <PageAccessoriesModal />,
   'page-accessories-modal': <PageAccessoriesModal />,
   'descendants-page-list-modal': <DescendantsPageListModal />,
   'descendants-page-list-modal': <DescendantsPageListModal />,
 
 

+ 5 - 0
packages/app/src/client/services/PageContainer.js

@@ -459,6 +459,7 @@ export default class PageContainer extends Container {
 
 
     const { pageId, remoteRevisionId, path } = this.state;
     const { pageId, remoteRevisionId, path } = this.state;
     const editorContainer = this.appContainer.getContainer('EditorContainer');
     const editorContainer = this.appContainer.getContainer('EditorContainer');
+    const pageEditor = this.appContainer.getComponentInstance('PageEditor');
     const options = editorContainer.getCurrentOptionsToSave();
     const options = editorContainer.getCurrentOptionsToSave();
     const optionsToSave = Object.assign({}, options);
     const optionsToSave = Object.assign({}, options);
 
 
@@ -467,6 +468,10 @@ export default class PageContainer extends Container {
     editorContainer.clearDraft(path);
     editorContainer.clearDraft(path);
     this.updateStateAfterSave(res.page, res.tags, res.revision, editorMode);
     this.updateStateAfterSave(res.page, res.tags, res.revision, editorMode);
 
 
+    if (pageEditor != null) {
+      pageEditor.updateEditorValue(markdown);
+    }
+
     editorContainer.setState({ tags: res.tags });
     editorContainer.setState({ tags: res.tags });
 
 
     return res;
     return res;

+ 1 - 1
packages/app/src/components/DescendantsPageListModal.tsx

@@ -6,7 +6,7 @@ import {
   Modal, ModalHeader, ModalBody,
   Modal, ModalHeader, ModalBody,
 } from 'reactstrap';
 } from 'reactstrap';
 
 
-import { useDescendantsPageListModal } from '~/stores/ui';
+import { useDescendantsPageListModal } from '~/stores/modal';
 import { useIsSharedUser } from '~/stores/context';
 import { useIsSharedUser } from '~/stores/context';
 
 
 import DescendantsPageList from './DescendantsPageList';
 import DescendantsPageList from './DescendantsPageList';

+ 2 - 2
packages/app/src/components/Fab.jsx

@@ -6,7 +6,7 @@ import loggerFactory from '~/utils/logger';
 
 
 import AppContainer from '~/client/services/AppContainer';
 import AppContainer from '~/client/services/AppContainer';
 
 
-import { useCreateModalStatus } from '~/stores/ui';
+import { usePageCreateModal } from '~/stores/modal';
 import { smoothScrollIntoView } from '~/client/util/smooth-scroll';
 import { smoothScrollIntoView } from '~/client/util/smooth-scroll';
 
 
 import { withUnstatedContainers } from './UnstatedUtils';
 import { withUnstatedContainers } from './UnstatedUtils';
@@ -19,7 +19,7 @@ const Fab = (props) => {
   const { appContainer } = props;
   const { appContainer } = props;
   const { currentUser } = appContainer;
   const { currentUser } = appContainer;
 
 
-  const { open: openCreateModal } = useCreateModalStatus();
+  const { open: openCreateModal } = usePageCreateModal();
 
 
   const [animateClasses, setAnimateClasses] = useState('invisible');
   const [animateClasses, setAnimateClasses] = useState('invisible');
   const [buttonClasses, setButtonClasses] = useState('');
   const [buttonClasses, setButtonClasses] = useState('');

+ 2 - 2
packages/app/src/components/Hotkeys/Subscribers/CreatePage.jsx

@@ -1,11 +1,11 @@
 import React, { useEffect } from 'react';
 import React, { useEffect } from 'react';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 
 
-import { useCreateModalStatus } from '~/stores/ui';
+import { usePageCreateModal } from '~/stores/modal';
 
 
 const CreatePage = React.memo((props) => {
 const CreatePage = React.memo((props) => {
 
 
-  const { open: openCreateModal } = useCreateModalStatus();
+  const { open: openCreateModal } = usePageCreateModal();
 
 
   // setup effect
   // setup effect
   useEffect(() => {
   useEffect(() => {

+ 1 - 1
packages/app/src/components/IdenticalPathPage.tsx

@@ -5,7 +5,7 @@ import { DevidedPagePath } from '@growi/core';
 
 
 import { IPageHasId, IPageWithMeta } from '~/interfaces/page';
 import { IPageHasId, IPageWithMeta } from '~/interfaces/page';
 import { useCurrentPagePath, useIsSharedUser } from '~/stores/context';
 import { useCurrentPagePath, useIsSharedUser } from '~/stores/context';
-import { useDescendantsPageListModal } from '~/stores/ui';
+import { useDescendantsPageListModal } from '~/stores/modal';
 import { useSWRxPageInfoForList } from '~/stores/page';
 import { useSWRxPageInfoForList } from '~/stores/page';
 
 
 import PageListIcon from './Icons/PageListIcon';
 import PageListIcon from './Icons/PageListIcon';

+ 17 - 9
packages/app/src/components/Navbar/GrowiContextualSubNavigation.tsx

@@ -9,9 +9,14 @@ import { withUnstatedContainers } from '../UnstatedUtils';
 import EditorContainer from '~/client/services/EditorContainer';
 import EditorContainer from '~/client/services/EditorContainer';
 import {
 import {
   EditorMode, useDrawerMode, useEditorMode, useIsDeviceSmallerThanMd, useIsAbleToShowPageManagement, useIsAbleToShowTagLabel,
   EditorMode, useDrawerMode, useEditorMode, useIsDeviceSmallerThanMd, useIsAbleToShowPageManagement, useIsAbleToShowTagLabel,
-  useIsAbleToShowPageEditorModeManager, useIsAbleToShowPageAuthors, usePageAccessoriesModal, PageAccessoriesModalContents,
-  usePageDuplicateModalStatus, usePageRenameModalStatus, usePageDeleteModal,
+  useIsAbleToShowPageEditorModeManager, useIsAbleToShowPageAuthors,
 } from '~/stores/ui';
 } from '~/stores/ui';
+import {
+  usePageAccessoriesModal, PageAccessoriesModalContents,
+  usePageDuplicateModal, usePageRenameModal, usePageDeleteModal, usePagePresentationModal,
+} from '~/stores/modal';
+
+
 import {
 import {
   useCurrentCreatedAt, useCurrentUpdatedAt, useCurrentPageId, useRevisionId, useCurrentPagePath,
   useCurrentCreatedAt, useCurrentUpdatedAt, useCurrentPageId, useRevisionId, useCurrentPagePath,
   useCreator, useRevisionAuthor, useCurrentUser, useIsGuestUser, useIsSharedUser, useShareLinkId,
   useCreator, useRevisionAuthor, useCurrentUser, useIsGuestUser, useIsSharedUser, useShareLinkId,
@@ -57,12 +62,15 @@ const AdditionalMenuItems = (props: AdditionalMenuItemsProps): JSX.Element => {
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isSharedUser } = useIsSharedUser();
   const { data: isSharedUser } = useIsSharedUser();
 
 
-  const { open } = usePageAccessoriesModal();
+  const { open: openPresentationModal } = usePagePresentationModal();
+  const { open: openAccessoriesModal } = usePageAccessoriesModal();
+
+  const hrefForPresentationModal = '?presentation=1';
 
 
   return (
   return (
     <>
     <>
       {/* Presentation */}
       {/* Presentation */}
-      <DropdownItem onClick={() => { /* TODO: implement in https://redmine.weseek.co.jp/issues/87672 */ }}>
+      <DropdownItem onClick={() => openPresentationModal(hrefForPresentationModal)}>
         <i className="icon-fw"><PresentationIcon /></i>
         <i className="icon-fw"><PresentationIcon /></i>
         { t('Presentation Mode') }
         { t('Presentation Mode') }
       </DropdownItem>
       </DropdownItem>
@@ -80,7 +88,7 @@ const AdditionalMenuItems = (props: AdditionalMenuItemsProps): JSX.Element => {
         refs: PageAccessoriesModalControl
         refs: PageAccessoriesModalControl
       */}
       */}
       <DropdownItem
       <DropdownItem
-        onClick={() => open(PageAccessoriesModalContents.PageHistory)}
+        onClick={() => openAccessoriesModal(PageAccessoriesModalContents.PageHistory)}
         disabled={isGuestUser || isSharedUser}
         disabled={isGuestUser || isSharedUser}
       >
       >
         <span className="mr-1"><HistoryIcon /></span>
         <span className="mr-1"><HistoryIcon /></span>
@@ -88,14 +96,14 @@ const AdditionalMenuItems = (props: AdditionalMenuItemsProps): JSX.Element => {
       </DropdownItem>
       </DropdownItem>
 
 
       <DropdownItem
       <DropdownItem
-        onClick={() => open(PageAccessoriesModalContents.Attachment)}
+        onClick={() => openAccessoriesModal(PageAccessoriesModalContents.Attachment)}
       >
       >
         <span className="mr-1"><AttachmentIcon /></span>
         <span className="mr-1"><AttachmentIcon /></span>
         {t('attachment_data')}
         {t('attachment_data')}
       </DropdownItem>
       </DropdownItem>
 
 
       <DropdownItem
       <DropdownItem
-        onClick={() => open(PageAccessoriesModalContents.ShareLink)}
+        onClick={() => openAccessoriesModal(PageAccessoriesModalContents.ShareLink)}
         disabled={isGuestUser || isSharedUser || isLinkSharingDisabled}
         disabled={isGuestUser || isSharedUser || isLinkSharingDisabled}
       >
       >
         <span className="mr-1"><ShareLinkIcon /></span>
         <span className="mr-1"><ShareLinkIcon /></span>
@@ -136,8 +144,8 @@ const GrowiContextualSubNavigation = (props) => {
 
 
   const { mutate: mutateSWRTagsInfo, data: tagsInfoData } = useSWRTagsInfo(pageId);
   const { mutate: mutateSWRTagsInfo, data: tagsInfoData } = useSWRTagsInfo(pageId);
 
 
-  const { open: openDuplicateModal } = usePageDuplicateModalStatus();
-  const { open: openRenameModal } = usePageRenameModalStatus();
+  const { open: openDuplicateModal } = usePageDuplicateModal();
+  const { open: openRenameModal } = usePageRenameModal();
   const { open: openDeleteModal } = usePageDeleteModal();
   const { open: openDeleteModal } = usePageDeleteModal();
 
 
   const [isPageTemplateModalShown, setIsPageTempleteModalShown] = useState(false);
   const [isPageTemplateModalShown, setIsPageTempleteModalShown] = useState(false);

+ 6 - 4
packages/app/src/components/Navbar/GrowiNavbar.tsx

@@ -7,8 +7,9 @@ import { UncontrolledTooltip } from 'reactstrap';
 
 
 import AppContainer from '~/client/services/AppContainer';
 import AppContainer from '~/client/services/AppContainer';
 import { IUser } from '~/interfaces/user';
 import { IUser } from '~/interfaces/user';
-import { useIsDeviceSmallerThanMd, useCreateModalStatus } from '~/stores/ui';
-import { useIsSearchPage } from '~/stores/context';
+import { useIsDeviceSmallerThanMd } from '~/stores/ui';
+import { usePageCreateModal } from '~/stores/modal';
+import { useIsSearchPage, useCurrentPagePath } from '~/stores/context';
 
 
 import { withUnstatedContainers } from '../UnstatedUtils';
 import { withUnstatedContainers } from '../UnstatedUtils';
 import GrowiLogo from '../Icons/GrowiLogo';
 import GrowiLogo from '../Icons/GrowiLogo';
@@ -23,7 +24,8 @@ type NavbarRightProps = {
 }
 }
 const NavbarRight: FC<NavbarRightProps> = memo((props: NavbarRightProps) => {
 const NavbarRight: FC<NavbarRightProps> = memo((props: NavbarRightProps) => {
   const { t } = useTranslation();
   const { t } = useTranslation();
-  const { open: openCreateModal } = useCreateModalStatus();
+  const { data: currentPagePath } = useCurrentPagePath();
+  const { open: openCreateModal } = usePageCreateModal();
 
 
   const { currentUser } = props;
   const { currentUser } = props;
 
 
@@ -42,7 +44,7 @@ const NavbarRight: FC<NavbarRightProps> = memo((props: NavbarRightProps) => {
         <button
         <button
           className="px-md-3 nav-link btn-create-page border-0 bg-transparent"
           className="px-md-3 nav-link btn-create-page border-0 bg-transparent"
           type="button"
           type="button"
-          onClick={() => openCreateModal()}
+          onClick={() => openCreateModal(currentPagePath || '')}
         >
         >
           <i className="icon-pencil mr-2"></i>
           <i className="icon-pencil mr-2"></i>
           <span className="d-none d-lg-block">{ t('New') }</span>
           <span className="d-none d-lg-block">{ t('New') }</span>

+ 3 - 2
packages/app/src/components/Navbar/GrowiNavbarBottom.jsx

@@ -2,7 +2,8 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 
 
 
 
-import { useCreateModalStatus, useIsDeviceSmallerThanMd, useDrawerOpened } from '~/stores/ui';
+import { useIsDeviceSmallerThanMd, useDrawerOpened } from '~/stores/ui';
+import { usePageCreateModal } from '~/stores/modal';
 import { useCurrentPagePath, useIsSearchPage } from '~/stores/context';
 import { useCurrentPagePath, useIsSearchPage } from '~/stores/context';
 
 
 import GlobalSearch from './GlobalSearch';
 import GlobalSearch from './GlobalSearch';
@@ -11,7 +12,7 @@ const GrowiNavbarBottom = (props) => {
 
 
   const { data: isDrawerOpened, mutate: mutateDrawerOpened } = useDrawerOpened();
   const { data: isDrawerOpened, mutate: mutateDrawerOpened } = useDrawerOpened();
   const { data: isDeviceSmallerThanMd } = useIsDeviceSmallerThanMd();
   const { data: isDeviceSmallerThanMd } = useIsDeviceSmallerThanMd();
-  const { open: openCreateModal } = useCreateModalStatus();
+  const { open: openCreateModal } = usePageCreateModal();
   const { data: currentPagePath } = useCurrentPagePath();
   const { data: currentPagePath } = useCurrentPagePath();
   const { data: isSearchPage } = useIsSearchPage();
   const { data: isSearchPage } = useIsSearchPage();
 
 

+ 1 - 1
packages/app/src/components/Navbar/SubNavButtons.tsx

@@ -6,7 +6,7 @@ import { useSWRxPageInfo } from '../../stores/page';
 import { useSWRBookmarkInfo } from '../../stores/bookmark';
 import { useSWRBookmarkInfo } from '../../stores/bookmark';
 import { useSWRxUsersList } from '../../stores/user';
 import { useSWRxUsersList } from '../../stores/user';
 import { useIsGuestUser } from '~/stores/context';
 import { useIsGuestUser } from '~/stores/context';
-import { IPageForPageDeleteModal } from '~/stores/ui';
+import { IPageForPageDeleteModal } from '~/stores/modal';
 
 
 import SubscribeButton from '../SubscribeButton';
 import SubscribeButton from '../SubscribeButton';
 import LikeButtons from '../LikeButtons';
 import LikeButtons from '../LikeButtons';

+ 2 - 1
packages/app/src/components/Page/DisplaySwitcher.tsx

@@ -4,7 +4,8 @@ import { TabContent, TabPane } from 'reactstrap';
 
 
 import { pagePathUtils } from '@growi/core';
 import { pagePathUtils } from '@growi/core';
 
 
-import { EditorMode, useEditorMode, useDescendantsPageListModal } from '~/stores/ui';
+import { EditorMode, useEditorMode } from '~/stores/ui';
+import { useDescendantsPageListModal } from '~/stores/modal';
 import {
 import {
   useCurrentPagePath, useIsSharedUser, useIsEditable, useCurrentPageId, useIsUserPage, usePageUser,
   useCurrentPagePath, useIsSharedUser, useIsEditable, useCurrentPageId, useIsUserPage, usePageUser,
 } from '~/stores/context';
 } from '~/stores/context';

+ 1 - 1
packages/app/src/components/Page/PageManagement.jsx

@@ -5,7 +5,7 @@ import { withTranslation } from 'react-i18next';
 import urljoin from 'url-join';
 import urljoin from 'url-join';
 
 
 import { pagePathUtils } from '@growi/core';
 import { pagePathUtils } from '@growi/core';
-import { usePageDeleteModal } from '~/stores/ui';
+import { usePageDeleteModal } from '~/stores/modal';
 
 
 import { withUnstatedContainers } from '../UnstatedUtils';
 import { withUnstatedContainers } from '../UnstatedUtils';
 import AppContainer from '~/client/services/AppContainer';
 import AppContainer from '~/client/services/AppContainer';

+ 1 - 0
packages/app/src/components/Page/RevisionBody.jsx

@@ -64,6 +64,7 @@ export default class RevisionBody extends React.PureComponent {
             this.props.inputRef(elm);
             this.props.inputRef(elm);
           }
           }
         }}
         }}
+        id="wiki"
         className={`wiki ${additionalClassName}`}
         className={`wiki ${additionalClassName}`}
         // eslint-disable-next-line react/no-danger
         // eslint-disable-next-line react/no-danger
         dangerouslySetInnerHTML={this.generateInnerHtml(this.props.html)}
         dangerouslySetInnerHTML={this.generateInnerHtml(this.props.html)}

+ 1 - 1
packages/app/src/components/Page/TrashPageAlert.jsx

@@ -11,7 +11,7 @@ import PutbackPageModal from '../PutbackPageModal';
 import EmptyTrashModal from '../EmptyTrashModal';
 import EmptyTrashModal from '../EmptyTrashModal';
 
 
 import { useCurrentUpdatedAt } from '~/stores/context';
 import { useCurrentUpdatedAt } from '~/stores/context';
-import { usePageDeleteModal } from '~/stores/ui';
+import { usePageDeleteModal } from '~/stores/modal';
 
 
 const TrashPageAlert = (props) => {
 const TrashPageAlert = (props) => {
   const { t, pageContainer } = props;
   const { t, pageContainer } = props;

+ 1 - 1
packages/app/src/components/PageAccessoriesModal.tsx

@@ -7,7 +7,7 @@ import {
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 
 
 import { useIsGuestUser, useIsSharedUser } from '~/stores/context';
 import { useIsGuestUser, useIsSharedUser } from '~/stores/context';
-import { usePageAccessoriesModal, PageAccessoriesModalContents } from '~/stores/ui';
+import { usePageAccessoriesModal, PageAccessoriesModalContents } from '~/stores/modal';
 import AppContainer from '~/client/services/AppContainer';
 import AppContainer from '~/client/services/AppContainer';
 
 
 import HistoryIcon from './Icons/HistoryIcon';
 import HistoryIcon from './Icons/HistoryIcon';

+ 4 - 5
packages/app/src/components/PageCreateModal.jsx

@@ -13,7 +13,8 @@ import { pagePathUtils, pathUtils } from '@growi/core';
 import AppContainer from '~/client/services/AppContainer';
 import AppContainer from '~/client/services/AppContainer';
 import { withUnstatedContainers } from './UnstatedUtils';
 import { withUnstatedContainers } from './UnstatedUtils';
 import { toastError } from '~/client/util/apiNotification';
 import { toastError } from '~/client/util/apiNotification';
-import { useCreateModalStatus, useCreateModalOpened, useCreateModalPath } from '~/stores/ui';
+
+import { usePageCreateModal } from '~/stores/modal';
 
 
 import PagePathAutoComplete from './PagePathAutoComplete';
 import PagePathAutoComplete from './PagePathAutoComplete';
 
 
@@ -24,10 +25,8 @@ const {
 const PageCreateModal = (props) => {
 const PageCreateModal = (props) => {
   const { t, appContainer } = props;
   const { t, appContainer } = props;
 
 
-  const { close: closeCreateModal } = useCreateModalStatus();
-  const { data: isOpened } = useCreateModalOpened();
-  const { data: path } = useCreateModalPath();
-
+  const { data: pageCreateModalData, close: closeCreateModal } = usePageCreateModal();
+  const { isOpened, path } = pageCreateModalData;
 
 
   const config = appContainer.getConfig();
   const config = appContainer.getConfig();
   const isReachable = config.isSearchServiceReachable;
   const isReachable = config.isSearchServiceReachable;

+ 13 - 14
packages/app/src/components/PageDeleteModal.tsx

@@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next';
 
 
 import { apiPost } from '~/client/util/apiv1-client';
 import { apiPost } from '~/client/util/apiv1-client';
 import { apiv3Post } from '~/client/util/apiv3-client';
 import { apiv3Post } from '~/client/util/apiv3-client';
-import { usePageDeleteModal, usePageDeleteModalOpened } from '~/stores/ui';
+import { usePageDeleteModal } from '~/stores/modal';
 
 
 import { IDeleteSinglePageApiv1Result, IDeleteManyPageApiv3Result } from '~/interfaces/page';
 import { IDeleteSinglePageApiv1Result, IDeleteManyPageApiv3Result } from '~/interfaces/page';
 
 
@@ -38,10 +38,9 @@ const PageDeleteModal: FC<Props> = (props: Props) => {
     isDeleteCompletelyModal, isAbleToDeleteCompletely,
     isDeleteCompletelyModal, isAbleToDeleteCompletely,
   } = props;
   } = props;
 
 
-  const { data: deleteModalStatus, close: closeDeleteModal } = usePageDeleteModal();
-  const { data: pageDeleteModalOpened } = usePageDeleteModalOpened();
+  const { data: deleteModalData, close: closeDeleteModal } = usePageDeleteModal();
 
 
-  const isOpened = pageDeleteModalOpened?.isOpend != null ? pageDeleteModalOpened.isOpend : false;
+  const isOpened = deleteModalData?.isOpened ?? false;
 
 
   const [isDeleteRecursively, setIsDeleteRecursively] = useState(true);
   const [isDeleteRecursively, setIsDeleteRecursively] = useState(true);
   const [isDeleteCompletely, setIsDeleteCompletely] = useState(isDeleteCompletelyModal && isAbleToDeleteCompletely);
   const [isDeleteCompletely, setIsDeleteCompletely] = useState(isDeleteCompletelyModal && isAbleToDeleteCompletely);
@@ -62,20 +61,20 @@ const PageDeleteModal: FC<Props> = (props: Props) => {
   }
   }
 
 
   async function deletePage() {
   async function deletePage() {
-    if (deleteModalStatus == null || deleteModalStatus.pages == null) {
+    if (deleteModalData == null || deleteModalData.pages == null) {
       return;
       return;
     }
     }
 
 
     /*
     /*
      * When multiple pages
      * When multiple pages
      */
      */
-    if (deleteModalStatus.pages.length > 1) {
+    if (deleteModalData.pages.length > 1) {
       try {
       try {
         const isRecursively = isDeleteRecursively === true ? true : undefined;
         const isRecursively = isDeleteRecursively === true ? true : undefined;
         const isCompletely = isDeleteCompletely === true ? true : undefined;
         const isCompletely = isDeleteCompletely === true ? true : undefined;
 
 
         const pageIdToRevisionIdMap = {};
         const pageIdToRevisionIdMap = {};
-        deleteModalStatus.pages.forEach((p) => { pageIdToRevisionIdMap[p.pageId] = p.revisionId });
+        deleteModalData.pages.forEach((p) => { pageIdToRevisionIdMap[p.pageId] = p.revisionId });
 
 
         const { data } = await apiv3Post<IDeleteManyPageApiv3Result>('/pages/delete', {
         const { data } = await apiv3Post<IDeleteManyPageApiv3Result>('/pages/delete', {
           pageIdToRevisionIdMap,
           pageIdToRevisionIdMap,
@@ -83,8 +82,8 @@ const PageDeleteModal: FC<Props> = (props: Props) => {
           isCompletely,
           isCompletely,
         });
         });
 
 
-        if (pageDeleteModalOpened != null && pageDeleteModalOpened.onDeleted != null) {
-          pageDeleteModalOpened.onDeleted(data.paths, data.isRecursively, data.isCompletely);
+        if (deleteModalData.onDeleted != null) {
+          deleteModalData.onDeleted(data.paths, data.isRecursively, data.isCompletely);
         }
         }
       }
       }
       catch (err) {
       catch (err) {
@@ -99,7 +98,7 @@ const PageDeleteModal: FC<Props> = (props: Props) => {
         const recursively = isDeleteRecursively === true ? true : undefined;
         const recursively = isDeleteRecursively === true ? true : undefined;
         const completely = isDeleteCompletely === true ? true : undefined;
         const completely = isDeleteCompletely === true ? true : undefined;
 
 
-        const page = deleteModalStatus.pages[0];
+        const page = deleteModalData.pages[0];
 
 
         const { path, isRecursively, isCompletely } = await apiPost('/pages.remove', {
         const { path, isRecursively, isCompletely } = await apiPost('/pages.remove', {
           page_id: page.pageId,
           page_id: page.pageId,
@@ -108,8 +107,8 @@ const PageDeleteModal: FC<Props> = (props: Props) => {
           completely,
           completely,
         }) as IDeleteSinglePageApiv1Result;
         }) as IDeleteSinglePageApiv1Result;
 
 
-        if (pageDeleteModalOpened != null && pageDeleteModalOpened.onDeleted != null) {
-          pageDeleteModalOpened.onDeleted(path, isRecursively, isCompletely);
+        if (deleteModalData.onDeleted != null) {
+          deleteModalData.onDeleted(path, isRecursively, isCompletely);
         }
         }
       }
       }
       catch (err) {
       catch (err) {
@@ -177,8 +176,8 @@ const PageDeleteModal: FC<Props> = (props: Props) => {
   }
   }
 
 
   const renderPagePathsToDelete = () => {
   const renderPagePathsToDelete = () => {
-    if (deleteModalStatus != null && deleteModalStatus.pages != null) {
-      return deleteModalStatus.pages.map(page => <div key={page.pageId}><code>{ page.path }</code></div>);
+    if (deleteModalData != null && deleteModalData.pages != null) {
+      return deleteModalData.pages.map(page => <div key={page.pageId}><code>{ page.path }</code></div>);
     }
     }
     return <></>;
     return <></>;
   };
   };

+ 3 - 4
packages/app/src/components/PageDuplicateModal.jsx

@@ -9,7 +9,7 @@ import { withTranslation } from 'react-i18next';
 import { debounce } from 'throttle-debounce';
 import { debounce } from 'throttle-debounce';
 import { withUnstatedContainers } from './UnstatedUtils';
 import { withUnstatedContainers } from './UnstatedUtils';
 import { toastError } from '~/client/util/apiNotification';
 import { toastError } from '~/client/util/apiNotification';
-import { usePageDuplicateModalStatus, usePageDuplicateModalOpened } from '~/stores/ui';
+import { usePageDuplicateModal } from '~/stores/modal';
 
 
 import AppContainer from '~/client/services/AppContainer';
 import AppContainer from '~/client/services/AppContainer';
 import PagePathAutoComplete from './PagePathAutoComplete';
 import PagePathAutoComplete from './PagePathAutoComplete';
@@ -27,10 +27,9 @@ const PageDuplicateModal = (props) => {
   const config = appContainer.getConfig();
   const config = appContainer.getConfig();
   const isReachable = config.isSearchServiceReachable;
   const isReachable = config.isSearchServiceReachable;
   const { crowi } = appContainer.config;
   const { crowi } = appContainer.config;
-  const { data: pagesDataToDuplicate, close: closeDuplicateModal } = usePageDuplicateModalStatus();
-  const { data: isOpened } = usePageDuplicateModalOpened();
+  const { data: pagesDataToDuplicate, close: closeDuplicateModal } = usePageDuplicateModal();
 
 
-  const { path, pageId } = pagesDataToDuplicate;
+  const { isOpened, path, pageId } = pagesDataToDuplicate;
 
 
   const [pageNameInput, setPageNameInput] = useState(path);
   const [pageNameInput, setPageNameInput] = useState(path);
 
 

+ 8 - 0
packages/app/src/components/PageEditor/AbstractEditor.tsx

@@ -12,6 +12,10 @@ export interface AbstractEditorProps extends ICodeMirror {
   onCtrlEnter?: (event: Event) => void;
   onCtrlEnter?: (event: Event) => void;
 }
 }
 
 
+interface defaultProps {
+  isGfmMode: true,
+}
+
 export default class AbstractEditor<T extends AbstractEditorProps> extends React.Component<T, Record<string, unknown>> {
 export default class AbstractEditor<T extends AbstractEditorProps> extends React.Component<T, Record<string, unknown>> {
 
 
   constructor(props: Readonly<T>) {
   constructor(props: Readonly<T>) {
@@ -29,6 +33,10 @@ export default class AbstractEditor<T extends AbstractEditorProps> extends React
     this.dispatchSave = this.dispatchSave.bind(this);
     this.dispatchSave = this.dispatchSave.bind(this);
   }
   }
 
 
+  public static defaultProps: defaultProps = {
+    isGfmMode: true,
+  };
+
   forceToFocus(): void {}
   forceToFocus(): void {}
 
 
   /**
   /**

+ 2 - 10
packages/app/src/components/PageEditor/CodeMirrorEditor.jsx

@@ -3,8 +3,6 @@ import PropTypes from 'prop-types';
 
 
 import urljoin from 'url-join';
 import urljoin from 'url-join';
 import * as codemirror from 'codemirror';
 import * as codemirror from 'codemirror';
-import { UnControlled as UncontrolledCodeMirror } from 'react-codemirror2';
-
 import { Button } from 'reactstrap';
 import { Button } from 'reactstrap';
 
 
 import { JSHINT } from 'jshint';
 import { JSHINT } from 'jshint';
@@ -13,6 +11,7 @@ import * as loadScript from 'simple-load-script';
 import * as loadCssSync from 'load-css-file';
 import * as loadCssSync from 'load-css-file';
 
 
 import { createValidator } from '@growi/codemirror-textlint';
 import { createValidator } from '@growi/codemirror-textlint';
+import { UncontrolledCodeMirror } from '../UncontrolledCodeMirror';
 import InterceptorManager from '~/services/interceptor-manager';
 import InterceptorManager from '~/services/interceptor-manager';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
@@ -32,7 +31,6 @@ import LinkEditModal from './LinkEditModal';
 import HandsontableModal from './HandsontableModal';
 import HandsontableModal from './HandsontableModal';
 import EditorIcon from './EditorIcon';
 import EditorIcon from './EditorIcon';
 import DrawioModal from './DrawioModal';
 import DrawioModal from './DrawioModal';
-// import { UncontrolledCodeMirror } from '../UncontrolledCodeMirror';
 
 
 // Textlint
 // Textlint
 window.JSHINT = JSHINT;
 window.JSHINT = JSHINT;
@@ -110,7 +108,7 @@ export default class CodeMirrorEditor extends AbstractEditor {
 
 
     this.state = {
     this.state = {
       value: this.props.value,
       value: this.props.value,
-      isGfmMode: this.props.isGfmMode ?? true,
+      isGfmMode: this.props.isGfmMode,
       isEnabledEmojiAutoComplete: false,
       isEnabledEmojiAutoComplete: false,
       isLoadingKeymap: false,
       isLoadingKeymap: false,
       isSimpleCheatsheetShown: this.props.isGfmMode && this.props.value.length === 0,
       isSimpleCheatsheetShown: this.props.isGfmMode && this.props.value.length === 0,
@@ -909,7 +907,6 @@ export default class CodeMirrorEditor extends AbstractEditor {
   }
   }
 
 
   render() {
   render() {
-    const mode = this.state.isGfmMode ? 'gfm-growi' : undefined;
     const lint = this.props.isTextlintEnabled ? this.codemirrorLintConfig : false;
     const lint = this.props.isTextlintEnabled ? this.codemirrorLintConfig : false;
     const additionalClasses = Array.from(this.state.additionalClassSet).join(' ');
     const additionalClasses = Array.from(this.state.additionalClassSet).join(' ');
     const placeholder = this.state.isGfmMode ? 'Input with Markdown..' : 'Input with Plain Text..';
     const placeholder = this.state.isGfmMode ? 'Input with Markdown..' : 'Input with Plain Text..';
@@ -936,11 +933,6 @@ export default class CodeMirrorEditor extends AbstractEditor {
           }}
           }}
           value={this.state.value}
           value={this.state.value}
           options={{
           options={{
-            mode,
-            theme: this.props.editorOptions.theme,
-            styleActiveLine: this.props.editorOptions.styleActiveLine,
-            lineNumbers: this.props.lineNumbers,
-            tabSize: 4,
             indentUnit: this.props.indentSize,
             indentUnit: this.props.indentSize,
             lineWrapping: true,
             lineWrapping: true,
             scrollPastEnd: true,
             scrollPastEnd: true,

+ 15 - 3
packages/app/src/components/PageList/PageListItemL.tsx

@@ -2,10 +2,12 @@ import React, { memo, useCallback } from 'react';
 
 
 import Clamp from 'react-multiline-clamp';
 import Clamp from 'react-multiline-clamp';
 import { format } from 'date-fns';
 import { format } from 'date-fns';
+import urljoin from 'url-join';
 
 
 import { UserPicture, PageListMeta } from '@growi/ui';
 import { UserPicture, PageListMeta } from '@growi/ui';
 import { DevidedPagePath } from '@growi/core';
 import { DevidedPagePath } from '@growi/core';
-import { useIsDeviceSmallerThanLg, usePageRenameModalStatus, usePageDeleteModal } from '~/stores/ui';
+import { useIsDeviceSmallerThanLg } from '~/stores/ui';
+import { usePageRenameModal, usePageDuplicateModal, usePageDeleteModal } from '~/stores/modal';
 import {
 import {
   IPageInfoAll, IPageWithMeta, isIPageInfoForEntity, isIPageInfoForListing,
   IPageInfoAll, IPageWithMeta, isIPageInfoForEntity, isIPageInfoForListing,
 } from '~/interfaces/page';
 } from '~/interfaces/page';
@@ -34,7 +36,8 @@ export const PageListItemL = memo((props: Props): JSX.Element => {
   } = props;
   } = props;
 
 
   const { data: isDeviceSmallerThanLg } = useIsDeviceSmallerThanLg();
   const { data: isDeviceSmallerThanLg } = useIsDeviceSmallerThanLg();
-  const { open: openRenameModal } = usePageRenameModalStatus();
+  const { open: openDuplicateModal } = usePageDuplicateModal();
+  const { open: openRenameModal } = usePageRenameModal();
   const { open: openDeleteModal } = usePageDeleteModal();
   const { open: openDeleteModal } = usePageDeleteModal();
 
 
   const elasticSearchResult = isIPageSearchMeta(pageMeta) ? pageMeta.elasticSearchResult : null;
   const elasticSearchResult = isIPageSearchMeta(pageMeta) ? pageMeta.elasticSearchResult : null;
@@ -58,6 +61,11 @@ export const PageListItemL = memo((props: Props): JSX.Element => {
     }
     }
   }, [isDeviceSmallerThanLg, onClickItem, pageData._id]);
   }, [isDeviceSmallerThanLg, onClickItem, pageData._id]);
 
 
+  const duplicateMenuItemClickHandler = useCallback(() => {
+    const { _id: pageId, path } = pageData;
+    openDuplicateModal(pageId, path);
+  }, [openDuplicateModal, pageData]);
+
   const renameMenuItemClickHandler = useCallback(() => {
   const renameMenuItemClickHandler = useCallback(() => {
     const { _id: pageId, revision: revisionId, path } = pageData;
     const { _id: pageId, revision: revisionId, path } = pageData;
     openRenameModal(pageId, revisionId as string, path);
     openRenameModal(pageId, revisionId as string, path);
@@ -111,7 +119,10 @@ export const PageListItemL = memo((props: Props): JSX.Element => {
               {/* page title */}
               {/* page title */}
               <Clamp lines={1}>
               <Clamp lines={1}>
                 <span className="h5 mb-0">
                 <span className="h5 mb-0">
-                  <PagePathHierarchicalLink linkedPagePath={linkedPagePathLatter} basePath={dPagePath.former} />
+                  {/* Use permanent links to care for pages with the same name (Cannot use page path url) */}
+                  <span className="grw-page-path-hierarchical-link text-break">
+                    <a className="page-segment" href={encodeURI(urljoin('/', pageData._id))}>{linkedPagePathLatter.pathName}</a>
+                  </span>
                 </span>
                 </span>
               </Clamp>
               </Clamp>
 
 
@@ -130,6 +141,7 @@ export const PageListItemL = memo((props: Props): JSX.Element => {
                   onClickDeleteMenuItem={deleteMennuItemClickHandler}
                   onClickDeleteMenuItem={deleteMennuItemClickHandler}
                   onClickRenameMenuItem={renameMenuItemClickHandler}
                   onClickRenameMenuItem={renameMenuItemClickHandler}
                   isEnableActions={isEnableActions}
                   isEnableActions={isEnableActions}
+                  onClickDuplicateMenuItem={duplicateMenuItemClickHandler}
                 />
                 />
               </div>
               </div>
             </div>
             </div>

+ 6 - 16
packages/app/src/components/PagePresentationModal.jsx

@@ -1,31 +1,21 @@
 import React from 'react';
 import React from 'react';
-import PropTypes from 'prop-types';
 import {
 import {
   Modal, ModalBody,
   Modal, ModalBody,
 } from 'reactstrap';
 } from 'reactstrap';
 
 
-const PagePresentationModal = (props) => {
+import { usePagePresentationModal } from '~/stores/modal';
 
 
-  function closeModalHandler() {
-    if (props.onClose === null) {
-      return;
-    }
-    props.onClose();
-  }
+const PagePresentationModal = () => {
+
+  const { data: presentationData, close: closePresentationModal } = usePagePresentationModal();
 
 
   return (
   return (
-    <Modal isOpen={props.isOpen} toggle={closeModalHandler} className="grw-presentation-modal" unmountOnClose={false}>
+    <Modal isOpen={presentationData.isOpened} toggle={closePresentationModal} className="grw-presentation-modal" unmountOnClose={false}>
       <ModalBody className="modal-body">
       <ModalBody className="modal-body">
-        <iframe src={props.href} />
+        <iframe src={presentationData.href} />
       </ModalBody>
       </ModalBody>
     </Modal>
     </Modal>
   );
   );
 };
 };
-PagePresentationModal.propTypes = {
-  isOpen: PropTypes.bool.isRequired,
-  onClose: PropTypes.func,
-  href: PropTypes.string.isRequired,
-};
-
 
 
 export default PagePresentationModal;
 export default PagePresentationModal;

+ 5 - 4
packages/app/src/components/PageRenameModal.jsx

@@ -10,7 +10,7 @@ import {
 import { withTranslation } from 'react-i18next';
 import { withTranslation } from 'react-i18next';
 
 
 import { debounce } from 'throttle-debounce';
 import { debounce } from 'throttle-debounce';
-import { usePageRenameModalStatus, usePageRenameModalOpened } from '~/stores/ui';
+import { usePageRenameModal } from '~/stores/modal';
 import { withUnstatedContainers } from './UnstatedUtils';
 import { withUnstatedContainers } from './UnstatedUtils';
 import { toastError } from '~/client/util/apiNotification';
 import { toastError } from '~/client/util/apiNotification';
 
 
@@ -29,10 +29,11 @@ const PageRenameModal = (props) => {
   } = props;
   } = props;
 
 
   const { crowi } = appContainer.config;
   const { crowi } = appContainer.config;
-  const { data: isOpened } = usePageRenameModalOpened();
-  const { data: pagesDataToRename, close: closeRenameModal } = usePageRenameModalStatus();
+  const { data: pagesDataToRename, close: closeRenameModal } = usePageRenameModal();
 
 
-  const { path, revisionId, pageId } = pagesDataToRename;
+  const {
+    isOpened, path, revisionId, pageId,
+  } = pagesDataToRename;
 
 
   const [pageNameInput, setPageNameInput] = useState('');
   const [pageNameInput, setPageNameInput] = useState('');
 
 

+ 48 - 5
packages/app/src/components/SearchPage/SearchResultContent.tsx

@@ -1,4 +1,6 @@
-import React, { FC, useCallback } from 'react';
+import React, {
+  FC, useCallback, useEffect, useRef,
+} from 'react';
 
 
 import { DropdownItem } from 'reactstrap';
 import { DropdownItem } from 'reactstrap';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
@@ -10,11 +12,12 @@ import { exportAsMarkdown } from '~/client/services/page-operation';
 
 
 import RevisionLoader from '../Page/RevisionLoader';
 import RevisionLoader from '../Page/RevisionLoader';
 import AppContainer from '../../client/services/AppContainer';
 import AppContainer from '../../client/services/AppContainer';
+import { smoothScrollIntoView } from '~/client/util/smooth-scroll';
 import { GrowiSubNavigation } from '../Navbar/GrowiSubNavigation';
 import { GrowiSubNavigation } from '../Navbar/GrowiSubNavigation';
 import { SubNavButtons } from '../Navbar/SubNavButtons';
 import { SubNavButtons } from '../Navbar/SubNavButtons';
 import { AdditionalMenuItemsRendererProps } from '../Common/Dropdown/PageItemControl';
 import { AdditionalMenuItemsRendererProps } from '../Common/Dropdown/PageItemControl';
 
 
-import { usePageDuplicateModalStatus, usePageRenameModalStatus, usePageDeleteModal } from '~/stores/ui';
+import { usePageDuplicateModal, usePageRenameModal, usePageDeleteModal } from '~/stores/modal';
 
 
 
 
 type AdditionalMenuItemsProps = AdditionalMenuItemsRendererProps & {
 type AdditionalMenuItemsProps = AdditionalMenuItemsRendererProps & {
@@ -40,6 +43,8 @@ const AdditionalMenuItems = (props: AdditionalMenuItemsProps): JSX.Element => {
   );
   );
 };
 };
 
 
+const SCROLL_OFFSET_TOP = 175; // approximate height of (navigation + subnavigation)
+const MUTATION_OBSERVER_CONFIG = { childList: true, subtree: true };
 
 
 type Props ={
 type Props ={
   appContainer: AppContainer,
   appContainer: AppContainer,
@@ -48,15 +53,53 @@ type Props ={
   showPageControlDropdown?: boolean,
   showPageControlDropdown?: boolean,
 }
 }
 
 
+const scrollTo = (scrollElement:HTMLElement) => {
+  // use querySelector to intentionally get the first element found
+  const highlightedKeyword = scrollElement.querySelector('.highlighted-keyword') as HTMLElement | null;
+  if (highlightedKeyword != null) {
+    smoothScrollIntoView(highlightedKeyword, SCROLL_OFFSET_TOP, scrollElement);
+  }
+};
+
+const generateObserverCallback = (doScroll: ()=>void) => {
+  return (mutationRecords:MutationRecord[]) => {
+    mutationRecords.forEach((record:MutationRecord) => {
+      const target = record.target as HTMLElement;
+      const targetId = target.id as string;
+      if (targetId !== 'wiki') return;
+      doScroll();
+    });
+  };
+};
+
 const SearchResultContent: FC<Props> = (props: Props) => {
 const SearchResultContent: FC<Props> = (props: Props) => {
+  const scrollElementRef = useRef(null);
+
+  // ***************************  Auto Scroll  ***************************
+  useEffect(() => {
+    const scrollElement = scrollElementRef.current as HTMLElement | null;
+    if (scrollElement == null) return;
+
+    const observerCallback = generateObserverCallback(() => {
+      scrollTo(scrollElement);
+    });
+
+    const observer = new MutationObserver(observerCallback);
+    observer.observe(scrollElement, MUTATION_OBSERVER_CONFIG);
+    return () => {
+      observer.disconnect();
+    };
+  });
+  // *******************************  end  *******************************
+
   const {
   const {
     appContainer,
     appContainer,
     focusedSearchResultData,
     focusedSearchResultData,
     showPageControlDropdown,
     showPageControlDropdown,
   } = props;
   } = props;
 
 
-  const { open: openDuplicateModal } = usePageDuplicateModalStatus();
-  const { open: openRenameModal } = usePageRenameModalStatus();
+  const { open: openDuplicateModal } = usePageDuplicateModal();
+  const { open: openRenameModal } = usePageRenameModal();
   const { open: openDeleteModal } = usePageDeleteModal();
   const { open: openDeleteModal } = usePageDeleteModal();
 
 
   const page = focusedSearchResultData?.pageData;
   const page = focusedSearchResultData?.pageData;
@@ -114,7 +157,7 @@ const SearchResultContent: FC<Props> = (props: Props) => {
         page={page}
         page={page}
         controls={ControlComponents}
         controls={ControlComponents}
       />
       />
-      <div className="search-result-page-content">
+      <div className="search-result-page-content" ref={scrollElementRef}>
         <RevisionLoader
         <RevisionLoader
           growiRenderer={growiRenderer}
           growiRenderer={growiRenderer}
           pageId={page._id}
           pageId={page._id}

+ 1 - 1
packages/app/src/components/Sidebar/PageTree/Item.tsx

@@ -13,7 +13,7 @@ import { pathUtils } from '@growi/core';
 import { toastWarning, toastError, toastSuccess } from '~/client/util/apiNotification';
 import { toastWarning, toastError, toastSuccess } from '~/client/util/apiNotification';
 
 
 import { useSWRxPageChildren } from '~/stores/page-listing';
 import { useSWRxPageChildren } from '~/stores/page-listing';
-import { IPageForPageDeleteModal } from '~/stores/ui';
+import { IPageForPageDeleteModal } from '~/stores/modal';
 import { apiv3Put } from '~/client/util/apiv3-client';
 import { apiv3Put } from '~/client/util/apiv3-client';
 
 
 import TriangleIcon from '~/components/Icons/TriangleIcon';
 import TriangleIcon from '~/components/Icons/TriangleIcon';

+ 4 - 5
packages/app/src/components/Sidebar/PageTree/ItemsTree.tsx

@@ -8,9 +8,8 @@ import { useSWRxPageAncestorsChildren, useSWRxRootPage } from '../../../stores/p
 import { TargetAndAncestors } from '~/interfaces/page-listing-results';
 import { TargetAndAncestors } from '~/interfaces/page-listing-results';
 import { toastError, toastSuccess } from '~/client/util/apiNotification';
 import { toastError, toastSuccess } from '~/client/util/apiNotification';
 import {
 import {
-  IPageForPageDeleteModal, usePageDuplicateModalStatus, usePageRenameModalStatus, usePageDeleteModal,
-  OnDeletedFunction,
-} from '~/stores/ui';
+  OnDeletedFunction, IPageForPageDeleteModal, usePageDuplicateModal, usePageRenameModal, usePageDeleteModal,
+} from '~/stores/modal';
 import { smoothScrollIntoView } from '~/client/util/smooth-scroll';
 import { smoothScrollIntoView } from '~/client/util/smooth-scroll';
 
 
 /*
 /*
@@ -97,8 +96,8 @@ const ItemsTree: FC<ItemsTreeProps> = (props: ItemsTreeProps) => {
 
 
   const { data: ancestorsChildrenData, error: error1 } = useSWRxPageAncestorsChildren(targetPath);
   const { data: ancestorsChildrenData, error: error1 } = useSWRxPageAncestorsChildren(targetPath);
   const { data: rootPageData, error: error2 } = useSWRxRootPage();
   const { data: rootPageData, error: error2 } = useSWRxRootPage();
-  const { open: openDuplicateModal } = usePageDuplicateModalStatus();
-  const { open: openRenameModal } = usePageRenameModalStatus();
+  const { open: openDuplicateModal } = usePageDuplicateModal();
+  const { open: openRenameModal } = usePageRenameModal();
   const { open: openDeleteModal } = usePageDeleteModal();
   const { open: openDeleteModal } = usePageDeleteModal();
 
 
   useEffect(() => {
   useEffect(() => {

+ 1 - 3
packages/app/src/components/UncontrolledCodeMirror.tsx

@@ -12,7 +12,6 @@ export interface UncontrolledCodeMirrorProps extends AbstractEditorProps {
   value: string;
   value: string;
   options?: ICodeMirror['options'];
   options?: ICodeMirror['options'];
   isGfmMode?: boolean;
   isGfmMode?: boolean;
-  indentSize?: number;
   lineNumbers?: boolean;
   lineNumbers?: boolean;
 }
 }
 
 
@@ -26,7 +25,7 @@ class UncontrolledCodeMirrorCore extends AbstractEditor<UncontrolledCodeMirrorCo
   render(): ReactNode {
   render(): ReactNode {
 
 
     const {
     const {
-      value, isGfmMode, indentSize, lineNumbers, editorContainer, options, forwardedRef, ...rest
+      value, isGfmMode, lineNumbers, editorContainer, options, forwardedRef, ...rest
     } = this.props;
     } = this.props;
 
 
     const { editorOptions } = editorContainer.state;
     const { editorOptions } = editorContainer.state;
@@ -41,7 +40,6 @@ class UncontrolledCodeMirrorCore extends AbstractEditor<UncontrolledCodeMirrorCo
           theme: editorOptions.theme,
           theme: editorOptions.theme,
           styleActiveLine: editorOptions.styleActiveLine,
           styleActiveLine: editorOptions.styleActiveLine,
           tabSize: 4,
           tabSize: 4,
-          indentUnit: indentSize,
           ...options,
           ...options,
         }}
         }}
         {...rest}
         {...rest}

+ 6 - 0
packages/app/src/server/service/page-grant.ts

@@ -10,6 +10,8 @@ import { isIncludesObjectId, excludeTestIdsFromTargetIds } from '~/server/util/c
 const { addTrailingSlash } = pathUtils;
 const { addTrailingSlash } = pathUtils;
 const { isTopPage } = pagePathUtils;
 const { isTopPage } = pagePathUtils;
 
 
+const LIMIT_FOR_MULTIPLE_PAGE_OP = 20;
+
 type ObjectIdLike = mongoose.Types.ObjectId | string;
 type ObjectIdLike = mongoose.Types.ObjectId | string;
 
 
 type ComparableTarget = {
 type ComparableTarget = {
@@ -343,6 +345,10 @@ class PageGrantService {
   }
   }
 
 
   async separateNormalizedAndNonNormalizedPages(pageIds: ObjectIdLike[]): Promise<[(PageDocument & { _id: any })[], (PageDocument & { _id: any })[]]> {
   async separateNormalizedAndNonNormalizedPages(pageIds: ObjectIdLike[]): Promise<[(PageDocument & { _id: any })[], (PageDocument & { _id: any })[]]> {
+    if (pageIds.length > LIMIT_FOR_MULTIPLE_PAGE_OP) {
+      throw Error(`The maximum number of pageIds allowed is ${LIMIT_FOR_MULTIPLE_PAGE_OP}.`);
+    }
+
     const Page = mongoose.model('Page') as unknown as PageModel;
     const Page = mongoose.model('Page') as unknown as PageModel;
     const { PageQueryBuilder } = Page;
     const { PageQueryBuilder } = Page;
     const shouldCheckDescendants = true;
     const shouldCheckDescendants = true;

+ 8 - 1
packages/app/src/server/service/page.ts

@@ -1864,7 +1864,14 @@ class PageService {
       return;
       return;
     }
     }
 
 
-    const [normalizedIds, notNormalizedPaths] = await this.crowi.pageGrantService.separateNormalizedAndNonNormalizedPages(pageIds);
+    let normalizedIds;
+    let notNormalizedPaths;
+    try {
+      [normalizedIds, notNormalizedPaths] = await this.crowi.pageGrantService.separateNormalizedAndNonNormalizedPages(pageIds);
+    }
+    catch (err) {
+      throw err;
+    }
 
 
     if (normalizedIds.length === 0) {
     if (normalizedIds.length === 0) {
       // socket.emit('normalizeParentRecursivelyByPageIds', { error: err.message }); TODO: use socket to tell user
       // socket.emit('normalizeParentRecursivelyByPageIds', { error: err.message }); TODO: use socket to tell user

+ 1 - 0
packages/app/src/server/views/layout/layout.html

@@ -107,6 +107,7 @@
 <div id="page-delete-modal"></div>
 <div id="page-delete-modal"></div>
 <div id="page-duplicate-modal"></div>
 <div id="page-duplicate-modal"></div>
 <div id="page-rename-modal"></div>
 <div id="page-rename-modal"></div>
+<div id="page-presentation-modal"></div>
 <div id="page-accessories-modal"></div>
 <div id="page-accessories-modal"></div>
 <div id="descendants-page-list-modal"></div>
 <div id="descendants-page-list-modal"></div>
 
 

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

@@ -0,0 +1,229 @@
+import { SWRResponse } from 'swr';
+import { useStaticSWR } from './use-static-swr';
+import { Nullable } from '~/interfaces/common';
+
+
+/*
+* PageCreateModal
+*/
+type CreateModalStatus = {
+  isOpened: boolean,
+  path?: string,
+}
+
+type CreateModalStatusUtils = {
+  open(path?: string): Promise<CreateModalStatus | undefined>
+  close(): Promise<CreateModalStatus | undefined>
+}
+
+export const usePageCreateModal = (status?: CreateModalStatus): SWRResponse<CreateModalStatus, Error> & CreateModalStatusUtils => {
+  const initialData: CreateModalStatus = { isOpened: false };
+  const swrResponse = useStaticSWR<CreateModalStatus, Error>('pageCreateModalStatus', status, { fallbackData: initialData });
+
+  return {
+    ...swrResponse,
+    open: (path?: string) => swrResponse.mutate({ isOpened: true, path }),
+    close: () => swrResponse.mutate({ isOpened: false }),
+  };
+};
+
+/*
+* PageDeleteModal
+*/
+export type IPageForPageDeleteModal = {
+  pageId: string,
+  revisionId?: string,
+  path: string
+}
+
+export type OnDeletedFunction = (pathOrPaths: string | string[], isRecursively: Nullable<true>, isCompletely: Nullable<true>) => void;
+
+type DeleteModalStatus = {
+  isOpened: boolean,
+  pages?: IPageForPageDeleteModal[],
+  onDeleted?: OnDeletedFunction,
+}
+
+type DeleteModalStatusUtils = {
+  open(pages?: IPageForPageDeleteModal[], onDeleted?: OnDeletedFunction): Promise<DeleteModalStatus | undefined>,
+  close(): Promise<DeleteModalStatus | undefined>,
+}
+
+export const usePageDeleteModal = (status?: DeleteModalStatus): SWRResponse<DeleteModalStatus, Error> & DeleteModalStatusUtils => {
+  const initialData: DeleteModalStatus = { isOpened: false, pages: [], onDeleted: () => {} };
+  const swrResponse = useStaticSWR<DeleteModalStatus, Error>('deleteModalStatus', status, { fallbackData: initialData });
+
+  return {
+    ...swrResponse,
+    open: (pages?: IPageForPageDeleteModal[], onDeleted?: OnDeletedFunction) => swrResponse.mutate({ isOpened: true, pages, onDeleted }),
+    close: () => swrResponse.mutate({ isOpened: false }),
+  };
+};
+
+/*
+* PageDuplicateModal
+*/
+export type IPageForPageDuplicateModal = {
+  pageId: string,
+  path: string
+}
+
+type DuplicateModalStatus = {
+  isOpened: boolean,
+  pageId?: string,
+  path?: string,
+}
+
+type DuplicateModalStatusUtils = {
+  open(pageId: string, path: string): Promise<DuplicateModalStatus | undefined>
+  close(): Promise<DuplicateModalStatus | undefined>
+}
+
+export const usePageDuplicateModal = (status?: DuplicateModalStatus): SWRResponse<DuplicateModalStatus, Error> & DuplicateModalStatusUtils => {
+  const initialData: DuplicateModalStatus = { isOpened: false, pageId: '', path: '' };
+  const swrResponse = useStaticSWR<DuplicateModalStatus, Error>('duplicateModalStatus', status, { fallbackData: initialData });
+
+  return {
+    ...swrResponse,
+    open: (pageId: string, path: string) => swrResponse.mutate({ isOpened: true, pageId, path }),
+    close: () => swrResponse.mutate({ isOpened: false }),
+  };
+};
+
+
+/*
+* PageRenameModal
+*/
+export type IPageForPageRenameModal = {
+  pageId: string,
+  revisionId: string,
+  path: string
+}
+
+type RenameModalStatus = {
+  isOpened: boolean,
+  pageId?: string,
+  revisionId?: string
+  path?: string,
+}
+
+type RenameModalStatusUtils = {
+  open(pageId: string, revisionId: string, path: string): Promise<RenameModalStatus | undefined>
+  close(): Promise<RenameModalStatus | undefined>
+}
+
+export const usePageRenameModal = (status?: RenameModalStatus): SWRResponse<RenameModalStatus, Error> & RenameModalStatusUtils => {
+  const initialData: RenameModalStatus = {
+    isOpened: false, pageId: '', revisionId: '', path: '',
+  };
+  const swrResponse = useStaticSWR<RenameModalStatus, Error>('renameModalStatus', status, { fallbackData: initialData });
+
+  return {
+    ...swrResponse,
+    open: (pageId: string, revisionId: string, path: string) => swrResponse.mutate({
+      isOpened: true, pageId, revisionId, path,
+    }),
+    close: () => swrResponse.mutate({ isOpened: false }),
+  };
+};
+
+/*
+* PagePresentationModal
+*/
+type PresentationModalStatus = {
+  isOpened: boolean,
+  href?: string
+}
+
+type PresentationModalStatusUtils = {
+  open(href: string): Promise<PresentationModalStatus | undefined>
+  close(): Promise<PresentationModalStatus | undefined>
+}
+
+export const usePagePresentationModal = (
+    status?: PresentationModalStatus,
+): SWRResponse<PresentationModalStatus, Error> & PresentationModalStatusUtils => {
+  const initialData: PresentationModalStatus = {
+    isOpened: false, href: '?presentation=1',
+  };
+  const swrResponse = useStaticSWR<PresentationModalStatus, Error>('presentationModalStatus', status, { fallbackData: initialData });
+
+  return {
+    ...swrResponse,
+    open: (href: string) => swrResponse.mutate({ isOpened: true, href }),
+    close: () => swrResponse.mutate({ isOpened: false }),
+  };
+};
+
+/*
+* DescendantsPageListModal
+*/
+type DescendantsPageListModalStatus = {
+  isOpened: boolean,
+  path?: string,
+}
+
+type DescendantsPageListUtils = {
+  open(path: string): Promise<DescendantsPageListModalStatus | undefined>
+  close(): Promise<DuplicateModalStatus | undefined>
+}
+
+export const useDescendantsPageListModal = (
+    status?: DescendantsPageListModalStatus,
+): SWRResponse<DescendantsPageListModalStatus, Error> & DescendantsPageListUtils => {
+
+  const initialData: DescendantsPageListModalStatus = { isOpened: false };
+  const swrResponse = useStaticSWR<DescendantsPageListModalStatus, Error>('descendantsPageListModalStatus', status, { fallbackData: initialData });
+
+  return {
+    ...swrResponse,
+    open: (path: string) => swrResponse.mutate({ isOpened: true, path }),
+    close: () => swrResponse.mutate({ isOpened: false }),
+  };
+};
+
+/*
+* PageAccessoriesModal
+*/
+export const PageAccessoriesModalContents = {
+  PageHistory: 'PageHistory',
+  Attachment: 'Attachment',
+  ShareLink: 'ShareLink',
+} as const;
+export type PageAccessoriesModalContents = typeof PageAccessoriesModalContents[keyof typeof PageAccessoriesModalContents];
+
+type PageAccessoriesModalStatus = {
+  isOpened: boolean,
+  onOpened?: (initialActivatedContents: PageAccessoriesModalContents) => void,
+}
+
+type PageAccessoriesModalUtils = {
+  open(activatedContents: PageAccessoriesModalContents): void
+  close(): void
+}
+
+export const usePageAccessoriesModal = (): SWRResponse<PageAccessoriesModalStatus, Error> & PageAccessoriesModalUtils => {
+
+  const initialStatus = { isOpened: false };
+  const swrResponse = useStaticSWR<PageAccessoriesModalStatus, Error>('pageAccessoriesModalStatus', undefined, { fallbackData: initialStatus });
+
+  return {
+    ...swrResponse,
+    open: (activatedContents: PageAccessoriesModalContents) => {
+      if (swrResponse.data == null) {
+        return;
+      }
+      swrResponse.mutate({ isOpened: true });
+
+      if (swrResponse.data.onOpened != null) {
+        swrResponse.data.onOpened(activatedContents);
+      }
+    },
+    close: () => {
+      if (swrResponse.data == null) {
+        return;
+      }
+      swrResponse.mutate({ isOpened: false });
+    },
+  };
+};

+ 0 - 241
packages/app/src/stores/ui.tsx

@@ -253,247 +253,6 @@ export const useSidebarResizeDisabled = (isDisabled?: boolean): SWRResponse<bool
   return useStaticSWR('isSidebarResizeDisabled', isDisabled, { fallbackData: false });
   return useStaticSWR('isSidebarResizeDisabled', isDisabled, { fallbackData: false });
 };
 };
 
 
-// PageCreateModal
-type CreateModalStatus = {
-  isOpened: boolean,
-  path?: string,
-}
-
-type CreateModalStatusUtils = {
-  open(path?: string): Promise<CreateModalStatus | undefined>
-  close(): Promise<CreateModalStatus | undefined>
-}
-
-export const useCreateModalStatus = (status?: CreateModalStatus): SWRResponse<CreateModalStatus, Error> & CreateModalStatusUtils => {
-  const initialData: CreateModalStatus = { isOpened: false };
-  const swrResponse = useStaticSWR<CreateModalStatus, Error>('pageCreateModalStatus', status, { fallbackData: initialData });
-
-  return {
-    ...swrResponse,
-    open: (path?: string) => swrResponse.mutate({ isOpened: true, path }),
-    close: () => swrResponse.mutate({ isOpened: false }),
-  };
-};
-
-export const useCreateModalOpened = (): SWRResponse<boolean, Error> => {
-  const { data } = useCreateModalStatus();
-  return useSWR(
-    data != null ? ['isCreaateModalOpened', data] : null,
-    () => {
-      return data != null ? data.isOpened : false;
-    },
-  );
-};
-
-export const useCreateModalPath = (): SWRResponse<string | null | undefined, Error> => {
-  const { data: currentPagePath } = useCurrentPagePath();
-  const { data: status } = useCreateModalStatus();
-
-  return useSWR(
-    currentPagePath != null && status != null ? [currentPagePath, status] : null,
-    (currentPagePath, status) => {
-      return status?.path || currentPagePath;
-    },
-  );
-};
-
-// PageDeleteModal
-export type IPageForPageDeleteModal = {
-  pageId: string,
-  revisionId?: string,
-  path: string
-}
-
-export type OnDeletedFunction = (pathOrPaths: string | string[], isRecursively: Nullable<true>, isCompletely: Nullable<true>) => void;
-
-type DeleteModalStatus = {
-  isOpened: boolean,
-  pages?: IPageForPageDeleteModal[],
-  onDeleted?: OnDeletedFunction,
-}
-
-type DeleteModalOpened = {
-  isOpend: boolean,
-  onDeleted?: OnDeletedFunction,
-}
-
-type DeleteModalStatusUtils = {
-  open(pages?: IPageForPageDeleteModal[], onDeleted?: OnDeletedFunction): Promise<DeleteModalStatus | undefined>,
-  close(): Promise<DeleteModalStatus | undefined>,
-}
-
-export const usePageDeleteModal = (status?: DeleteModalStatus): SWRResponse<DeleteModalStatus, Error> & DeleteModalStatusUtils => {
-  const initialData: DeleteModalStatus = { isOpened: false };
-  const swrResponse = useStaticSWR<DeleteModalStatus, Error>('deleteModalStatus', status, { fallbackData: initialData });
-
-  return {
-    ...swrResponse,
-    open: (pages?: IPageForPageDeleteModal[], onDeleted?: OnDeletedFunction) => swrResponse.mutate({ isOpened: true, pages, onDeleted }),
-    close: () => swrResponse.mutate({ isOpened: false }),
-  };
-};
-
-export const usePageDeleteModalOpened = (): SWRResponse<(DeleteModalOpened | null), Error> => {
-  const { data } = usePageDeleteModal();
-  return useSWRImmutable(
-    data != null ? ['isDeleteModalOpened', data] : null,
-    () => {
-      return data != null ? { isOpend: data.isOpened, onDeleted: data?.onDeleted } : null;
-    },
-  );
-};
-
-// PageDuplicateModal
-export type IPageForPageDuplicateModal = {
-  pageId: string,
-  path: string
-}
-
-type DuplicateModalStatus = {
-  isOpened: boolean,
-  pageId?: string,
-  path?: string,
-}
-
-type DuplicateModalStatusUtils = {
-  open(pageId: string, path: string): Promise<DuplicateModalStatus | undefined>
-  close(): Promise<DuplicateModalStatus | undefined>
-}
-
-export const usePageDuplicateModalStatus = (status?: DuplicateModalStatus): SWRResponse<DuplicateModalStatus, Error> & DuplicateModalStatusUtils => {
-  const initialData: DuplicateModalStatus = { isOpened: false, pageId: '', path: '' };
-  const swrResponse = useStaticSWR<DuplicateModalStatus, Error>('duplicateModalStatus', status, { fallbackData: initialData });
-
-  return {
-    ...swrResponse,
-    open: (pageId: string, path: string) => swrResponse.mutate({ isOpened: true, pageId, path }),
-    close: () => swrResponse.mutate({ isOpened: false }),
-  };
-};
-
-export const usePageDuplicateModalOpened = (): SWRResponse<boolean, Error> => {
-  const { data } = usePageDuplicateModalStatus();
-  return useSWRImmutable(
-    data != null ? ['isDuplicateModalOpened', data] : null,
-    () => {
-      return data != null ? data.isOpened : false;
-    },
-  );
-};
-
-// PageRenameModal
-export type IPageForPageRenameModal = {
-  pageId: string,
-  revisionId: string,
-  path: string
-}
-
-type RenameModalStatus = {
-  isOpened: boolean,
-  pageId?: string,
-  revisionId?: string
-  path?: string,
-}
-
-type RenameModalStatusUtils = {
-  open(pageId: string, revisionId: string, path: string): Promise<RenameModalStatus | undefined>
-  close(): Promise<RenameModalStatus | undefined>
-}
-
-export const usePageRenameModalStatus = (status?: RenameModalStatus): SWRResponse<RenameModalStatus, Error> & RenameModalStatusUtils => {
-  const initialData: RenameModalStatus = {
-    isOpened: false, pageId: '', revisionId: '', path: '',
-  };
-  const swrResponse = useStaticSWR<RenameModalStatus, Error>('renameModalStatus', status, { fallbackData: initialData });
-
-  return {
-    ...swrResponse,
-    open: (pageId: string, revisionId: string, path: string) => swrResponse.mutate({
-      isOpened: true, pageId, revisionId, path,
-    }),
-    close: () => swrResponse.mutate({ isOpened: false }),
-  };
-};
-
-export const usePageRenameModalOpened = (): SWRResponse<boolean, Error> => {
-  const { data } = usePageRenameModalStatus();
-  return useSWRImmutable(
-    data != null ? ['isRenameModalOpened', data] : null,
-    () => {
-      return data != null ? data.isOpened : false;
-    },
-  );
-};
-
-
-type DescendantsPageListModalStatus = {
-  isOpened: boolean,
-  path?: string,
-}
-
-type DescendantsPageListUtils = {
-  open(path: string): Promise<DescendantsPageListModalStatus | undefined>
-  close(): Promise<DuplicateModalStatus | undefined>
-}
-
-export const useDescendantsPageListModal = (
-    status?: DescendantsPageListModalStatus,
-): SWRResponse<DescendantsPageListModalStatus, Error> & DescendantsPageListUtils => {
-
-  const initialData: DescendantsPageListModalStatus = { isOpened: false };
-  const swrResponse = useStaticSWR<DescendantsPageListModalStatus, Error>('descendantsPageListModalStatus', status, { fallbackData: initialData });
-
-  return {
-    ...swrResponse,
-    open: (path: string) => swrResponse.mutate({ isOpened: true, path }),
-    close: () => swrResponse.mutate({ isOpened: false }),
-  };
-};
-
-
-export const PageAccessoriesModalContents = {
-  PageHistory: 'PageHistory',
-  Attachment: 'Attachment',
-  ShareLink: 'ShareLink',
-} as const;
-export type PageAccessoriesModalContents = typeof PageAccessoriesModalContents[keyof typeof PageAccessoriesModalContents];
-
-type PageAccessoriesModalStatus = {
-  isOpened: boolean,
-  onOpened?: (initialActivatedContents: PageAccessoriesModalContents) => void,
-}
-
-type PageAccessoriesModalUtils = {
-  open(activatedContents: PageAccessoriesModalContents): void
-  close(): void
-}
-
-export const usePageAccessoriesModal = (): SWRResponse<PageAccessoriesModalStatus, Error> & PageAccessoriesModalUtils => {
-
-  const initialStatus = { isOpened: false };
-  const swrResponse = useStaticSWR<PageAccessoriesModalStatus, Error>('pageAccessoriesModalStatus', undefined, { fallbackData: initialStatus });
-
-  return {
-    ...swrResponse,
-    open: (activatedContents: PageAccessoriesModalContents) => {
-      if (swrResponse.data == null) {
-        return;
-      }
-      swrResponse.mutate({ isOpened: true });
-
-      if (swrResponse.data.onOpened != null) {
-        swrResponse.data.onOpened(activatedContents);
-      }
-    },
-    close: () => {
-      if (swrResponse.data == null) {
-        return;
-      }
-      swrResponse.mutate({ isOpened: false });
-    },
-  };
-};
-
 
 
 export const useSelectedGrant = (initialData?: Nullable<number>): SWRResponse<Nullable<number>, Error> => {
 export const useSelectedGrant = (initialData?: Nullable<number>): SWRResponse<Nullable<number>, Error> => {
   return useStaticSWR<Nullable<number>, Error>('grant', initialData);
   return useStaticSWR<Nullable<number>, Error>('grant', initialData);