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

Merge pull request #6340 from weseek/imprv/100017-next-NotFoundPage

Imprv/100017 next not found page
Yuki Takei 3 лет назад
Родитель
Сommit
b3da75f0e1

+ 1 - 1
packages/app/src/client/app.jsx

@@ -35,7 +35,7 @@ import CommentEditorLazyRenderer from '../components/PageComment/CommentEditorLa
 import PageContentFooter from '../components/PageContentFooter';
 import PageContentFooter from '../components/PageContentFooter';
 import BookmarkList from '../components/PageList/BookmarkList';
 import BookmarkList from '../components/PageList/BookmarkList';
 import PageStatusAlert from '../components/PageStatusAlert';
 import PageStatusAlert from '../components/PageStatusAlert';
-import PageTimeline from '../components/PageTimeline';
+import { PageTimeline } from '../components/PageTimeline';
 import RecentCreated from '../components/RecentCreated/RecentCreated';
 import RecentCreated from '../components/RecentCreated/RecentCreated';
 import { SearchPage } from '../components/SearchPage';
 import { SearchPage } from '../components/SearchPage';
 import Sidebar from '../components/Sidebar';
 import Sidebar from '../components/Sidebar';

+ 3 - 1
packages/app/src/components/CustomNavigation/CustomNav.jsx

@@ -7,6 +7,8 @@ import {
   Nav, NavItem, NavLink,
   Nav, NavItem, NavLink,
 } from 'reactstrap';
 } from 'reactstrap';
 
 
+import styles from './CustomNav.module.scss';
+
 
 
 function getBreakpointOneLevelLarger(breakpoint) {
 function getBreakpointOneLevelLarger(breakpoint) {
   switch (breakpoint) {
   switch (breakpoint) {
@@ -149,7 +151,7 @@ export const CustomNavTab = (props) => {
   }
   }
 
 
   return (
   return (
-    <div className="grw-custom-nav-tab">
+    <div className={`grw-custom-nav-tab ${styles['grw-custom-nav-tab']}`}>
       <div ref={navContainer} className="d-flex justify-content-between">
       <div ref={navContainer} className="d-flex justify-content-between">
         <Nav className="nav-title">
         <Nav className="nav-title">
           {Object.entries(navTabMapping).map(([key, value]) => {
           {Object.entries(navTabMapping).map(([key, value]) => {

+ 8 - 6
packages/app/src/styles/_navbar.scss → packages/app/src/components/CustomNavigation/CustomNav.module.scss

@@ -1,14 +1,16 @@
 .grw-custom-nav-tab,
 .grw-custom-nav-tab,
 .grw-custom-nav-dropdown {
 .grw-custom-nav-dropdown {
-  svg {
-    width: 17px;
-    height: 17px;
-    margin-right: 5px;
-    vertical-align: text-bottom;
+  :global {
+    svg {
+      width: 17px;
+      height: 17px;
+      margin-right: 5px;
+      vertical-align: text-bottom;
+    }
   }
   }
 }
 }
 
 
-.grw-custom-nav-tab {
+.grw-custom-nav-tab :global {
   .nav-title {
   .nav-title {
     flex-wrap: nowrap;
     flex-wrap: nowrap;
   }
   }

+ 12 - 18
packages/app/src/components/CustomNavigation/CustomNavAndContents.jsx → packages/app/src/components/CustomNavigation/CustomNavAndContents.tsx

@@ -1,14 +1,21 @@
-import React, { useState } from 'react';
-
-import PropTypes from 'prop-types';
+import React, { ReactNode, useState } from 'react';
 
 
 import CustomNav, { CustomNavTab, CustomNavDropdown } from './CustomNav';
 import CustomNav, { CustomNavTab, CustomNavDropdown } from './CustomNav';
 import CustomTabContent from './CustomTabContent';
 import CustomTabContent from './CustomTabContent';
 
 
+type CustomNavAndContentsProps = {
+  navTabMapping: any,
+  defaultTabIndex?: number,
+  navigationMode?: 'both' | 'tab' | 'dropdown',
+  tabContentClasses?: string[],
+  breakpointToHideInactiveTabsDown?: 'xs' | 'sm' | 'md' | 'lg' | 'xl',
+  navRightElement?: ReactNode
+}
+
 
 
-const CustomNavAndContents = (props) => {
+const CustomNavAndContents = (props: CustomNavAndContentsProps): JSX.Element => {
   const {
   const {
-    navTabMapping, defaultTabIndex, navigationMode, tabContentClasses, breakpointToHideInactiveTabsDown, navRightElement,
+    navTabMapping, defaultTabIndex, navigationMode = 'tab', tabContentClasses = ['p-4'], breakpointToHideInactiveTabsDown, navRightElement,
   } = props;
   } = props;
   const [activeTab, setActiveTab] = useState(Object.keys(props.navTabMapping)[defaultTabIndex || 0]);
   const [activeTab, setActiveTab] = useState(Object.keys(props.navTabMapping)[defaultTabIndex || 0]);
 
 
@@ -39,17 +46,4 @@ const CustomNavAndContents = (props) => {
   );
   );
 };
 };
 
 
-CustomNavAndContents.propTypes = {
-  navTabMapping: PropTypes.object.isRequired,
-  defaultTabIndex: PropTypes.number,
-  navigationMode: PropTypes.oneOf(['both', 'tab', 'dropdown']),
-  tabContentClasses: PropTypes.arrayOf(PropTypes.string),
-  breakpointToHideInactiveTabsDown: PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl']),
-  navRightElement: PropTypes.node,
-};
-CustomNavAndContents.defaultProps = {
-  navigationMode: 'tab',
-  tabContentClasses: ['p-4'],
-};
-
 export default CustomNavAndContents;
 export default CustomNavAndContents;

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

@@ -15,7 +15,7 @@ import { DescendantsPageList } from './DescendantsPageList';
 import ExpandOrContractButton from './ExpandOrContractButton';
 import ExpandOrContractButton from './ExpandOrContractButton';
 import PageListIcon from './Icons/PageListIcon';
 import PageListIcon from './Icons/PageListIcon';
 import TimeLineIcon from './Icons/TimeLineIcon';
 import TimeLineIcon from './Icons/TimeLineIcon';
-import PageTimeline from './PageTimeline';
+import { PageTimeline } from './PageTimeline';
 
 
 
 
 export const DescendantsPageListModal = (): JSX.Element => {
 export const DescendantsPageListModal = (): JSX.Element => {

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

@@ -10,11 +10,12 @@ import { exportAsMarkdown } from '~/client/services/page-operation';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { apiPost } from '~/client/util/apiv1-client';
 import { apiPost } from '~/client/util/apiv1-client';
 import {
 import {
-  IPageToRenameWithMeta, IPageWithMeta, IPageInfoForEntity,
+  IPageToRenameWithMeta, IPageWithMeta, IPageInfoForEntity, IPageHasId,
 } from '~/interfaces/page';
 } from '~/interfaces/page';
 import { OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction } from '~/interfaces/ui';
 import { OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction } from '~/interfaces/ui';
 import {
 import {
   useCurrentPageId,
   useCurrentPageId,
+  useCurrentPathname,
   useCurrentUser, useIsGuestUser, useIsSharedUser, useShareLinkId, useTemplateTagData,
   useCurrentUser, useIsGuestUser, useIsSharedUser, useShareLinkId, useTemplateTagData,
 } from '~/stores/context';
 } from '~/stores/context';
 import { usePageTagsForEditors } from '~/stores/editor';
 import { usePageTagsForEditors } from '~/stores/editor';
@@ -162,6 +163,7 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
   const { data: isDrawerMode } = useDrawerMode();
   const { data: isDrawerMode } = useDrawerMode();
   const { data: editorMode, mutate: mutateEditorMode } = useEditorMode();
   const { data: editorMode, mutate: mutateEditorMode } = useEditorMode();
   const { data: pageId } = useCurrentPageId();
   const { data: pageId } = useCurrentPageId();
+  const { data: currentPathname } = useCurrentPathname();
   const { data: currentUser } = useCurrentUser();
   const { data: currentUser } = useCurrentUser();
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isSharedUser } = useIsSharedUser();
   const { data: isSharedUser } = useIsSharedUser();
@@ -344,13 +346,17 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
     templateMenuItemClickHandler, isPageTemplateModalShown,
     templateMenuItemClickHandler, isPageTemplateModalShown,
   ]);
   ]);
 
 
-  if (currentPage == null) {
+  if (currentPathname == null) {
     return <></>;
     return <></>;
   }
   }
 
 
+  const notFoundPage: Partial<IPageHasId> = {
+    path: currentPathname,
+  };
+
   return (
   return (
     <GrowiSubNavigation
     <GrowiSubNavigation
-      page={currentPage}
+      page={currentPage ?? notFoundPage}
       showDrawerToggler={isDrawerMode}
       showDrawerToggler={isDrawerMode}
       showTagLabel={isAbleToShowTagLabel}
       showTagLabel={isAbleToShowTagLabel}
       showPageAuthors={isAbleToShowPageAuthors}
       showPageAuthors={isAbleToShowPageAuthors}

+ 3 - 5
packages/app/src/components/NotFoundPage.tsx

@@ -6,13 +6,12 @@ import dynamic from 'next/dynamic';
 import { DescendantsPageListForCurrentPath } from './DescendantsPageList';
 import { DescendantsPageListForCurrentPath } from './DescendantsPageList';
 import PageListIcon from './Icons/PageListIcon';
 import PageListIcon from './Icons/PageListIcon';
 import TimeLineIcon from './Icons/TimeLineIcon';
 import TimeLineIcon from './Icons/TimeLineIcon';
-// import PageTimeline from './PageTimeline';
+import { PageTimeline } from './PageTimeline';
+import CustomNavAndContents from './CustomNavigation/CustomNavAndContents';
 
 
 const NotFoundPage = (): JSX.Element => {
 const NotFoundPage = (): JSX.Element => {
   const { t } = useTranslation();
   const { t } = useTranslation();
 
 
-  const CustomNavAndContents = dynamic(() => import('./CustomNavigation/CustomNavAndContents'), { ssr: false });
-
   const navTabMapping = useMemo(() => {
   const navTabMapping = useMemo(() => {
     return {
     return {
       pagelist: {
       pagelist: {
@@ -23,8 +22,7 @@ const NotFoundPage = (): JSX.Element => {
       },
       },
       timeLine: {
       timeLine: {
         Icon: TimeLineIcon,
         Icon: TimeLineIcon,
-        // Content: PageTimeline,
-        Content: () => <></>,
+        Content: PageTimeline,
         i18n: t('Timeline View'),
         i18n: t('Timeline View'),
         index: 1,
         index: 1,
       },
       },

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

@@ -16,7 +16,6 @@ import { EditorMode, useEditorMode } from '~/stores/ui';
 import CountBadge from '../Common/CountBadge';
 import CountBadge from '../Common/CountBadge';
 import PageListIcon from '../Icons/PageListIcon';
 import PageListIcon from '../Icons/PageListIcon';
 import { NotCreatablePage } from '../NotCreatablePage';
 import { NotCreatablePage } from '../NotCreatablePage';
-import NotFoundPage from '../NotFoundPage';
 import { Page } from '../Page';
 import { Page } from '../Page';
 // import PageEditor from '../PageEditor';
 // import PageEditor from '../PageEditor';
 // import PageEditorByHackmd from '../PageEditorByHackmd';
 // import PageEditorByHackmd from '../PageEditorByHackmd';
@@ -37,6 +36,7 @@ const DisplaySwitcher = (): JSX.Element => {
   const EditorNavbarBottom = dynamic(() => import('../PageEditor/EditorNavbarBottom'), { ssr: false });
   const EditorNavbarBottom = dynamic(() => import('../PageEditor/EditorNavbarBottom'), { ssr: false });
   const HashChanged = dynamic(() => import('../EventListeneres/HashChanged'), { ssr: false });
   const HashChanged = dynamic(() => import('../EventListeneres/HashChanged'), { ssr: false });
   const ContentLinkButtons = dynamic(() => import('../ContentLinkButtons'), { ssr: false });
   const ContentLinkButtons = dynamic(() => import('../ContentLinkButtons'), { ssr: false });
+  const NotFoundPage = dynamic(() => import('../NotFoundPage'), { ssr: false });
 
 
   // get element for smoothScroll
   // get element for smoothScroll
   // const getCommentListDom = useMemo(() => { return document.getElementById('page-comments-list') }, []);
   // const getCommentListDom = useMemo(() => { return document.getElementById('page-comments-list') }, []);

+ 0 - 136
packages/app/src/components/PageTimeline.jsx

@@ -1,136 +0,0 @@
-import React from 'react';
-
-import PropTypes from 'prop-types';
-import { useTranslation } from 'next-i18next';
-
-import AppContainer from '~/client/services/AppContainer';
-import PageContainer from '~/client/services/PageContainer';
-import { apiv3Get } from '~/client/util/apiv3-client';
-import { RendererOptions } from '~/services/renderer/renderer';
-import { useTimelineOptions } from '~/stores/renderer';
-
-import RevisionLoader from './Page/RevisionLoader';
-import PaginationWrapper from './PaginationWrapper';
-import { withUnstatedContainers } from './UnstatedUtils';
-
-
-class PageTimeline extends React.Component {
-
-  constructor(props) {
-    super(props);
-
-    this.state = {
-      activePage: 1,
-      totalPageItems: 0,
-      limit: null,
-
-      // TODO: remove after when timeline is implemented with React and inject data with props
-      pages: this.props.pages,
-    };
-
-    this.handlePage = this.handlePage.bind(this);
-  }
-
-
-  async handlePage(selectedPage) {
-    const { appContainer, pageContainer } = this.props;
-    const { path } = pageContainer.state;
-    const page = selectedPage;
-
-    const res = await apiv3Get('/pages/list', { path, page });
-    const totalPageItems = res.data.totalCount;
-    const pages = res.data.pages;
-    const pagingLimit = res.data.limit;
-    this.setState({
-      activePage: selectedPage,
-      totalPageItems,
-      pages,
-      limit: pagingLimit,
-    });
-  }
-
-  UNSAFE_componentWillMount() {
-    const { rendererOptions } = this.props;
-    // initialize GrowiRenderer
-    this.rendererOptions = rendererOptions;
-  }
-
-  async componentDidMount() {
-    await this.handlePage(1);
-    this.setState({
-      activePage: 1,
-    });
-  }
-
-  render() {
-    const { t } = this.props;
-    const { pages } = this.state;
-
-    if (pages == null || pages.length === 0) {
-      return (
-        <div className="mt-2">
-          {/* eslint-disable-next-line react/no-danger */}
-          <p>{t('custom_navigation.no_page_list')}</p>
-        </div>
-      );
-    }
-
-    return (
-      <div>
-        { pages.map((page) => {
-          return (
-            <div className="timeline-body" key={`key-${page._id}`}>
-              <div className="card card-timeline">
-                <div className="card-header"><a href={page.path}>{page.path}</a></div>
-                <div className="card-body">
-                  <RevisionLoader
-                    lazy
-                    rendererOptions={this.rendererOptions}
-                    pageId={page._id}
-                    pagePath={page.path}
-                    revisionId={page.revision}
-                  />
-                </div>
-              </div>
-            </div>
-          );
-        }) }
-        <PaginationWrapper
-          activePage={this.state.activePage}
-          changePage={this.handlePage}
-          totalItemsCount={this.state.totalPageItems}
-          pagingLimit={this.state.limit}
-          align="center"
-        />
-      </div>
-    );
-
-  }
-
-}
-
-PageTimeline.propTypes = {
-  t: PropTypes.func.isRequired, // i18next
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-  rendererOptions: PropTypes.instanceOf(RendererOptions).isRequired,
-  pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
-  pages: PropTypes.arrayOf(PropTypes.object),
-};
-
-const PageTimelineWrapperFC = (props) => {
-  const { t } = useTranslation();
-  const { data: rendererOptions } = useTimelineOptions();
-
-  if (rendererOptions == null) {
-    return <></>;
-  }
-
-  return <PageTimeline t={t} rendererOptions={rendererOptions} {...props} />;
-};
-
-/**
- * Wrapper component for using unstated
- */
-const PageTimelineWrapper = withUnstatedContainers(PageTimelineWrapperFC, [AppContainer, PageContainer]);
-
-export default PageTimelineWrapper;

+ 78 - 0
packages/app/src/components/PageTimeline.tsx

@@ -0,0 +1,78 @@
+import React, { useEffect, useState, useCallback } from 'react';
+
+import { useTranslation } from 'next-i18next';
+
+import { apiv3Get } from '~/client/util/apiv3-client';
+import { IPageHasId } from '~/interfaces/page';
+import { useCurrentPagePath } from '~/stores/context';
+import { useTimelineOptions } from '~/stores/renderer';
+
+import RevisionLoader from './Page/RevisionLoader';
+import PaginationWrapper from './PaginationWrapper';
+
+export const PageTimeline = (): JSX.Element => {
+  const [activePage, setActivePage] = useState(1);
+  const [totalPageItems, setTotalPageItems] = useState(0);
+  const [limit, setLimit] = useState(10);
+  const [pages, setPages] = useState<IPageHasId[] | null>(null);
+
+  const { data: currentPagePath } = useCurrentPagePath();
+  const { t } = useTranslation();
+  const { data: rendererOptions } = useTimelineOptions();
+
+  const handlePage = useCallback(async(selectedPage: number) => {
+    if (currentPagePath == null) { return }
+    const res = await apiv3Get('/pages/list', { path: currentPagePath, selectedPage });
+    setTotalPageItems(res.data.totalCount);
+    setPages(res.data.pages);
+    setLimit(res.data.limit);
+    setActivePage(selectedPage);
+  }, [currentPagePath]);
+
+  useEffect(() => {
+    handlePage(1);
+  }, [handlePage]);
+
+  if (rendererOptions == null) {
+    return <></>;
+  }
+
+  if (pages == null || pages.length === 0) {
+    return (
+      <div className="mt-2">
+        {/* eslint-disable-next-line react/no-danger */}
+        <p>{t('custom_navigation.no_page_list')}</p>
+      </div>
+    );
+  }
+
+  return (
+    <div>
+      { pages.map((page) => {
+        return (
+          <div className="timeline-body" key={`key-${page._id}`}>
+            <div className="card card-timeline">
+              <div className="card-header"><a href={page.path}>{page.path}</a></div>
+              <div className="card-body">
+                <RevisionLoader
+                  lazy
+                  rendererOptions={rendererOptions}
+                  pageId={page._id}
+                  pagePath={page.path}
+                  revisionId={page.revision}
+                />
+              </div>
+            </div>
+          </div>
+        );
+      }) }
+      <PaginationWrapper
+        activePage={activePage}
+        changePage={handlePage}
+        totalItemsCount={totalPageItems}
+        pagingLimit={limit}
+        align="center"
+      />
+    </div>
+  );
+};