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

Merge branch 'master' into support/omit-unstated

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

+ 11 - 5
packages/app/src/components/Admin/UserGroupDetail/UserGroupUserFormByInput.jsx

@@ -1,15 +1,17 @@
 import React from 'react';
-import PropTypes from 'prop-types';
-import { withTranslation } from 'react-i18next';
 
+import { UserPicture } from '@growi/ui';
+import PropTypes from 'prop-types';
 import { AsyncTypeahead } from 'react-bootstrap-typeahead';
+import { withTranslation } from 'react-i18next';
 import { debounce } from 'throttle-debounce';
-import { UserPicture } from '@growi/ui';
-import { withUnstatedContainers } from '../../UnstatedUtils';
-import AppContainer from '~/client/services/AppContainer';
+
 import AdminUserGroupDetailContainer from '~/client/services/AdminUserGroupDetailContainer';
+import AppContainer from '~/client/services/AppContainer';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 
+import { withUnstatedContainers } from '../../UnstatedUtils';
+
 class UserGroupUserFormByInput extends React.Component {
 
   constructor(props) {
@@ -44,12 +46,16 @@ class UserGroupUserFormByInput extends React.Component {
 
     try {
       await adminUserGroupDetailContainer.addUserByUsername(userName);
+      await adminUserGroupDetailContainer.init();
+      await adminUserGroupDetailContainer.closeUserGroupUserModal();
       toastSuccess(`Added "${this.xss.process(userName)}" to "${this.xss.process(userGroup.name)}"`);
       this.setState({ inputUser: '' });
     }
     catch (err) {
       toastError(new Error(`Unable to add "${this.xss.process(userName)}" to "${this.xss.process(userGroup.name)}"`));
     }
+
+
   }
 
   validateForm() {

+ 4 - 3
packages/app/src/components/Page/DisplaySwitcher.tsx

@@ -7,10 +7,10 @@ import { TabContent, TabPane } from 'reactstrap';
 
 import { smoothScrollIntoView } from '~/client/util/smooth-scroll';
 import {
-  useCurrentPagePath, useIsSharedUser, useIsEditable, useCurrentPageId, useIsUserPage, usePageUser,
+  useCurrentPagePath, useIsSharedUser, useIsEditable, useCurrentPageId, useIsUserPage, usePageUser, useShareLinkId,
 } from '~/stores/context';
 import { useDescendantsPageListModal } from '~/stores/modal';
-import { useSWRxPageByPath } from '~/stores/page';
+import { useSWRxCurrentPage } from '~/stores/page';
 import { EditorMode, useEditorMode } from '~/stores/ui';
 
 import CountBadge from '../Common/CountBadge';
@@ -41,10 +41,11 @@ const DisplaySwitcher = (): JSX.Element => {
   const { data: currentPageId } = useCurrentPageId();
   const { data: currentPath } = useCurrentPagePath();
   const { data: isSharedUser } = useIsSharedUser();
+  const { data: shareLinkId } = useShareLinkId();
   const { data: isUserPage } = useIsUserPage();
   const { data: isEditable } = useIsEditable();
   const { data: pageUser } = usePageUser();
-  const { data: currentPage } = useSWRxPageByPath(currentPath);
+  const { data: currentPage } = useSWRxCurrentPage(shareLinkId ?? undefined);
 
   const { data: editorMode } = useEditorMode();
 

+ 4 - 4
packages/app/src/server/routes/apiv3/page.js

@@ -245,8 +245,8 @@ module.exports = (crowi) => {
    *                schema:
    *                  $ref: '#/components/schemas/Page'
    */
-  router.get('/', accessTokenParser, loginRequired, validator.getPage, apiV3FormValidator, async(req, res) => {
-    const { pageId, path } = req.query;
+  router.get('/', certifySharedPage, accessTokenParser, loginRequired, validator.getPage, apiV3FormValidator, async(req, res) => {
+    const { pageId, path, user } = req.query;
 
     if (pageId == null && path == null) {
       return res.apiv3Err(new ErrorV3('Parameter path or pageId is required.', 'invalid-request'));
@@ -255,10 +255,10 @@ module.exports = (crowi) => {
     let page;
     try {
       if (pageId != null) { // prioritized
-        page = await Page.findByIdAndViewer(pageId, req.user);
+        page = await Page.findByIdAndViewer(pageId, user);
       }
       else {
-        page = await Page.findByPathAndViewer(path, req.user);
+        page = await Page.findByPathAndViewer(path, user);
       }
     }
     catch (err) {

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

@@ -2297,7 +2297,7 @@ class PageService {
         grantedUsers: notEmptyParent.grantedUsers,
       };
 
-      systematicallyCreatedPage = await this.createBySystem(
+      systematicallyCreatedPage = await this.forceCreateBySystem(
         path,
         '',
         options,
@@ -3371,7 +3371,7 @@ class PageService {
     return savedPage;
   }
 
-  private async canProcessCreateBySystem(
+  private async canProcessForceCreateBySystem(
       path: string,
       grantData: {
         grant: number,
@@ -3382,7 +3382,15 @@ class PageService {
     return this.canProcessCreate(path, grantData, false);
   }
 
-  async createBySystem(path: string, body: string, options: PageCreateOptions & { grantedUsers?: ObjectIdLike[] }): Promise<PageDocument> {
+  /**
+   * @private
+   * This method receives the same arguments as the PageService.create method does except for the added type '{ grantedUsers?: ObjectIdLike[] }'.
+   * This additional value is used to determine the grantedUser of the page to be created by system.
+   * This method must not run isGrantNormalized method to validate grant. **If necessary, run it before use this method.**
+   * -- Reason 1: This is because it is not expected to use this method when the grant validation is required.
+   * -- Reason 2: This is because it is not expected to use this method when the program cannot determine the operator.
+   */
+  private async forceCreateBySystem(path: string, body: string, options: PageCreateOptions & { grantedUsers?: ObjectIdLike[] }): Promise<PageDocument> {
     const Page = mongoose.model('Page') as unknown as PageModel;
 
     const isV5Compatible = this.crowi.configManager.getConfig('crowi', 'app:isV5Compatible');
@@ -3412,9 +3420,9 @@ class PageService {
     if (isGrantOwner && grantedUsers?.length !== 1) {
       throw Error('grantedUser must exist when grant is GRANT_OWNER');
     }
-    const canProcessCreateBySystem = await this.canProcessCreateBySystem(path, grantData);
-    if (!canProcessCreateBySystem) {
-      throw Error('Cannnot process createBySystem');
+    const canProcessForceCreateBySystem = await this.canProcessForceCreateBySystem(path, grantData);
+    if (!canProcessForceCreateBySystem) {
+      throw Error('Cannnot process forceCreateBySystem');
     }
 
     // Prepare a page document

+ 7 - 6
packages/app/src/stores/context.tsx

@@ -88,8 +88,8 @@ export const useShareLinksNumber = (initialData?: Nullable<any>): SWRResponse<Nu
   return useStaticSWR<Nullable<any>, Error>('shareLinksNumber', initialData);
 };
 
-export const useShareLinkId = (initialData?: Nullable<any>): SWRResponse<Nullable<any>, Error> => {
-  return useStaticSWR<Nullable<any>, Error>('shareLinkId', initialData);
+export const useShareLinkId = (initialData?: Nullable<string>): SWRResponse<Nullable<string>, Error> => {
+  return useStaticSWR<Nullable<string>, Error>('shareLinkId', initialData);
 };
 
 export const useRevisionIdHackmdSynced = (initialData?: Nullable<any>): SWRResponse<Nullable<any>, Error> => {
@@ -197,12 +197,13 @@ export const useIsEditable = (): SWRResponse<boolean, Error> => {
 
 export const useIsSharedUser = (): SWRResponse<boolean, Error> => {
   const { data: isGuestUser } = useIsGuestUser();
-  const { data: currentPagePath } = useCurrentPagePath();
+
+  const pathname = window.location.pathname;
 
   return useSWRImmutable(
-    ['isSharedUser', isGuestUser, currentPagePath],
-    (key: Key, isGuestUser: boolean, currentPagePath: string) => {
-      return isGuestUser && pagePathUtils.isSharedPage(currentPagePath as string);
+    ['isSharedUser', isGuestUser, pathname],
+    (key: Key, isGuestUser: boolean, pathname: string) => {
+      return isGuestUser && pagePathUtils.isSharedPage(pathname);
     },
   );
 };

+ 14 - 5
packages/app/src/stores/page.tsx

@@ -13,20 +13,29 @@ import { IPagingResult } from '~/interfaces/paging-result';
 import { apiGet } from '../client/util/apiv1-client';
 import { IPageTagsInfo } from '../interfaces/pageTagsInfo';
 
-import { useCurrentPagePath } from './context';
+import { useCurrentPageId, useCurrentPagePath } from './context';
 import { ITermNumberManagerUtil, useTermNumberManager } from './use-static-swr';
 
 
-export const useSWRxPageByPath = (path: string | null | undefined, initialData?: IPageHasId): SWRResponse<IPageHasId, Error> => {
+export const useSWRxPage = (pageId?: string, shareLinkId?: string): SWRResponse<IPageHasId, Error> => {
+  return useSWR(
+    pageId != null ? ['/page', pageId, shareLinkId] : null,
+    (endpoint, pageId, shareLinkId) => apiv3Get(endpoint, { pageId, shareLinkId }).then(result => result.data.page),
+  );
+};
+
+export const useSWRxPageByPath = (path?: string): SWRResponse<IPageHasId, Error> => {
   return useSWR(
     path != null ? ['/page', path] : null,
     (endpoint, path) => apiv3Get(endpoint, { path }).then(result => result.data.page),
-    {
-      fallbackData: initialData,
-    },
   );
 };
 
+export const useSWRxCurrentPage = (shareLinkId?: string): SWRResponse<IPageHasId, Error> => {
+  const { data: currentPageId } = useCurrentPageId();
+
+  return useSWRxPage(currentPageId ?? undefined, shareLinkId);
+};
 
 // eslint-disable-next-line @typescript-eslint/no-unused-vars
 export const useSWRxRecentlyUpdated = (): SWRResponse<(IPageHasId)[], Error> => {

+ 0 - 1
packages/app/src/styles/_layout.scss

@@ -68,7 +68,6 @@ body.growi-layout-fluid .grw-container-convertible {
   position: sticky;
   // growisubnavigation + grw-navbar-boder + some spacing
   top: calc(100px + 4px + 20px);
-  margin-top: 5px;
 }
 
 .grw-fab {