Taichi Masuyama 4 лет назад
Родитель
Сommit
4b64fb1cf5

+ 20 - 0
packages/app/src/components/SearchPage.jsx

@@ -34,6 +34,7 @@ class SearchPage extends React.Component {
       focusedSearchResultData: null,
       selectedPagesIdList: new Set(),
       searchResultCount: 0,
+      shortBodiesMap: null,
       activePage: 1,
       pagingLimit: this.props.appContainer.config.pageLimitationL,
       excludeUserPages: true,
@@ -140,6 +141,11 @@ class SearchPage extends React.Component {
     this.setState({ pagingLimit: limit }, () => this.search({ keyword: this.state.searchedKeyword }));
   }
 
+  async fetchShortBodiesMap(pageIds) {
+    const res = await this.props.appContainer.apiGet('/page-listing/short-bodies', { pageIds });
+    this.setState({ shortBodiesMap: res.data.shortBodiesMap });
+  }
+
   // todo: refactoring
   // refs: https://redmine.weseek.co.jp/issues/82139
   async search(data) {
@@ -171,6 +177,19 @@ class SearchPage extends React.Component {
         sort,
         order,
       });
+
+      /*
+       * non-await asynchronous short body fetch
+       */
+      const pageIds = res.data.map((page) => {
+        if (page.pageMeta?.elasticsearchResult?.snippet != null) {
+          return null;
+        }
+
+        return page.pageData._id;
+      }).filter(page => page != null);
+      this.fetchShortBodiesMap(pageIds);
+
       this.changeURL(keyword);
       if (res.data.length > 0) {
         this.setState({
@@ -288,6 +307,7 @@ class SearchPage extends React.Component {
         focusedSearchResultData={this.state.focusedSearchResultData}
         selectedPagesIdList={this.state.selectedPagesIdList || []}
         searchResultCount={this.state.searchResultCount}
+        shortBodiesMap={this.state.shortBodiesMap}
         activePage={this.state.activePage}
         pagingLimit={this.state.pagingLimit}
         onClickSearchResultItem={this.selectPage}

+ 5 - 1
packages/app/src/components/SearchPage/SearchResultList.tsx

@@ -11,6 +11,7 @@ type Props = {
   searchResultCount?: number,
   activePage?: number,
   pagingLimit?: number,
+  shortBodiesMap?: Record<string, string>
   focusedSearchResultData?: IPageSearchResultData,
   onPagingNumberChanged?: (activePage: number) => void,
   onClickSearchResultItem?: (pageId: string) => void,
@@ -20,7 +21,9 @@ type Props = {
 }
 
 const SearchResultList: FC<Props> = (props:Props) => {
-  const { focusedSearchResultData, selectedPagesIdList, isEnableActions } = props;
+  const {
+    focusedSearchResultData, selectedPagesIdList, isEnableActions, shortBodiesMap,
+  } = props;
 
   const focusedPageId = (focusedSearchResultData != null && focusedSearchResultData.pageData != null) ? focusedSearchResultData.pageData._id : '';
   return (
@@ -33,6 +36,7 @@ const SearchResultList: FC<Props> = (props:Props) => {
             key={page.pageData._id}
             page={page}
             isEnableActions={isEnableActions}
+            shortBody={shortBodiesMap?.[page.pageData._id]}
             onClickSearchResultItem={props.onClickSearchResultItem}
             onClickCheckbox={props.onClickCheckbox}
             isChecked={isChecked}

+ 13 - 10
packages/app/src/components/SearchPage/SearchResultListItem.tsx

@@ -1,4 +1,4 @@
-import React, { FC } from 'react';
+import React, { FC, memo } from 'react';
 
 import Clamp from 'react-multiline-clamp';
 
@@ -14,15 +14,16 @@ type Props = {
   isSelected: boolean,
   isChecked: boolean,
   isEnableActions: boolean,
+  shortBody?: string
   onClickCheckbox?: (pageId: string) => void,
   onClickSearchResultItem?: (pageId: string) => void,
   onClickDeleteButton?: (pageId: string) => void,
 }
 
-const SearchResultListItem: FC<Props> = (props:Props) => {
+const SearchResultListItem: FC<Props> = memo((props:Props) => {
   const {
     // todo: refactoring variable name to clear what changed
-    page: { pageData, pageMeta }, isSelected, onClickSearchResultItem, onClickCheckbox, isChecked, isEnableActions,
+    page: { pageData, pageMeta }, isSelected, onClickSearchResultItem, onClickCheckbox, isChecked, isEnableActions, shortBody,
   } = props;
 
   // Add prefix 'id_' in pageId, because scrollspy of bootstrap doesn't work when the first letter of id attr of target component is numeral.
@@ -82,13 +83,15 @@ const SearchResultListItem: FC<Props> = (props:Props) => {
               </div>
             </div>
             <div className="my-2">
-              {
-                pageMeta.elasticSearchResult != null && (
-                  <Clamp lines={2}>
+              <Clamp lines={2}>
+                {
+                  pageMeta.elasticSearchResult != null && pageMeta.elasticSearchResult?.snippet.length !== 0 ? (
                     <div className="mt-1" dangerouslySetInnerHTML={{ __html: pageMeta.elasticSearchResult.snippet }}></div>
-                  </Clamp>
-                )
-              }
+                  ) : (
+                    <div className="mt-1">{ shortBody != null ? shortBody : 'Loading ...' }</div> // TODO: improve indicator
+                  )
+                }
+              </Clamp>
             </div>
           </div>
         </div>
@@ -96,6 +99,6 @@ const SearchResultListItem: FC<Props> = (props:Props) => {
       </a>
     </li>
   );
-};
+});
 
 export default SearchResultListItem;

+ 17 - 0
packages/app/src/server/routes/apiv3/page-listing.ts

@@ -27,6 +27,9 @@ const validator = {
     query('id').isMongoId(),
     query('path').isString(),
   ], 'id or path is required'),
+  pageIdsRequired: [
+    query('pageIds').isArray().withMessage('pageIds is required'),
+  ],
 };
 
 /*
@@ -90,5 +93,19 @@ export default (crowi: Crowi): Router => {
     }
   });
 
+  // eslint-disable-next-line max-len
+  router.get('/short-bodies', accessTokenParser, loginRequired, validator.pageIdsRequired, async(req: AuthorizedRequest, res: ApiV3Response) => {
+    const { pageIds } = req.query;
+
+    try {
+      const shortBodiesMap = await crowi.pageService.shortBodiesMapByPageIds(pageIds, req.user);
+      return res.apiv3({ shortBodiesMap });
+    }
+    catch (err) {
+      logger.error('Error occurred while fetching shortBodiesMap.', err);
+      return res.apiv3Err(new ErrorV3('Error occurred while fetching shortBodiesMap.'));
+    }
+  });
+
   return router;
 };

+ 5 - 0
packages/app/src/server/service/page.js

@@ -771,6 +771,11 @@ class PageService {
     }
   }
 
+  async shortBodiesMapByPageIds(pageIds, user) {
+    // TODO: fetch
+    return {};
+  }
+
   validateCrowi() {
     if (this.crowi == null) {
       throw new Error('"crowi" is null. Init User model with "crowi" argument first.');