Yuki Takei 3 лет назад
Родитель
Сommit
13db27813a

+ 1 - 1
packages/app/src/components/Admin/UserManagement.jsx

@@ -75,7 +75,7 @@ class UserManagement extends React.Component {
     try {
       adminUsersContainer.resetAllChanges();
       this.searchUserElement.value = '';
-      this.state.isNotifyCommentShow = false;
+      this.setState({ isNotifyCommentShow: false });
     }
     catch (err) {
       toastError(err);

+ 5 - 8
packages/app/src/components/DescendantsPageListModal.tsx

@@ -1,27 +1,24 @@
 
 import React, { useState, useMemo } from 'react';
-import { useTranslation } from 'react-i18next';
 
+import { useTranslation } from 'react-i18next';
 import {
   Modal, ModalHeader, ModalBody,
 } from 'reactstrap';
 
-import { useDescendantsPageListModal } from '~/stores/modal';
 import { useIsSharedUser } from '~/stores/context';
+import { useDescendantsPageListModal } from '~/stores/modal';
 
+import { CustomNavTab } from './CustomNavigation/CustomNav';
+import CustomTabContent from './CustomNavigation/CustomTabContent';
 import { DescendantsPageList } from './DescendantsPageList';
 import ExpandOrContractButton from './ExpandOrContractButton';
-import { CustomNavTab } from './CustomNavigation/CustomNav';
 import PageListIcon from './Icons/PageListIcon';
 import TimeLineIcon from './Icons/TimeLineIcon';
-import CustomTabContent from './CustomNavigation/CustomTabContent';
 import PageTimeline from './PageTimeline';
 
 
-type Props = {
-}
-
-export const DescendantsPageListModal = (props: Props): JSX.Element => {
+export const DescendantsPageListModal = (): JSX.Element => {
   const { t } = useTranslation();
 
   const [activeTab, setActiveTab] = useState('pagelist');

+ 1 - 2
packages/app/src/components/Me/EditorSettings.tsx

@@ -9,8 +9,7 @@ import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { apiv3Get, apiv3Put } from '~/client/util/apiv3-client';
 
 
-type EditorSettingsBodyProps = {
-}
+type EditorSettingsBodyProps = Record<string, never>;
 
 type RuleListGroupProps = {
   title: string;

+ 4 - 7
packages/app/src/components/Me/UserSettings.tsx

@@ -1,15 +1,11 @@
-import React, { FC } from 'react';
+import React from 'react';
 
 import { useTranslation } from 'react-i18next';
 
 import BasicInfoSettings from './BasicInfoSettings';
 import ProfileImageSettings from './ProfileImageSettings';
 
-type Props = {
-
-};
-
-const UserSettings: FC<Props> = () => {
+const UserSettings = React.memo((): JSX.Element => {
   const { t } = useTranslation();
 
   return (
@@ -24,6 +20,7 @@ const UserSettings: FC<Props> = () => {
       </div>
     </div>
   );
-};
+});
+UserSettings.displayName = 'UserSettings';
 
 export default UserSettings;

+ 1 - 0
packages/app/src/components/Page/FixPageGrantAlert.tsx

@@ -188,6 +188,7 @@ const FixPageGrantModal = (props: ModalProps): JSX.Element => {
                     {
                       applicableGroups != null && applicableGroups.map(g => (
                         <button
+                          key={g._id}
                           className="dropdown-item"
                           type="button"
                           onClick={() => setSelectedGroup(g)}

+ 4 - 7
packages/app/src/components/Page/RedirectedAlert.tsx

@@ -1,12 +1,8 @@
-import React, { FC } from 'react';
+import React from 'react';
 
 import { useTranslation } from 'react-i18next';
 
-type Props = {
-
-}
-
-const RedirectedAlert: FC<Props> = () => {
+const RedirectedAlert = React.memo((): JSX.Element => {
   const { t } = useTranslation();
   const urlParams = new URLSearchParams(window.location.search);
   const fromPath = urlParams.get('redirectFrom');
@@ -16,6 +12,7 @@ const RedirectedAlert: FC<Props> = () => {
       <strong>{ t('Redirected') }:</strong> { t('page_page.notice.redirected')} <code>{fromPath}</code> {t('page_page.notice.redirected_period')}
     </>
   );
-};
+});
+RedirectedAlert.displayName = 'RedirectedAlert';
 
 export default RedirectedAlert;

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

@@ -26,7 +26,7 @@ export default class RevisionBody extends React.PureComponent {
     }
   }
 
-  componentWillReceiveProps(nextProps) {
+  UNSAFE_componentWillReceiveProps(nextProps) {
     const MathJax = window.MathJax;
     if (MathJax != null && this.props.isMathJaxEnabled && this.props.renderMathJaxOnInit) {
       this.renderMathJaxWithDebounce();

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

@@ -49,7 +49,7 @@ class PageStatusAlert extends React.Component {
         <i className="icon-fw icon-people"></i>
         {t('hackmd.someone_editing')}
       </>,
-      <a href="#hackmd" className="btn btn-outline-white">
+      <a href="#hackmd" key="btnOpenHackmdSomeoneEditing" className="btn btn-outline-white">
         <i className="fa fa-fw fa-file-text-o mr-1"></i>
         Open HackMD Editor
       </a>,
@@ -64,7 +64,7 @@ class PageStatusAlert extends React.Component {
         <i className="icon-fw icon-pencil"></i>
         {t('hackmd.this_page_has_draft')}
       </>,
-      <a href="#hackmd" className="btn btn-outline-white">
+      <a href="#hackmd" key="btnOpenHackmdPageHasDraft" className="btn btn-outline-white">
         <i className="fa fa-fw fa-file-text-o mr-1"></i>
         Open HackMD Editor
       </a>,

+ 3 - 6
packages/app/src/components/PrivateLegacyPagesMigrationModal.tsx

@@ -1,8 +1,9 @@
 import React, { useState } from 'react';
+
+import { useTranslation } from 'react-i18next';
 import {
   Modal, ModalHeader, ModalBody, ModalFooter,
 } from 'reactstrap';
-import { useTranslation } from 'react-i18next';
 
 import { apiv3Post } from '~/client/util/apiv3-client';
 import { usePrivateLegacyPagesMigrationModal } from '~/stores/modal';
@@ -10,11 +11,7 @@ import { usePrivateLegacyPagesMigrationModal } from '~/stores/modal';
 import ApiErrorMessageList from './PageManagement/ApiErrorMessageList';
 
 
-type Props = {
-
-}
-
-export const PrivateLegacyPagesMigrationModal = (props: Props): JSX.Element => {
+export const PrivateLegacyPagesMigrationModal = (): JSX.Element => {
   const { t } = useTranslation();
 
   const { data: status, close } = usePrivateLegacyPagesMigrationModal();

+ 1 - 0
packages/app/src/components/ShareLink/ShareLinkList.tsx

@@ -76,6 +76,7 @@ const ShareLinkList = (props: Props): JSX.Element => {
       <>
         {props.shareLinks.map(shareLink => (
           <ShareLinkTr
+            key={shareLink._id}
             isAdmin={props.isAdmin}
             shareLink={shareLink}
             onDelete={() => {

+ 4 - 8
packages/app/src/components/Sidebar.tsx

@@ -1,5 +1,5 @@
 import React, {
-  FC, useCallback, useEffect, useRef, useState,
+  useCallback, useEffect, useRef, useState,
 } from 'react';
 
 import { useUserUISettings } from '~/client/services/user-ui-settings';
@@ -13,10 +13,9 @@ import {
 } from '~/stores/ui';
 
 import DrawerToggler from './Navbar/DrawerToggler';
-
-import SidebarNav from './Sidebar/SidebarNav';
-import SidebarContents from './Sidebar/SidebarContents';
 import { NavigationResizeHexagon } from './Sidebar/NavigationResizeHexagon';
+import SidebarContents from './Sidebar/SidebarContents';
+import SidebarNav from './Sidebar/SidebarNav';
 import { StickyStretchableScroller } from './StickyStretchableScroller';
 
 const sidebarMinWidth = 240;
@@ -80,10 +79,7 @@ const SidebarContentsWrapper = () => {
 };
 
 
-type Props = {
-}
-
-const Sidebar: FC<Props> = (props: Props) => {
+const Sidebar = (): JSX.Element => {
   const { data: isDrawerMode } = useDrawerMode();
   const { data: isDrawerOpened, mutate: mutateDrawerOpened } = useDrawerOpened();
   const { data: currentProductNavWidth, mutate: mutateProductNavWidth } = useCurrentProductNavWidth();

+ 4 - 7
packages/app/src/components/Sidebar/NavigationResizeHexagon.tsx

@@ -1,10 +1,6 @@
-import React, { FC } from 'react';
+import React from 'react';
 
-type Props = {
-
-};
-
-export const NavigationResizeHexagon: FC<Props> = () => (
+export const NavigationResizeHexagon = React.memo((): JSX.Element => (
   <svg
     xmlns="http://www.w3.org/2000/svg"
     viewBox="0 0 27.691 23.999"
@@ -17,4 +13,5 @@ export const NavigationResizeHexagon: FC<Props> = () => (
       <path d="M2.124,9.114l5.28,5.34a.647.647,0,0,0,.922,0l.616-.623a.665.665,0,0,0,0-.932L4.759,8.648,8.943,4.4a.665.665,0,0,0,0-.932l-.616-.623a.647.647,0,0,0-.922,0l-5.28,5.34A.665.665,0,0,0,2.124,9.114Z" transform="translate(-1.933 -2.648)"></path>
     </g>
   </svg>
-);
+));
+NavigationResizeHexagon.displayName = 'NavigationResizeHexagon';

+ 4 - 6
packages/app/src/components/Sidebar/SidebarContents.tsx

@@ -1,16 +1,14 @@
-import React, { FC } from 'react';
+import React from 'react';
 
 import { SidebarContentsType } from '~/interfaces/ui';
 import { useCurrentSidebarContents } from '~/stores/ui';
-import RecentChanges from './RecentChanges';
+
 import CustomSidebar from './CustomSidebar';
 import PageTree from './PageTree';
+import RecentChanges from './RecentChanges';
 import Tag from './Tag';
 
-type Props = {
-};
-
-const SidebarContents: FC<Props> = (props: Props) => {
+const SidebarContents = (): JSX.Element => {
   const { data: currentSidebarContents } = useCurrentSidebarContents();
 
   let Contents;

+ 3 - 4
packages/app/src/pages/[[...path]].page.tsx

@@ -1,6 +1,7 @@
 import React, { useEffect } from 'react';
 
 import { pagePathUtils } from '@growi/core';
+import { isValidObjectId } from 'mongoose';
 import {
   NextPage, GetServerSideProps, GetServerSidePropsContext,
 } from 'next';
@@ -16,6 +17,7 @@ import { CrowiRequest } from '~/interfaces/crowi-request';
 // import { useRendererSettings } from '~/stores/renderer';
 // import { EditorMode, useEditorMode, useIsMobile } from '~/stores/ui';
 import { IPageWithMeta } from '~/interfaces/page';
+import { PageModel } from '~/server/models/page';
 import { serializeUserSecurely } from '~/server/models/serializers/user-serializer';
 import { useSWRxCurrentPage, useSWRxPageInfo } from '~/stores/page';
 import loggerFactory from '~/utils/logger';
@@ -41,8 +43,6 @@ import {
 } from '../stores/context';
 
 import { CommonProps, getServerSideCommonProps, useCustomTitle } from './commons';
-import { PageModel } from '~/server/models/page';
-import { isValidObjectId } from 'mongoose';
 // import { useCurrentPageSWR } from '../stores/page';
 
 
@@ -227,9 +227,8 @@ async function injectPageInformation(context: GetServerSidePropsContext, props:
   const { currentPathname } = props;
 
   // determine pageId
-  let pageId;
   const pageIdStr = currentPathname.substring(1);
-  pageId = isValidObjectId(pageIdStr) ? pageIdStr : null;
+  const pageId = isValidObjectId(pageIdStr) ? pageIdStr : null;
 
   const result: IPageWithMeta = await pageService.findPageAndMetaDataByViewer(pageId, currentPathname, user, true); // includeEmpty = true, isSharedPage = false
   const page = result.data;

+ 3 - 2
packages/app/src/pages/_document.page.tsx

@@ -22,7 +22,7 @@ async function importCustomManifest(): Promise<any> {
 
 class GrowiDocument extends Document<GrowiDocumentProps> {
 
-  static async getInitialProps(ctx: DocumentContext): Promise<GrowiDocumentInitialProps> {
+  static override async getInitialProps(ctx: DocumentContext): Promise<GrowiDocumentInitialProps> {
     const initialProps: DocumentInitialProps = await Document.getInitialProps(ctx);
 
     const customManifest: any = await importCustomManifest();
@@ -31,13 +31,14 @@ class GrowiDocument extends Document<GrowiDocumentProps> {
     return { ...initialProps, bootJsPath };
   }
 
-  render(): JSX.Element {
+  override render(): JSX.Element {
 
     const { bootJsPath } = this.props;
 
     return (
       <Html>
         <Head>
+          {/* eslint-disable-next-line @next/next/no-sync-scripts */}
           <script src={bootJsPath}></script>
           {/*
           {renderScriptTagsByGroup('basis')}

+ 3 - 2
packages/app/src/server/service/s2s-messaging/index.ts

@@ -1,4 +1,5 @@
 import loggerFactory from '~/utils/logger';
+
 import { S2sMessagingService } from './base';
 
 const logger = loggerFactory('growi:service:s2s-messaging:S2sMessagingServiceFactory');
@@ -51,9 +52,9 @@ class S2sMessagingServiceFactory {
 
     logger.info(`Config pub/sub server type '${type}' is set.`);
 
-    const module = envToModuleMappings[type];
+    const moduleFileName = envToModuleMappings[type];
 
-    const modulePath = `./${module}`;
+    const modulePath = `./${moduleFileName}`;
     // eslint-disable-next-line @typescript-eslint/no-var-requires
     this.delegator = require(modulePath)(crowi);
 

+ 12 - 13
packages/app/src/server/service/slack-integration.ts

@@ -1,23 +1,22 @@
-import mongoose from 'mongoose';
-
-import { IncomingWebhookSendArguments } from '@slack/webhook';
-import { ChatPostMessageArguments, WebClient } from '@slack/web-api';
-
 import {
   generateWebClient, GrowiCommand, InteractionPayloadAccessor, markdownSectionBlock, SlackbotType,
   RespondUtil, GrowiBotEvent,
 } from '@growi/slack';
+import { ChatPostMessageArguments, WebClient } from '@slack/web-api';
+import { IncomingWebhookSendArguments } from '@slack/webhook';
+import mongoose from 'mongoose';
+
 
 import loggerFactory from '~/utils/logger';
 
+import { EventActionsPermission } from '../interfaces/slack-integration/events';
 import S2sMessage from '../models/vo/s2s-message';
+import { SlackCommandHandlerError } from '../models/vo/slack-command-handler-error';
 
 import ConfigManager from './config-manager';
 import { S2sMessagingService } from './s2s-messaging/base';
 import { S2sMessageHandlable } from './s2s-messaging/handlable';
-import { SlackCommandHandlerError } from '../models/vo/slack-command-handler-error';
 import { LinkSharedEventHandler } from './slack-event-handler/link-shared';
-import { EventActionsPermission } from '../interfaces/slack-integration/events';
 
 const logger = loggerFactory('growi:service:SlackBotService');
 
@@ -245,11 +244,11 @@ export class SlackIntegrationService implements S2sMessageHandlable {
    */
   async handleCommandRequest(growiCommand: GrowiCommand, client, body, respondUtil: RespondUtil): Promise<void> {
     const { growiCommandType } = growiCommand;
-    const module = `./slack-command-handler/${growiCommandType}`;
+    const modulePath = `./slack-command-handler/${growiCommandType}`;
 
     let handler;
     try {
-      handler = require(module)(this.crowi);
+      handler = require(modulePath)(this.crowi);
     }
     catch (err) {
       const text = `*No command.*\n \`command: ${growiCommand.text}\``;
@@ -275,11 +274,11 @@ export class SlackIntegrationService implements S2sMessageHandlable {
     const commandName = actionId.split(':')[0];
     const handlerMethodName = actionId.split(':')[1];
 
-    const module = `./slack-command-handler/${commandName}`;
+    const modulePath = `./slack-command-handler/${commandName}`;
 
     let handler;
     try {
-      handler = require(module)(this.crowi);
+      handler = require(modulePath)(this.crowi);
     }
     catch (err) {
       throw new SlackCommandHandlerError(`No interaction.\n \`actionId: ${actionId}\``);
@@ -296,11 +295,11 @@ export class SlackIntegrationService implements S2sMessageHandlable {
     const commandName = callbackId.split(':')[0];
     const handlerMethodName = callbackId.split(':')[1];
 
-    const module = `./slack-command-handler/${commandName}`;
+    const modulePath = `./slack-command-handler/${commandName}`;
 
     let handler;
     try {
-      handler = require(module)(this.crowi);
+      handler = require(modulePath)(this.crowi);
     }
     catch (err) {
       throw new SlackCommandHandlerError(`No interaction.\n \`callbackId: ${callbackId}\``);

+ 10 - 10
packages/app/test/integration/service/v5.migration.test.js

@@ -34,7 +34,7 @@ describe('V5 page migration', () => {
   const pageId10 = new mongoose.Types.ObjectId();
   const pageId11 = new mongoose.Types.ObjectId();
 
-  const public = filter => ({ grant: Page.GRANT_PUBLIC, ...filter });
+  const onlyPublic = filter => ({ grant: Page.GRANT_PUBLIC, ...filter });
   const ownedByTestUser1 = filter => ({ grant: Page.GRANT_OWNER, grantedUsers: [testUser1._id], ...filter });
   const root = filter => ({ grantedUsers: [rootUser._id], ...filter });
   const rootUserGroup = filter => ({ grantedGroup: rootUserGroupId, ...filter });
@@ -502,8 +502,8 @@ describe('V5 page migration', () => {
 
     test('should not run normalization when the target page is GRANT_USER_GROUP surrounded by public pages', async() => {
       const mockMainOperation = jest.spyOn(crowi.pageService, 'normalizeParentRecursivelyMainOperation').mockImplementation(v => v);
-      const _page1 = await Page.findOne(public({ path: '/deep_path/normalize_a', ...empty }));
-      const _page2 = await Page.findOne(public({ path: '/deep_path/normalize_a/normalize_b', ...normalized }));
+      const _page1 = await Page.findOne(onlyPublic({ path: '/deep_path/normalize_a', ...empty }));
+      const _page2 = await Page.findOne(onlyPublic({ path: '/deep_path/normalize_a/normalize_b', ...normalized }));
       const _page3 = await Page.findOne(testUser1Group({ path: '/deep_path/normalize_a', ...notNormalized }));
       const _page4 = await Page.findOne(testUser1Group({ path: '/deep_path/normalize_c', ...notNormalized }));
 
@@ -521,7 +521,7 @@ describe('V5 page migration', () => {
     });
 
     test('should not include siblings', async() => {
-      const _page1 = await Page.findOne(public({ path: '/normalize_d', ...empty }));
+      const _page1 = await Page.findOne(onlyPublic({ path: '/normalize_d', ...empty }));
       const _page2 = await Page.findOne(testUser1Group({ path: '/normalize_d/normalize_e', ...normalized }));
       const _page3 = await Page.findOne(testUser1Group({ path: '/normalize_d', ...notNormalized }));
       const _page4 = await Page.findOne(testUser1Group({ path: '/normalize_f', ...notNormalized }));
@@ -537,7 +537,7 @@ describe('V5 page migration', () => {
       const page1 = await Page.findOne(testUser1Group({ path: '/normalize_d/normalize_e' }));
       const page2 = await Page.findOne(testUser1Group({ path: '/normalize_d' }));
       const page3 = await Page.findOne(testUser1Group({ path: '/normalize_f' }));
-      const empty4 = await Page.findOne(public({ path: '/normalize_d', ...empty }));
+      const empty4 = await Page.findOne(onlyPublic({ path: '/normalize_d', ...empty }));
 
       expect(page1).not.toBeNull();
       expect(page2).not.toBeNull();
@@ -556,7 +556,7 @@ describe('V5 page migration', () => {
     });
 
     test('should replace all unnecessary empty pages and normalization succeeds', async() => {
-      const _pageG = await Page.findOne(public({ path: '/normalize_g', ...normalized }));
+      const _pageG = await Page.findOne(onlyPublic({ path: '/normalize_g', ...normalized }));
       const _pageGH = await Page.findOne(ownedByTestUser1({ path: '/normalize_g/normalize_h', ...notNormalized }));
       const _pageGI = await Page.findOne(ownedByTestUser1({ path: '/normalize_g/normalize_i', ...notNormalized }));
       const _pageGHJ = await Page.findOne(ownedByTestUser1({ path: '/normalize_g/normalize_h/normalize_j', ...notNormalized }));
@@ -584,7 +584,7 @@ describe('V5 page migration', () => {
       expect(countGIK).toBe(1);
 
       // -- normalized pages
-      const pageG = await Page.findOne(public({ path: '/normalize_g' }));
+      const pageG = await Page.findOne(onlyPublic({ path: '/normalize_g' }));
       const emptyGH = await Page.findOne({ path: '/normalize_g/normalize_h', ...empty });
       const emptyGI = await Page.findOne({ path: '/normalize_g/normalize_i', ...empty });
       const pageGHJ = await Page.findOne({ path: '/normalize_g/normalize_h/normalize_j' });
@@ -1229,7 +1229,7 @@ describe('V5 page migration', () => {
 
     test('should normalize all granted pages under the path when an empty page exists at the path', async() => {
       const _emptyD = await Page.findOne({ path: '/norm_parent_by_path_D', ...empty, ...normalized });
-      const _pageDE = await Page.findOne(public({ path: '/norm_parent_by_path_D/norm_parent_by_path_E', ...normalized }));
+      const _pageDE = await Page.findOne(onlyPublic({ path: '/norm_parent_by_path_D/norm_parent_by_path_E', ...normalized }));
       const _pageDF = await Page.findOne(root({ path: '/norm_parent_by_path_D/norm_parent_by_path_F', ...notNormalized }));
 
       expect(_emptyD).not.toBeNull();
@@ -1268,8 +1268,8 @@ describe('V5 page migration', () => {
     });
 
     test('should normalize all granted pages under the path when a non-empty page exists at the path', async() => {
-      const _pageG = await Page.findOne(public({ path: '/norm_parent_by_path_G', ...normalized }));
-      const _pageGH = await Page.findOne(public({ path: '/norm_parent_by_path_G/norm_parent_by_path_H', ...normalized }));
+      const _pageG = await Page.findOne(onlyPublic({ path: '/norm_parent_by_path_G', ...normalized }));
+      const _pageGH = await Page.findOne(onlyPublic({ path: '/norm_parent_by_path_G/norm_parent_by_path_H', ...normalized }));
       const _pageGI = await Page.findOne(root({ path: '/norm_parent_by_path_G/norm_parent_by_path_I', ...notNormalized }));
 
       expect(_pageG).not.toBeNull();