Przeglądaj źródła

Merge branch 'feat/auditlog' of https://github.com/weseek/growi into feat/97283-create-activit-by-general-user-operation

Shun Miyazawa 3 lat temu
rodzic
commit
e4bce2fb7a

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

@@ -10,9 +10,7 @@ import { Provider } from 'unstated';
 import ContextExtractor from '~/client/services/ContextExtractor';
 import ContextExtractor from '~/client/services/ContextExtractor';
 import EditorContainer from '~/client/services/EditorContainer';
 import EditorContainer from '~/client/services/EditorContainer';
 import PageContainer from '~/client/services/PageContainer';
 import PageContainer from '~/client/services/PageContainer';
-import PageHistoryContainer from '~/client/services/PageHistoryContainer';
 import PersonalContainer from '~/client/services/PersonalContainer';
 import PersonalContainer from '~/client/services/PersonalContainer';
-import RevisionComparerContainer from '~/client/services/RevisionComparerContainer';
 import IdenticalPathPage from '~/components/IdenticalPathPage';
 import IdenticalPathPage from '~/components/IdenticalPathPage';
 import PrivateLegacyPages from '~/components/PrivateLegacyPages';
 import PrivateLegacyPages from '~/components/PrivateLegacyPages';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
@@ -58,13 +56,10 @@ const socketIoContainer = appContainer.getContainer('SocketIoContainer');
 
 
 // create unstated container instance
 // create unstated container instance
 const pageContainer = new PageContainer(appContainer);
 const pageContainer = new PageContainer(appContainer);
-const pageHistoryContainer = new PageHistoryContainer(appContainer, pageContainer);
-const revisionComparerContainer = new RevisionComparerContainer(appContainer, pageContainer);
 const editorContainer = new EditorContainer(appContainer);
 const editorContainer = new EditorContainer(appContainer);
 const personalContainer = new PersonalContainer(appContainer);
 const personalContainer = new PersonalContainer(appContainer);
 const injectableContainers = [
 const injectableContainers = [
-  appContainer, socketIoContainer, pageContainer, pageHistoryContainer, revisionComparerContainer,
-  editorContainer, personalContainer,
+  appContainer, socketIoContainer, pageContainer, editorContainer, personalContainer,
 ];
 ];
 
 
 logger.info('unstated containers have been initialized');
 logger.info('unstated containers have been initialized');

+ 0 - 172
packages/app/src/client/services/PageHistoryContainer.js

@@ -1,172 +0,0 @@
-import { Container } from 'unstated';
-
-import loggerFactory from '~/utils/logger';
-
-import { toastError } from '../util/apiNotification';
-import { apiv3Get } from '../util/apiv3-client';
-
-const logger = loggerFactory('growi:PageHistoryContainer');
-
-/**
- * Service container for personal settings page (PageHistory.jsx)
- * @extends {Container} unstated Container
- */
-export default class PageHistoryContainer extends Container {
-
-  constructor(appContainer, pageContainer) {
-    super();
-
-    this.appContainer = appContainer;
-    this.pageContainer = pageContainer;
-    this.dummyRevisions = 0;
-
-    this.state = {
-      errorMessage: null,
-
-      // set dummy rivisions for using suspense
-      revisions: this.dummyRevisions,
-      latestRevision: this.dummyRevisions,
-      oldestRevision: this.dummyRevisions,
-      diffOpened: {},
-
-      totalPages: 0,
-      activePage: 1,
-      pagingLimit: 10,
-    };
-
-    this.retrieveRevisions = this.retrieveRevisions.bind(this);
-    this.getPreviousRevision = this.getPreviousRevision.bind(this);
-    this.fetchPageRevisionBody = this.fetchPageRevisionBody.bind(this);
-  }
-
-  /**
-   * Workaround for the mangling in production build to break constructor.name
-   */
-  static getClassName() {
-    return 'PageHistoryContainer';
-  }
-
-  /**
-   * syncRevisions of selectedPage
-   * @param {number} selectedPage
-   */
-  async retrieveRevisions(selectedPage) {
-    const { pageId, shareLinkId } = this.pageContainer.state;
-    const { pagingLimit } = this.state;
-    const page = selectedPage;
-    const pagingLimitForApiParam = pagingLimit + 1;
-
-    if (!pageId) {
-      return;
-    }
-
-    // Get one more for the bottom display
-    const res = await apiv3Get('/revisions/list', {
-      pageId, shareLinkId, page, limit: pagingLimitForApiParam,
-    });
-    const rev = res.data.docs;
-    // set Pagination state
-    this.setState({
-      activePage: selectedPage,
-      totalPages: res.data.totalDocs,
-      pagingLimit,
-    });
-
-    const diffOpened = {};
-
-    let lastId = rev.length - 1;
-
-    // If the number of rev count is the same, the last rev is for diff display, so exclude it.
-    if (rev.length > pagingLimit) {
-      lastId = rev.length - 2;
-    }
-
-    res.data.docs.forEach((revision, i) => {
-      const user = revision.author;
-      if (user) {
-        rev[i].author = user;
-      }
-
-      if (i === 0 || i === lastId) {
-        diffOpened[revision._id] = true;
-      }
-      else {
-        diffOpened[revision._id] = false;
-      }
-    });
-
-    this.setState({ revisions: rev });
-    this.setState({ diffOpened });
-
-    if (selectedPage === 1) {
-      this.setState({ latestRevision: rev[0] });
-    }
-
-    if (selectedPage === res.data.totalPages) {
-      this.setState({ oldestRevision: rev[lastId] });
-    }
-
-    // load 0, and last default
-    if (rev[0]) {
-      this.fetchPageRevisionBody(rev[0]);
-    }
-    if (rev[1]) {
-      this.fetchPageRevisionBody(rev[1]);
-    }
-    if (lastId !== 0 && lastId !== 1 && rev[lastId]) {
-      this.fetchPageRevisionBody(rev[lastId]);
-    }
-
-    return;
-  }
-
-  getPreviousRevision(currentRevision) {
-    let cursor = null;
-    for (const revision of this.state.revisions) {
-      // comparing ObjectId
-      // eslint-disable-next-line eqeqeq
-      if (cursor && cursor._id == currentRevision._id) {
-        cursor = revision;
-        break;
-      }
-
-      cursor = revision;
-    }
-
-    return cursor;
-  }
-
-  /**
-   * fetch page revision body by revision in argument
-   * @param {object} revision
-   */
-  async fetchPageRevisionBody(revision) {
-    const { pageId, shareLinkId } = this.pageContainer.state;
-
-    if (revision.body) {
-      return;
-    }
-
-    try {
-      const res = await apiv3Get(`/revisions/${revision._id}`, { pageId, shareLinkId });
-      this.setState({
-        revisions: this.state.revisions.map((rev) => {
-          // comparing ObjectId
-          // eslint-disable-next-line eqeqeq
-          if (rev._id == res.data.revision._id) {
-            return res.data.revision;
-          }
-
-          return rev;
-        }),
-      });
-    }
-    catch (err) {
-      toastError(err);
-      this.setState({ errorMessage: err.message });
-      logger.error(err);
-    }
-  }
-
-
-}

+ 0 - 113
packages/app/src/client/services/RevisionComparerContainer.js

@@ -1,113 +0,0 @@
-import { Container } from 'unstated';
-
-import loggerFactory from '~/utils/logger';
-
-import { toastError } from '../util/apiNotification';
-import { apiv3Get } from '../util/apiv3-client';
-
-const logger = loggerFactory('growi:PageHistoryContainer');
-
-/**
- * Service container for personal settings page (RevisionCompare.jsx)
- * @extends {Container} unstated Container
- */
-export default class RevisionComparerContainer extends Container {
-
-  constructor(appContainer, pageContainer) {
-    super();
-
-    this.appContainer = appContainer;
-    this.pageContainer = pageContainer;
-
-    this.state = {
-      errMessage: null,
-
-      sourceRevision: null,
-      targetRevision: null,
-      latestRevision: null,
-    };
-
-    this.initRevisions = this.initRevisions.bind(this);
-  }
-
-  /**
-   * Workaround for the mangling in production build to break constructor.name
-   */
-  static getClassName() {
-    return 'RevisionComparerContainer';
-  }
-
-  /**
-   * Initialize the revisions
-   */
-  async initRevisions() {
-    const latestRevision = await this.fetchLatestRevision();
-
-    const [sourceRevisionId, targetRevisionId] = this.getRevisionIDsToCompareAsParam();
-    const sourceRevision = sourceRevisionId ? await this.fetchRevision(sourceRevisionId) : latestRevision;
-    const targetRevision = targetRevisionId ? await this.fetchRevision(targetRevisionId) : latestRevision;
-    const compareWithLatest = targetRevisionId ? false : this.state.compareWithLatest;
-
-    this.setState({
-      sourceRevision, targetRevision, latestRevision, compareWithLatest,
-    });
-  }
-
-  /**
-   * Get the IDs of the comparison source and target from "window.location" as an array
-   */
-  getRevisionIDsToCompareAsParam() {
-    const searchParams = {};
-    for (const param of window.location.search?.substr(1)?.split('&')) {
-      const [k, v] = param.split('=');
-      searchParams[k] = v;
-    }
-    if (!searchParams.compare) {
-      return [];
-    }
-
-    return searchParams.compare.split('...') || [];
-  }
-
-  /**
-   * Fetch the latest revision
-   */
-  async fetchLatestRevision() {
-    const { pageId, shareLinkId } = this.pageContainer.state;
-
-    try {
-      const res = await apiv3Get('/revisions/list', {
-        pageId, shareLinkId, page: 1, limit: 1,
-      });
-      return res.data.docs[0];
-    }
-    catch (err) {
-      toastError(err);
-      this.setState({ errorMessage: err.message });
-      logger.error(err);
-    }
-    return null;
-  }
-
-  /**
-   * Fetch the revision of the specified ID
-   * @param {string} revision ID
-   */
-  async fetchRevision(revisionId) {
-    const { pageId, shareLinkId } = this.pageContainer.state;
-
-    try {
-      const res = await apiv3Get(`/revisions/${revisionId}`, {
-        pageId, shareLinkId,
-      });
-      return res.data.revision;
-    }
-    catch (err) {
-      toastError(err);
-      this.setState({ errorMessage: err.message });
-      logger.error(err);
-    }
-    return null;
-  }
-
-}

+ 33 - 8
packages/app/src/components/Admin/AuditLog/SelectActionDropdown.tsx

@@ -1,12 +1,14 @@
-import React, { FC, useCallback } from 'react';
+import React, { FC, useMemo, useCallback } from 'react';
 
 
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 
 
-import { SupportedActionType } from '~/interfaces/activity';
+import {
+  SupportedActionType, SupportedActionCategoryType, SupportedActionCategory, PageActions, CommentActions, UserActions, AdminActions,
+} from '~/interfaces/activity';
 
 
 type Props = {
 type Props = {
-  dropdownItems: Array<{actionCategory: string, actionNames: SupportedActionType[]}>
   actionMap: Map<SupportedActionType, boolean>
   actionMap: Map<SupportedActionType, boolean>
+  availableActions: SupportedActionType[]
   onChangeAction: (action: SupportedActionType) => void
   onChangeAction: (action: SupportedActionType) => void
   onChangeMultipleAction: (actions: SupportedActionType[], isChecked: boolean) => void
   onChangeMultipleAction: (actions: SupportedActionType[], isChecked: boolean) => void
 }
 }
@@ -14,9 +16,32 @@ type Props = {
 export const SelectActionDropdown: FC<Props> = (props: Props) => {
 export const SelectActionDropdown: FC<Props> = (props: Props) => {
   const { t } = useTranslation();
   const { t } = useTranslation();
   const {
   const {
-    dropdownItems, actionMap, onChangeAction, onChangeMultipleAction,
+    actionMap, availableActions, onChangeAction, onChangeMultipleAction,
   } = props;
   } = props;
 
 
+  const dropdownItems = useMemo<Array<{actionCategory: SupportedActionCategoryType, actions: SupportedActionType[]}>>(() => {
+    return (
+      [
+        {
+          actionCategory: SupportedActionCategory.PAGE,
+          actions: PageActions.filter(action => availableActions.includes(action)),
+        },
+        {
+          actionCategory: SupportedActionCategory.COMMENT,
+          actions: CommentActions.filter(action => availableActions.includes(action)),
+        },
+        {
+          actionCategory: SupportedActionCategory.USER,
+          actions: UserActions.filter(action => availableActions.includes(action)),
+        },
+        {
+          actionCategory: SupportedActionCategory.ADMIN,
+          actions: AdminActions.filter(action => availableActions.includes(action)),
+        },
+      ]
+    );
+  }, [availableActions]).filter(item => item.actions.length !== 0);
+
   const actionCheckboxChangedHandler = useCallback((action) => {
   const actionCheckboxChangedHandler = useCallback((action) => {
     if (onChangeAction != null) {
     if (onChangeAction != null) {
       onChangeAction(action);
       onChangeAction(action);
@@ -30,11 +55,11 @@ export const SelectActionDropdown: FC<Props> = (props: Props) => {
   }, [onChangeMultipleAction]);
   }, [onChangeMultipleAction]);
 
 
   return (
   return (
-    <div className="btn-group mr-2">
+    <div className="btn-group mr-2 admin-audit-log">
       <button className="btn btn-outline-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown">
       <button className="btn btn-outline-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown">
         <i className="fa fa-fw fa-bolt" />{t('admin:audit_log_management.action')}
         <i className="fa fa-fw fa-bolt" />{t('admin:audit_log_management.action')}
       </button>
       </button>
-      <ul className="dropdown-menu" aria-labelledby="dropdownMenuButton">
+      <ul className="dropdown-menu select-action-dropdown" aria-labelledby="dropdownMenuButton">
         {dropdownItems.map(item => (
         {dropdownItems.map(item => (
           <div key={item.actionCategory}>
           <div key={item.actionCategory}>
             <div className="dropdown-item">
             <div className="dropdown-item">
@@ -43,13 +68,13 @@ export const SelectActionDropdown: FC<Props> = (props: Props) => {
                   type="checkbox"
                   type="checkbox"
                   className="form-check-input"
                   className="form-check-input"
                   defaultChecked
                   defaultChecked
-                  onChange={(e) => { multipleActionCheckboxChangedHandler(item.actionNames, e.target.checked) }}
+                  onChange={(e) => { multipleActionCheckboxChangedHandler(item.actions, e.target.checked) }}
                 />
                 />
                 <label className="form-check-label">{item.actionCategory}</label>
                 <label className="form-check-label">{item.actionCategory}</label>
               </div>
               </div>
             </div>
             </div>
             {
             {
-              item.actionNames.map(action => (
+              item.actions.map(action => (
                 <div className="dropdown-item" key={action}>
                 <div className="dropdown-item" key={action}>
                   <div className="form-group px-4 m-0">
                   <div className="form-group px-4 m-0">
                     <input
                     <input

+ 7 - 9
packages/app/src/components/Admin/AuditLogManagement.tsx

@@ -3,11 +3,9 @@ import React, { FC, useState, useCallback } from 'react';
 import { format } from 'date-fns';
 import { format } from 'date-fns';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 
 
-import {
-  SupportedActionType, AllSupportedActions, PageActions, CommentActions,
-} from '~/interfaces/activity';
+import { SupportedActionType } from '~/interfaces/activity';
 import { useSWRxActivity } from '~/stores/activity';
 import { useSWRxActivity } from '~/stores/activity';
-import { useAuditLogEnabled } from '~/stores/context';
+import { useAuditLogEnabled, useAuditLogAvailableActions } from '~/stores/context';
 
 
 import PaginationWrapper from '../PaginationWrapper';
 import PaginationWrapper from '../PaginationWrapper';
 
 
@@ -31,6 +29,9 @@ const PAGING_LIMIT = 10;
 export const AuditLogManagement: FC = () => {
 export const AuditLogManagement: FC = () => {
   const { t } = useTranslation();
   const { t } = useTranslation();
 
 
+  const { data: auditLogAvailableActionsData } = useAuditLogAvailableActions();
+  const auditLogAvailableActions = auditLogAvailableActionsData != null ? auditLogAvailableActionsData : [];
+
   /*
   /*
    * State
    * State
    */
    */
@@ -41,7 +42,7 @@ export const AuditLogManagement: FC = () => {
   const [endDate, setEndDate] = useState<Date | null>(null);
   const [endDate, setEndDate] = useState<Date | null>(null);
   const [selectedUsernames, setSelectedUsernames] = useState<string[]>([]);
   const [selectedUsernames, setSelectedUsernames] = useState<string[]>([]);
   const [actionMap, setActionMap] = useState(
   const [actionMap, setActionMap] = useState(
-    new Map<SupportedActionType, boolean>(AllSupportedActions.map(action => [action, true])),
+    new Map<SupportedActionType, boolean>(auditLogAvailableActions.map(action => [action, true])),
   );
   );
 
 
   /*
   /*
@@ -132,11 +133,8 @@ export const AuditLogManagement: FC = () => {
             />
             />
 
 
             <SelectActionDropdown
             <SelectActionDropdown
-              dropdownItems={[
-                { actionCategory: 'Page', actionNames: PageActions },
-                { actionCategory: 'Comment', actionNames: CommentActions },
-              ]}
               actionMap={actionMap}
               actionMap={actionMap}
+              availableActions={auditLogAvailableActions}
               onChangeAction={actionCheckboxChangedHandler}
               onChangeAction={actionCheckboxChangedHandler}
               onChangeMultipleAction={multipleActionCheckboxChangedHandler}
               onChangeMultipleAction={multipleActionCheckboxChangedHandler}
             />
             />

+ 35 - 59
packages/app/src/components/PageHistory.jsx

@@ -1,66 +1,45 @@
-import React, { useCallback } from 'react';
-import PropTypes from 'prop-types';
-import loggerFactory from '~/utils/logger';
+import React, { useState, useEffect } from 'react';
 
 
-import { withUnstatedContainers } from './UnstatedUtils';
-import { toastError } from '~/client/util/apiNotification';
+import { useCurrentPageId } from '~/stores/context';
+import { useSWRxPageRevisions } from '~/stores/page';
+import loggerFactory from '~/utils/logger';
 
 
-import { withLoadingSppiner } from './SuspenseUtils';
 import PageRevisionTable from './PageHistory/PageRevisionTable';
 import PageRevisionTable from './PageHistory/PageRevisionTable';
-
-import PageHistroyContainer from '~/client/services/PageHistoryContainer';
 import PaginationWrapper from './PaginationWrapper';
 import PaginationWrapper from './PaginationWrapper';
 import RevisionComparer from './RevisionComparer/RevisionComparer';
 import RevisionComparer from './RevisionComparer/RevisionComparer';
-import RevisionComparerContainer from '~/client/services/RevisionComparerContainer';
 
 
 const logger = loggerFactory('growi:PageHistory');
 const logger = loggerFactory('growi:PageHistory');
 
 
-function PageHistory(props) {
-  const { pageHistoryContainer, revisionComparerContainer } = props;
-  const { getPreviousRevision } = pageHistoryContainer;
-  const {
-    activePage, totalPages, pagingLimit, revisions, diffOpened,
-  } = pageHistoryContainer.state;
+const PageHistory = () => {
+  const [activePage, setActivePage] = useState(1);
+  const { data: currentPageId } = useCurrentPageId();
+  const { data: revisionsData } = useSWRxPageRevisions(currentPageId, activePage, 10);
+  const [sourceRevision, setSourceRevision] = useState(null);
+  const [targetRevision, setTargetRevision] = useState(null);
 
 
-  const handlePage = useCallback(async(selectedPage) => {
-    try {
-      await props.pageHistoryContainer.retrieveRevisions(selectedPage);
-    }
-    catch (err) {
-      toastError(err);
-      props.pageHistoryContainer.setState({ errorMessage: err.message });
-      logger.error(err);
+  useEffect(() => {
+    if (revisionsData != null) {
+      setSourceRevision(revisionsData.revisions[0]);
+      setTargetRevision(revisionsData.revisions[0]);
     }
     }
-  }, [props.pageHistoryContainer]);
+  }, [revisionsData]);
 
 
-  if (pageHistoryContainer.state.errorMessage != null) {
+
+  const pagingLimit = 10;
+
+  if (revisionsData == null) {
     return (
     return (
-      <div className="my-5">
-        <div className="text-danger">{pageHistoryContainer.state.errorMessage}</div>
+      <div className="text-muted text-center">
+        <i className="fa fa-2x fa-spinner fa-pulse mt-3"></i>
       </div>
       </div>
     );
     );
   }
   }
-
-  if (pageHistoryContainer.state.revisions === pageHistoryContainer.dummyRevisions) {
-    throw new Promise(async() => {
-      try {
-        await props.pageHistoryContainer.retrieveRevisions(1);
-        await props.revisionComparerContainer.initRevisions();
-      }
-      catch (err) {
-        toastError(err);
-        pageHistoryContainer.setState({ errorMessage: err.message });
-        logger.error(err);
-      }
-    });
-  }
-
   function pager() {
   function pager() {
     return (
     return (
       <PaginationWrapper
       <PaginationWrapper
         activePage={activePage}
         activePage={activePage}
-        changePage={handlePage}
-        totalItemsCount={totalPages}
+        changePage={setActivePage}
+        totalItemsCount={revisionsData.totalCounts}
         pagingLimit={pagingLimit}
         pagingLimit={pagingLimit}
         align="center"
         align="center"
       />
       />
@@ -70,26 +49,23 @@ function PageHistory(props) {
   return (
   return (
     <div className="revision-history" data-testid="page-history">
     <div className="revision-history" data-testid="page-history">
       <PageRevisionTable
       <PageRevisionTable
-        pageHistoryContainer={pageHistoryContainer}
-        revisionComparerContainer={revisionComparerContainer}
-        revisions={revisions}
-        diffOpened={diffOpened}
-        getPreviousRevision={getPreviousRevision}
+        revisions={revisionsData.revisions}
+        pagingLimit={pagingLimit}
+        sourceRevision={sourceRevision}
+        targetRevision={targetRevision}
+        onChangeSourceInvoked={setSourceRevision}
+        onChangeTargetInvoked={setTargetRevision}
       />
       />
       <div className="my-3">
       <div className="my-3">
         {pager()}
         {pager()}
       </div>
       </div>
-      <RevisionComparer />
+      <RevisionComparer
+        sourceRevision={sourceRevision}
+        targetRevision={targetRevision}
+        currentPageId={currentPageId}
+      />
     </div>
     </div>
   );
   );
-
-}
-
-const RenderPageHistoryWrapper = withUnstatedContainers(withLoadingSppiner(PageHistory), [PageHistroyContainer, RevisionComparerContainer]);
-
-PageHistory.propTypes = {
-  pageHistoryContainer: PropTypes.instanceOf(PageHistroyContainer).isRequired,
-  revisionComparerContainer: PropTypes.instanceOf(RevisionComparerContainer).isRequired,
 };
 };
 
 
-export default RenderPageHistoryWrapper;
+export default PageHistory;

+ 32 - 33
packages/app/src/components/PageHistory/PageRevisionTable.jsx

@@ -3,9 +3,6 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 
 
-import PageHistroyContainer from '~/client/services/PageHistoryContainer';
-import RevisionComparerContainer from '~/client/services/RevisionComparerContainer';
-
 import Revision from './Revision';
 import Revision from './Revision';
 
 
 class PageRevisionTable extends React.Component {
 class PageRevisionTable extends React.Component {
@@ -17,21 +14,20 @@ class PageRevisionTable extends React.Component {
    * @param {boolean} hasDiff whether revision has difference to previousRevision
    * @param {boolean} hasDiff whether revision has difference to previousRevision
    * @param {boolean} isContiguousNodiff true if the current 'hasDiff' and one of previous row is both false
    * @param {boolean} isContiguousNodiff true if the current 'hasDiff' and one of previous row is both false
    */
    */
-  renderRow(revision, previousRevision, hasDiff, isContiguousNodiff) {
-    const { revisionComparerContainer, t } = this.props;
-    const { latestRevision, oldestRevision } = this.props.pageHistoryContainer.state;
+  renderRow(revision, previousRevision, latestRevision, isOldestRevision, hasDiff) {
+    const {
+      t, sourceRevision, targetRevision, onChangeSourceInvoked, onChangeTargetInvoked,
+    } = this.props;
     const revisionId = revision._id;
     const revisionId = revision._id;
-    const revisionDiffOpened = this.props.diffOpened[revisionId] || false;
-    const { sourceRevision, targetRevision } = revisionComparerContainer.state;
 
 
     const handleCompareLatestRevisionButton = () => {
     const handleCompareLatestRevisionButton = () => {
-      revisionComparerContainer.setState({ sourceRevision: revision });
-      revisionComparerContainer.setState({ targetRevision: latestRevision });
+      onChangeSourceInvoked(revision);
+      onChangeTargetInvoked(latestRevision);
     };
     };
 
 
     const handleComparePreviousRevisionButton = () => {
     const handleComparePreviousRevisionButton = () => {
-      revisionComparerContainer.setState({ sourceRevision: previousRevision });
-      revisionComparerContainer.setState({ targetRevision: revision });
+      onChangeSourceInvoked(previousRevision);
+      onChangeTargetInvoked(revision);
     };
     };
 
 
     return (
     return (
@@ -42,7 +38,6 @@ class PageRevisionTable extends React.Component {
               t={this.props.t}
               t={this.props.t}
               revision={revision}
               revision={revision}
               isLatestRevision={revision === latestRevision}
               isLatestRevision={revision === latestRevision}
-              revisionDiffOpened={revisionDiffOpened}
               hasDiff={hasDiff}
               hasDiff={hasDiff}
               key={`revision-history-rev-${revisionId}`}
               key={`revision-history-rev-${revisionId}`}
             />
             />
@@ -60,7 +55,7 @@ class PageRevisionTable extends React.Component {
                     type="button"
                     type="button"
                     className="btn btn-outline-secondary btn-sm"
                     className="btn btn-outline-secondary btn-sm"
                     onClick={handleComparePreviousRevisionButton}
                     onClick={handleComparePreviousRevisionButton}
-                    disabled={revision === oldestRevision}
+                    disabled={isOldestRevision}
                   >
                   >
                     {t('page_history.compare_previous')}
                     {t('page_history.compare_previous')}
                   </button>
                   </button>
@@ -70,34 +65,34 @@ class PageRevisionTable extends React.Component {
           </div>
           </div>
         </td>
         </td>
         <td className="col-1">
         <td className="col-1">
-          {(hasDiff || revision._id === sourceRevision?._id) && (
+          {(hasDiff || revisionId === sourceRevision?._id) && (
             <div className="custom-control custom-radio custom-control-inline mr-0">
             <div className="custom-control custom-radio custom-control-inline mr-0">
               <input
               <input
                 type="radio"
                 type="radio"
                 className="custom-control-input"
                 className="custom-control-input"
-                id={`compareSource-${revision._id}`}
+                id={`compareSource-${revisionId}`}
                 name="compareSource"
                 name="compareSource"
-                value={revision._id}
-                checked={revision._id === sourceRevision?._id}
-                onChange={() => revisionComparerContainer.setState({ sourceRevision: revision })}
+                value={revisionId}
+                checked={revisionId === sourceRevision?._id}
+                onChange={() => onChangeSourceInvoked(revision)}
               />
               />
-              <label className="custom-control-label" htmlFor={`compareSource-${revision._id}`} />
+              <label className="custom-control-label" htmlFor={`compareSource-${revisionId}`} />
             </div>
             </div>
           )}
           )}
         </td>
         </td>
         <td className="col-2">
         <td className="col-2">
-          {(hasDiff || revision._id === targetRevision?._id) && (
+          {(hasDiff || revisionId === targetRevision?._id) && (
             <div className="custom-control custom-radio custom-control-inline mr-0">
             <div className="custom-control custom-radio custom-control-inline mr-0">
               <input
               <input
                 type="radio"
                 type="radio"
                 className="custom-control-input"
                 className="custom-control-input"
-                id={`compareTarget-${revision._id}`}
+                id={`compareTarget-${revisionId}`}
                 name="compareTarget"
                 name="compareTarget"
-                value={revision._id}
-                checked={revision._id === targetRevision?._id}
-                onChange={() => revisionComparerContainer.setState({ targetRevision: revision })}
+                value={revisionId}
+                checked={revisionId === targetRevision?._id}
+                onChange={() => onChangeTargetInvoked(revision)}
               />
               />
-              <label className="custom-control-label" htmlFor={`compareTarget-${revision._id}`} />
+              <label className="custom-control-label" htmlFor={`compareTarget-${revisionId}`} />
             </div>
             </div>
           )}
           )}
         </td>
         </td>
@@ -106,16 +101,18 @@ class PageRevisionTable extends React.Component {
   }
   }
 
 
   render() {
   render() {
-    const { t, pageHistoryContainer } = this.props;
+    const { t, pagingLimit } = this.props;
 
 
     const revisions = this.props.revisions;
     const revisions = this.props.revisions;
     const revisionCount = this.props.revisions.length;
     const revisionCount = this.props.revisions.length;
+    const latestRevision = revisions[0];
+    const oldestRevision = revisions[revisions.length - 1];
 
 
     let hasDiffPrev;
     let hasDiffPrev;
 
 
     const revisionList = this.props.revisions.map((revision, idx) => {
     const revisionList = this.props.revisions.map((revision, idx) => {
       // Returns null because the last revision is for the bottom diff display
       // Returns null because the last revision is for the bottom diff display
-      if (idx === pageHistoryContainer.state.pagingLimit) {
+      if (idx === pagingLimit) {
         return null;
         return null;
       }
       }
 
 
@@ -127,13 +124,13 @@ class PageRevisionTable extends React.Component {
         previousRevision = revision; // if it is the first revision, show full text as diff text
         previousRevision = revision; // if it is the first revision, show full text as diff text
       }
       }
 
 
+      const isOldestRevision = revision === oldestRevision;
 
 
       const hasDiff = revision.hasDiffToPrev !== false; // set 'true' if undefined for backward compatibility
       const hasDiff = revision.hasDiffToPrev !== false; // set 'true' if undefined for backward compatibility
-      const isContiguousNodiff = !hasDiff && !hasDiffPrev;
 
 
       hasDiffPrev = hasDiff;
       hasDiffPrev = hasDiff;
 
 
-      return this.renderRow(revision, previousRevision, hasDiff, isContiguousNodiff);
+      return this.renderRow(revision, previousRevision, latestRevision, isOldestRevision, hasDiff);
     });
     });
 
 
     return (
     return (
@@ -156,11 +153,13 @@ class PageRevisionTable extends React.Component {
 
 
 PageRevisionTable.propTypes = {
 PageRevisionTable.propTypes = {
   t: PropTypes.func.isRequired, // i18next
   t: PropTypes.func.isRequired, // i18next
-  pageHistoryContainer: PropTypes.instanceOf(PageHistroyContainer).isRequired,
-  revisionComparerContainer: PropTypes.instanceOf(RevisionComparerContainer).isRequired,
 
 
   revisions: PropTypes.array,
   revisions: PropTypes.array,
-  diffOpened: PropTypes.object,
+  pagingLimit: PropTypes.number,
+  sourceRevision: PropTypes.instanceOf(Object),
+  targetRevision: PropTypes.instanceOf(Object),
+  onChangeSourceInvoked: PropTypes.func.isRequired,
+  onChangeTargetInvoked: PropTypes.func.isRequired,
 };
 };
 
 
 const PageRevisionTableWrapperFC = (props) => {
 const PageRevisionTableWrapperFC = (props) => {

+ 2 - 2
packages/app/src/components/PageHistory/Revision.jsx

@@ -1,7 +1,8 @@
 import React from 'react';
 import React from 'react';
-import PropTypes from 'prop-types';
 
 
 import { UserPicture } from '@growi/ui';
 import { UserPicture } from '@growi/ui';
+import PropTypes from 'prop-types';
+
 import UserDate from '../User/UserDate';
 import UserDate from '../User/UserDate';
 import Username from '../User/Username';
 import Username from '../User/Username';
 
 
@@ -83,6 +84,5 @@ Revision.propTypes = {
   t: PropTypes.func.isRequired, // i18next
   t: PropTypes.func.isRequired, // i18next
   revision: PropTypes.object,
   revision: PropTypes.object,
   isLatestRevision: PropTypes.bool.isRequired,
   isLatestRevision: PropTypes.bool.isRequired,
-  revisionDiffOpened: PropTypes.bool.isRequired,
   hasDiff: PropTypes.bool.isRequired,
   hasDiff: PropTypes.bool.isRequired,
 };
 };

+ 24 - 26
packages/app/src/components/RevisionComparer/RevisionComparer.jsx

@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React, { useState, useEffect } from 'react';
 
 
 import { pagePathUtils } from '@growi/core';
 import { pagePathUtils } from '@growi/core';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
@@ -8,11 +8,9 @@ import {
   Dropdown, DropdownToggle, DropdownMenu, DropdownItem,
   Dropdown, DropdownToggle, DropdownMenu, DropdownItem,
 } from 'reactstrap';
 } from 'reactstrap';
 
 
-
-import RevisionComparerContainer from '~/client/services/RevisionComparerContainer';
+import { useCurrentPagePath } from '~/stores/context';
 
 
 import RevisionDiff from '../PageHistory/RevisionDiff';
 import RevisionDiff from '../PageHistory/RevisionDiff';
-import { withUnstatedContainers } from '../UnstatedUtils';
 
 
 
 
 const { encodeSpaces } = pagePathUtils;
 const { encodeSpaces } = pagePathUtils;
@@ -29,12 +27,13 @@ const DropdownItemContents = ({ title, contents }) => (
 
 
 const RevisionComparer = (props) => {
 const RevisionComparer = (props) => {
 
 
-  const [dropdownOpen, setDropdownOpen] = useState(false);
-
   const { t } = useTranslation();
   const { t } = useTranslation();
-  const { revisionComparerContainer } = props;
-
-  const { path, pageId } = revisionComparerContainer.pageContainer.state;
+  const { data: currentPagePath } = useCurrentPagePath();
+  const [dropdownOpen, setDropdownOpen] = useState(false);
+  const {
+    sourceRevision, targetRevision,
+    currentPageId,
+  } = props;
 
 
   function toggleDropdown() {
   function toggleDropdown() {
     setDropdownOpen(!dropdownOpen);
     setDropdownOpen(!dropdownOpen);
@@ -42,7 +41,6 @@ const RevisionComparer = (props) => {
 
 
   const generateURL = (pathName) => {
   const generateURL = (pathName) => {
     const { origin } = window.location;
     const { origin } = window.location;
-    const { sourceRevision, targetRevision } = revisionComparerContainer.state;
 
 
     const url = new URL(pathName, origin);
     const url = new URL(pathName, origin);
 
 
@@ -55,13 +53,17 @@ const RevisionComparer = (props) => {
 
 
   };
   };
 
 
-  const { sourceRevision, targetRevision } = revisionComparerContainer.state;
-
+  let isNodiff;
   if (sourceRevision == null || targetRevision == null) {
   if (sourceRevision == null || targetRevision == null) {
-    return null;
+    isNodiff = true;
+  }
+  else {
+    isNodiff = sourceRevision._id === targetRevision._id;
   }
   }
 
 
-  const isNodiff = sourceRevision._id === targetRevision._id;
+  if (currentPageId == null || currentPagePath == null) {
+    return <>{ t('not_found_page.page_not_exist')}</>;
+  }
 
 
   return (
   return (
     <div className="revision-compare">
     <div className="revision-compare">
@@ -80,15 +82,15 @@ const RevisionComparer = (props) => {
           </DropdownToggle>
           </DropdownToggle>
           <DropdownMenu positionFixed right modifiers={{ preventOverflow: { boundariesElement: undefined } }}>
           <DropdownMenu positionFixed right modifiers={{ preventOverflow: { boundariesElement: undefined } }}>
             {/* Page path URL */}
             {/* Page path URL */}
-            <CopyToClipboard text={generateURL(path)}>
+            <CopyToClipboard text={generateURL(currentPagePath)}>
               <DropdownItem className="px-3">
               <DropdownItem className="px-3">
-                <DropdownItemContents title={t('copy_to_clipboard.Page URL')} contents={generateURL(path)} />
+                <DropdownItemContents title={t('copy_to_clipboard.Page URL')} contents={generateURL(currentPagePath)} />
               </DropdownItem>
               </DropdownItem>
             </CopyToClipboard>
             </CopyToClipboard>
             {/* Permanent Link URL */}
             {/* Permanent Link URL */}
-            <CopyToClipboard text={generateURL(pageId)}>
+            <CopyToClipboard text={generateURL(currentPageId)}>
               <DropdownItem className="px-3">
               <DropdownItem className="px-3">
-                <DropdownItemContents title={t('copy_to_clipboard.Permanent link')} contents={generateURL(pageId)} />
+                <DropdownItemContents title={t('copy_to_clipboard.Permanent link')} contents={generateURL(currentPageId)} />
               </DropdownItem>
               </DropdownItem>
             </CopyToClipboard>
             </CopyToClipboard>
             <DropdownItem divider className="my-0"></DropdownItem>
             <DropdownItem divider className="my-0"></DropdownItem>
@@ -115,13 +117,9 @@ const RevisionComparer = (props) => {
 };
 };
 
 
 RevisionComparer.propTypes = {
 RevisionComparer.propTypes = {
-  revisionComparerContainer: PropTypes.instanceOf(RevisionComparerContainer).isRequired,
-  revisions: PropTypes.array,
+  sourceRevision: PropTypes.instanceOf(Object),
+  targetRevision: PropTypes.instanceOf(Object),
+  currentPageId: PropTypes.string,
 };
 };
 
 
-/**
- * Wrapper component for using unstated
- */
-const RevisionComparerWrapper = withUnstatedContainers(RevisionComparer, [RevisionComparerContainer]);
-
-export default RevisionComparerWrapper;
+export default RevisionComparer;

+ 31 - 30
packages/app/src/interfaces/activity.ts

@@ -8,10 +8,10 @@ const MODEL_COMMENT = 'Comment';
 
 
 // Action
 // Action
 const ACTION_UNSETTLED = 'UNSETTLED';
 const ACTION_UNSETTLED = 'UNSETTLED';
-const ACTION_REGISTRATION_SUCCESS = 'REGISTRATION_SUCCESS';
-const ACTION_LOGIN_SUCCESS = 'LOGIN_SUCCESS';
-const ACTION_LOGIN_FAILURE = 'LOGIN_FAILURE';
-const ACTION_LOGOUT = 'LOGOUT';
+const ACTION_USER_REGISTRATION_SUCCESS = 'USER_REGISTRATION_SUCCESS';
+const ACTION_USER_LOGIN_SUCCESS = 'USER_LOGIN_SUCCESS';
+const ACTION_USER_LOGIN_FAILURE = 'USER_LOGIN_FAILURE';
+const ACTION_USER_LOGOUT = 'USER_LOGOUT';
 const ACTION_USER_PERSONAL_SETTINGS_UPDATE = 'USER_PERSONAL_SETTINGS_UPDATE';
 const ACTION_USER_PERSONAL_SETTINGS_UPDATE = 'USER_PERSONAL_SETTINGS_UPDATE';
 const ACTION_USER_IMAGE_TYPE_UPDATE = 'USER_IMAGE_TYPE_UPDATE';
 const ACTION_USER_IMAGE_TYPE_UPDATE = 'USER_IMAGE_TYPE_UPDATE';
 const ACTION_USER_LDAP_ACCOUNT_ASSOCIATE = 'USER_LDAP_ACCOUNT_ASSOCIATE';
 const ACTION_USER_LDAP_ACCOUNT_ASSOCIATE = 'USER_LDAP_ACCOUNT_ASSOCIATE';
@@ -108,12 +108,19 @@ export const SupportedEventModel = {
   MODEL_COMMENT,
   MODEL_COMMENT,
 } as const;
 } as const;
 
 
+export const SupportedActionCategory = {
+  PAGE: 'Page',
+  COMMENT: 'Comment',
+  USER: 'User',
+  ADMIN: 'Admin',
+} as const;
+
 export const SupportedAction = {
 export const SupportedAction = {
   ACTION_UNSETTLED,
   ACTION_UNSETTLED,
-  ACTION_REGISTRATION_SUCCESS,
-  ACTION_LOGIN_SUCCESS,
-  ACTION_LOGIN_FAILURE,
-  ACTION_LOGOUT,
+  ACTION_USER_REGISTRATION_SUCCESS,
+  ACTION_USER_LOGIN_SUCCESS,
+  ACTION_USER_LOGIN_FAILURE,
+  ACTION_USER_LOGOUT,
   ACTION_USER_PERSONAL_SETTINGS_UPDATE,
   ACTION_USER_PERSONAL_SETTINGS_UPDATE,
   ACTION_USER_IMAGE_TYPE_UPDATE,
   ACTION_USER_IMAGE_TYPE_UPDATE,
   ACTION_USER_LDAP_ACCOUNT_ASSOCIATE,
   ACTION_USER_LDAP_ACCOUNT_ASSOCIATE,
@@ -222,9 +229,9 @@ export const ActionGroupSize = {
 } as const;
 } as const;
 
 
 export const SmallActionGroup = {
 export const SmallActionGroup = {
-  ACTION_LOGIN_SUCCESS,
-  ACTION_LOGIN_FAILURE,
-  ACTION_LOGOUT,
+  ACTION_USER_LOGIN_SUCCESS,
+  ACTION_USER_LOGIN_FAILURE,
+  ACTION_USER_LOGOUT,
   ACTION_PAGE_CREATE,
   ACTION_PAGE_CREATE,
   ACTION_PAGE_DELETE,
   ACTION_PAGE_DELETE,
 } as const;
 } as const;
@@ -232,6 +239,7 @@ export const SmallActionGroup = {
 // SmallActionGroup + Action by all General Users - PAGE_VIEW
 // SmallActionGroup + Action by all General Users - PAGE_VIEW
 export const MediumActionGroup = {
 export const MediumActionGroup = {
   ...SmallActionGroup,
   ...SmallActionGroup,
+  ACTION_USER_REGISTRATION_SUCCESS,
   ACTION_USER_PERSONAL_SETTINGS_UPDATE,
   ACTION_USER_PERSONAL_SETTINGS_UPDATE,
   ACTION_USER_IMAGE_TYPE_UPDATE,
   ACTION_USER_IMAGE_TYPE_UPDATE,
   ACTION_USER_LDAP_ACCOUNT_ASSOCIATE,
   ACTION_USER_LDAP_ACCOUNT_ASSOCIATE,
@@ -324,25 +332,6 @@ export const LargeActionGroup = {
   ACTION_ADMIN_SEARCH_INDICES_REBUILD,
   ACTION_ADMIN_SEARCH_INDICES_REBUILD,
 } as const;
 } as const;
 
 
-/*
- * For AuditLogManagement.tsx
- */
-export const PageActions = Object.values({
-  ACTION_PAGE_LIKE,
-  ACTION_PAGE_BOOKMARK,
-  ACTION_PAGE_CREATE,
-  ACTION_PAGE_UPDATE,
-  ACTION_PAGE_RENAME,
-  ACTION_PAGE_DUPLICATE,
-  ACTION_PAGE_DELETE,
-  ACTION_PAGE_DELETE_COMPLETELY,
-  ACTION_PAGE_REVERT,
-} as const);
-
-export const CommentActions = Object.values({
-  ACTION_COMMENT_CREATE,
-  ACTION_COMMENT_UPDATE,
-} as const);
 
 
 /*
 /*
  * Array
  * Array
@@ -355,12 +344,24 @@ export const AllSmallGroupActions = Object.values(SmallActionGroup);
 export const AllMediumGroupActions = Object.values(MediumActionGroup);
 export const AllMediumGroupActions = Object.values(MediumActionGroup);
 export const AllLargeGroupActions = Object.values(LargeActionGroup);
 export const AllLargeGroupActions = Object.values(LargeActionGroup);
 
 
+// Action categories(for SelectActionDropdown.tsx)
+const pageRegExp = new RegExp(`^${SupportedActionCategory.PAGE.toUpperCase()}_`);
+const commentRegExp = new RegExp(`^${SupportedActionCategory.COMMENT.toUpperCase()}_`);
+const userRegExp = new RegExp(`^${SupportedActionCategory.USER.toUpperCase()}_`);
+const adminRegExp = new RegExp(`^${SupportedActionCategory.ADMIN.toUpperCase()}_`);
+
+export const PageActions = AllSupportedActions.filter(action => action.match(pageRegExp));
+export const CommentActions = AllSupportedActions.filter(action => action.match(commentRegExp));
+export const UserActions = AllSupportedActions.filter(action => action.match(userRegExp));
+export const AdminActions = AllSupportedActions.filter(action => action.match(adminRegExp));
+
 /*
 /*
  * Type
  * Type
  */
  */
 export type SupportedTargetModelType = typeof SupportedTargetModel[keyof typeof SupportedTargetModel];
 export type SupportedTargetModelType = typeof SupportedTargetModel[keyof typeof SupportedTargetModel];
 export type SupportedEventModelType = typeof SupportedEventModel[keyof typeof SupportedEventModel];
 export type SupportedEventModelType = typeof SupportedEventModel[keyof typeof SupportedEventModel];
 export type SupportedActionType = typeof SupportedAction[keyof typeof SupportedAction];
 export type SupportedActionType = typeof SupportedAction[keyof typeof SupportedAction];
+export type SupportedActionCategoryType = typeof SupportedActionCategory[keyof typeof SupportedActionCategory]
 
 
 export type ISnapshot = Partial<Pick<IUser, 'username'>>
 export type ISnapshot = Partial<Pick<IUser, 'username'>>
 
 

+ 5 - 0
packages/app/src/interfaces/revision.ts

@@ -8,6 +8,11 @@ export type IRevision = {
   updatedAt: Date,
   updatedAt: Date,
 }
 }
 
 
+export type IRevisionsForPagination = {
+  revisions: IRevision[], // revisions in one pagination
+  totalCounts: number // total counts
+}
+
 export type IRevisionOnConflict = {
 export type IRevisionOnConflict = {
   revisionId: string,
   revisionId: string,
   revisionBody: string,
   revisionBody: string,

+ 1 - 1
packages/app/src/server/routes/apiv3/logout.js

@@ -17,7 +17,7 @@ module.exports = (crowi) => {
     req.session.destroy();
     req.session.destroy();
 
 
     const activityId = res.locals.activity._id;
     const activityId = res.locals.activity._id;
-    const parameters = { action: SupportedAction.ACTION_LOGOUT };
+    const parameters = { action: SupportedAction.ACTION_USER_LOGOUT };
     activityEvent.emit('update', activityId, parameters);
     activityEvent.emit('update', activityId, parameters);
 
 
     return res.send();
     return res.send();

+ 1 - 1
packages/app/src/server/routes/installer.js

@@ -59,7 +59,7 @@ module.exports = function(crowi) {
 
 
       req.flash('successMessage', req.t('message.complete_to_install2'));
       req.flash('successMessage', req.t('message.complete_to_install2'));
 
 
-      const parameters = { action: SupportedAction.ACTION_REGISTRATION_SUCCESS };
+      const parameters = { action: SupportedAction.ACTION_USER_REGISTRATION_SUCCESS };
       activityEvent.emit('update', res.locals.activity._id, parameters);
       activityEvent.emit('update', res.locals.activity._id, parameters);
 
 
       return res.redirect('/');
       return res.redirect('/');

+ 2 - 2
packages/app/src/server/routes/login-passport.js

@@ -33,7 +33,7 @@ module.exports = function(crowi, app) {
     // remove session.redirectTo
     // remove session.redirectTo
     delete req.session.redirectTo;
     delete req.session.redirectTo;
 
 
-    const parameters = { action: SupportedAction.ACTION_LOGIN_SUCCESS };
+    const parameters = { action: SupportedAction.ACTION_USER_LOGIN_SUCCESS };
     activityEvent.emit('update', res.locals.activity._id, parameters);
     activityEvent.emit('update', res.locals.activity._id, parameters);
 
 
     return res.safeRedirect(redirectTo);
     return res.safeRedirect(redirectTo);
@@ -47,7 +47,7 @@ module.exports = function(crowi, app) {
   const loginFailureHandler = async(req, res, message) => {
   const loginFailureHandler = async(req, res, message) => {
     req.flash('errorMessage', message || req.t('message.sign_in_failure'));
     req.flash('errorMessage', message || req.t('message.sign_in_failure'));
 
 
-    const parameters = { action: SupportedAction.ACTION_LOGIN_FAILURE };
+    const parameters = { action: SupportedAction.ACTION_USER_LOGIN_FAILURE };
     activityEvent.emit('update', res.locals.activity._id, parameters);
     activityEvent.emit('update', res.locals.activity._id, parameters);
 
 
     return res.redirect('/login');
     return res.redirect('/login');

+ 1 - 1
packages/app/src/server/routes/login.js

@@ -40,7 +40,7 @@ module.exports = function(crowi, app) {
       // remove session.redirectTo
       // remove session.redirectTo
       delete req.session.redirectTo;
       delete req.session.redirectTo;
 
 
-      const parameters = { action: SupportedAction.ACTION_REGISTRATION_SUCCESS };
+      const parameters = { action: SupportedAction.ACTION_USER_REGISTRATION_SUCCESS };
       activityEvent.emit('update', res.locals.activity._id, parameters);
       activityEvent.emit('update', res.locals.activity._id, parameters);
 
 
       return res.safeRedirect(redirectTo);
       return res.safeRedirect(redirectTo);

+ 21 - 0
packages/app/src/stores/page.tsx

@@ -9,6 +9,7 @@ import {
 } from '~/interfaces/page';
 } from '~/interfaces/page';
 import { IRecordApplicableGrant, IResIsGrantNormalized } from '~/interfaces/page-grant';
 import { IRecordApplicableGrant, IResIsGrantNormalized } from '~/interfaces/page-grant';
 import { IPagingResult } from '~/interfaces/paging-result';
 import { IPagingResult } from '~/interfaces/paging-result';
+import { IRevisionsForPagination } from '~/interfaces/revision';
 
 
 import { apiGet } from '../client/util/apiv1-client';
 import { apiGet } from '../client/util/apiv1-client';
 import { Nullable } from '../interfaces/common';
 import { Nullable } from '../interfaces/common';
@@ -158,6 +159,26 @@ export const useSWRxPageInfoForList = (
   };
   };
 };
 };
 
 
+export const useSWRxPageRevisions = (
+    pageId: string,
+    page: number, // page number of pagination
+    limit: number, // max number of pages in one paginate
+): SWRResponse<IRevisionsForPagination, Error> => {
+
+  return useSWRImmutable<IRevisionsForPagination, Error>(
+    ['/revisions/list', pageId, page, limit],
+    (endpoint, pageId, page, limit) => {
+      return apiv3Get(endpoint, { pageId, page, limit }).then((response) => {
+        const revisions = {
+          revisions: response.data.docs,
+          totalCounts: response.data.totalDocs,
+        };
+        return revisions;
+      });
+    },
+  );
+};
+
 /*
 /*
  * Grant normalization fetching hooks
  * Grant normalization fetching hooks
  */
  */

+ 7 - 0
packages/app/src/styles/_admin.scss

@@ -228,6 +228,13 @@ $slack-work-space-name-card-border: #efc1f6;
   //   }
   //   }
   // }
   // }
 
 
+  .admin-audit-log {
+    .select-action-dropdown {
+      max-height: 500px;
+      overflow-y: auto;
+    }
+  }
+
   #layoutOptions {
   #layoutOptions {
     .customize-layout-card {
     .customize-layout-card {
       border: 4px solid $border-color;
       border: 4px solid $border-color;