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

Merge branch 'imprv/show-page-control-menu-on-empty-page' into feat/determine-if-empty-page-is-deletable

yohei0125 3 лет назад
Родитель
Сommit
155ba6fcc7

+ 8 - 2
packages/app/src/components/NotFoundPage.tsx

@@ -27,8 +27,14 @@ const NotFoundPage = (): JSX.Element => {
 
   // replace url in address bar with path when accessing empty page by permalink
   useEffect(() => {
-    if (pageId != null && path != null && !notFoundTargetPathOrId?.includes('/')) {
-      replaceURLHistory(path);
+    const isEmptyPage = pageId != null; // Todo: should be improved. https://redmine.weseek.co.jp/issues/98152
+    const isPathExist = path != null;
+    const isPathLink = notFoundTargetPathOrId?.includes('/');
+
+    if (isEmptyPage && isPathExist && !isPathLink) {
+      // The (as string) below is a workaround for the case. See the link. (Fixed in typescript version 4.4)
+      // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-4.html#control-flow-analysis-of-aliased-conditions-and-discriminants
+      replaceURLHistory(path as string);
     }
   }, [pageId, path, notFoundTargetPathOrId]);
 

+ 14 - 18
packages/app/src/components/Page/TagsInput.tsx

@@ -1,11 +1,10 @@
 import React, {
   FC, useRef, useState, useCallback,
 } from 'react';
+
 import { AsyncTypeahead } from 'react-bootstrap-typeahead';
 
-import { apiGet } from '~/client/util/apiv1-client';
-import { toastError } from '~/client/util/apiNotification';
-import { IResTagsSearchApiv1 } from '~/interfaces/tag';
+import { useSWRxTagsSearch } from '~/stores/tag';
 
 type TypeaheadInstance = {
   _handleMenuItemSelect: (activeItem: string, event: React.KeyboardEvent) => void,
@@ -24,7 +23,11 @@ const TagsInput: FC<Props> = (props: Props) => {
   const tagsInputRef = useRef<TypeaheadInstance>(null);
 
   const [resultTags, setResultTags] = useState<string[]>([]);
-  const [isLoading, setLoading] = useState(false);
+  const [searchQuery, setSearchQuery] = useState('');
+
+  const { data: tagsSearch, error } = useSWRxTagsSearch(searchQuery);
+
+  const isLoading = error == null && tagsSearch === undefined;
 
   const changeHandler = useCallback((selected: string[]) => {
     if (props.onTagsUpdated != null) {
@@ -33,20 +36,13 @@ const TagsInput: FC<Props> = (props: Props) => {
   }, [props]);
 
   const searchHandler = useCallback(async(query: string) => {
-    setLoading(true);
-    try {
-      // TODO: 91698 SWRize
-      const res = await apiGet('/tags.search', { q: query }) as IResTagsSearchApiv1;
-      res.tags.unshift(query);
-      setResultTags(Array.from(new Set(res.tags)));
-    }
-    catch (err) {
-      toastError(err);
-    }
-    finally {
-      setLoading(false);
-    }
-  }, []);
+    const tagsSearchData = tagsSearch?.tags || [];
+    setSearchQuery(query);
+
+    tagsSearchData.unshift(searchQuery);
+    setResultTags(Array.from(new Set(tagsSearchData)));
+
+  }, [searchQuery, tagsSearch?.tags]);
 
   const keyDownHandler = useCallback((event: React.KeyboardEvent) => {
     if (event.key === ' ') {

+ 8 - 14
packages/app/src/server/routes/page.js

@@ -594,18 +594,6 @@ module.exports = function(crowi, app) {
     res.render('layout-growi/page_list', renderVars);
   };
 
-  /**
-   * Redirect process for single non-empty page
-   */
-  async function redirectOperationForSinglePage(page, req, res) {
-    const url = new URL('https://dummy.origin');
-    url.pathname = `/${page._id}`;
-    Object.entries(req.query).forEach(([key, value], i) => {
-      url.searchParams.append(key, value);
-    });
-    return res.safeRedirect(urljoin(url.pathname, url.search));
-  }
-
   /**
    * redirector
    */
@@ -630,8 +618,14 @@ module.exports = function(crowi, app) {
     }
 
     if (nonEmptyPages.length === 1) {
-      const nonEmptyPage = pages.find(p => !p.isEmpty); // find the nonEmpty Page
-      return redirectOperationForSinglePage(nonEmptyPage, req, res);
+      const nonEmptyPage = nonEmptyPages[0];
+      const url = new URL('https://dummy.origin');
+
+      url.pathname = `/${nonEmptyPage._id}`;
+      Object.entries(req.query).forEach(([key, value], i) => {
+        url.searchParams.append(key, value);
+      });
+      return res.safeRedirect(urljoin(url.pathname, url.search));
     }
 
     // Processing of nonEmptyPage is finished by the time this code is read

+ 4 - 2
packages/app/src/server/service/file-uploader/gcs.js

@@ -2,8 +2,8 @@ import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:service:fileUploaderAws');
 
-const urljoin = require('url-join');
 const { Storage } = require('@google-cloud/storage');
+const urljoin = require('url-join');
 
 let _instance;
 
@@ -21,7 +21,9 @@ module.exports = function(crowi) {
     if (_instance == null) {
       const keyFilename = configManager.getConfig('crowi', 'gcs:apiKeyJsonPath');
       // see https://googleapis.dev/nodejs/storage/latest/Storage.html
-      _instance = new Storage({ keyFilename });
+      _instance = keyFilename != null
+        ? new Storage({ keyFilename }) // Create a client with explicit credentials
+        : new Storage(); // Create a client that uses Application Default Credentials
     }
     return _instance;
   }

+ 8 - 1
packages/app/src/stores/tag.tsx

@@ -2,7 +2,7 @@ import { SWRResponse } from 'swr';
 import useSWRImmutable from 'swr/immutable';
 
 import { apiGet } from '~/client/util/apiv1-client';
-import { IResTagsListApiv1 } from '~/interfaces/tag';
+import { IResTagsListApiv1, IResTagsSearchApiv1 } from '~/interfaces/tag';
 
 export const useSWRxTagsList = (limit?: number, offset?: number): SWRResponse<IResTagsListApiv1, Error> => {
   return useSWRImmutable(
@@ -10,3 +10,10 @@ export const useSWRxTagsList = (limit?: number, offset?: number): SWRResponse<IR
     (endpoint, limit, offset) => apiGet(endpoint, { limit, offset }).then((result: IResTagsListApiv1) => result),
   );
 };
+
+export const useSWRxTagsSearch = (query: string): SWRResponse<IResTagsSearchApiv1, Error> => {
+  return useSWRImmutable(
+    ['/tags.search', query],
+    (endpoint, query) => apiGet(endpoint, { q: query }).then((result: IResTagsSearchApiv1) => result),
+  );
+};