Explorar o código

Merge pull request #4649 from weseek/feat/bookmark-count-search-result

Feat/bookmark count search result
Yuki Takei %!s(int64=4) %!d(string=hai) anos
pai
achega
eba8716b10

+ 7 - 15
packages/app/src/components/SearchPage/SearchResultList.tsx

@@ -1,40 +1,32 @@
 import React, { FC } from 'react';
 import SearchResultListItem from './SearchResultListItem';
-import { IPageHasId } from '../../interfaces/page';
 import PaginationWrapper from '../PaginationWrapper';
+import { IPageSearchResultData } from '../../interfaces/search';
 
-// TOOD: retrieve bookmark count and add it to the following type
-export type ISearchedPage = IPageHasId & {
-  snippet: string,
-  elasticSearchResult: {
-    snippet: string,
-    matchedPath: string,
-  },
-};
 
 type Props = {
-  pages: ISearchedPage[],
-  selectedPages: ISearchedPage[],
+  pages: IPageSearchResultData[],
+  selectedPages: IPageSearchResultData[],
   onClickInvoked?: (pageId: string) => void,
   searchResultCount?: number,
   activePage?: number,
   pagingLimit?: number,
   onPagingNumberChanged?: (activePage: number) => void,
-  focusedPage?: ISearchedPage,
+  focusedPage?: IPageSearchResultData,
 }
 
 const SearchResultList: FC<Props> = (props:Props) => {
   const { focusedPage } = props;
-  const focusedPageId = focusedPage != null && focusedPage._id != null ? focusedPage._id : '';
+  const focusedPageId = (focusedPage !== undefined && focusedPage.pageData !== undefined) ? focusedPage.pageData._id : '';
   return (
     <>
       {props.pages.map((page) => {
         return (
           <SearchResultListItem
-            key={page._id}
+            key={page.pageData._id}
             page={page}
             onClickInvoked={props.onClickInvoked}
-            isSelected={page._id === focusedPageId || false}
+            isSelected={page.pageData._id === focusedPageId || false}
           />
         );
       })}

+ 16 - 17
packages/app/src/components/SearchPage/SearchResultListItem.tsx

@@ -5,17 +5,19 @@ import Clamp from 'react-multiline-clamp';
 import { useTranslation } from 'react-i18next';
 import { UserPicture, PageListMeta, PagePathLabel } from '@growi/ui';
 import { DevidedPagePath } from '@growi/core';
-import { ISearchedPage } from './SearchResultList';
+import { IPageSearchResultData } from '../../interfaces/search';
+
 
 import loggerFactory from '~/utils/logger';
+import { IPageHasId } from '~/interfaces/page';
 
 const logger = loggerFactory('growi:searchResultList');
 
 type PageItemControlProps = {
-  page: ISearchedPage,
+  page: IPageHasId,
 }
 
-const PageItemControl: FC<PageItemControlProps> = (props: {page: ISearchedPage}) => {
+const PageItemControl: FC<PageItemControlProps> = (props: {page: IPageHasId}) => {
 
   const { page } = props;
   const { t } = useTranslation('');
@@ -67,19 +69,19 @@ const PageItemControl: FC<PageItemControlProps> = (props: {page: ISearchedPage})
 };
 
 type Props = {
-  page: ISearchedPage,
+  page: IPageSearchResultData,
   isSelected: boolean,
   onClickInvoked?: (pageId: string) => void,
 }
 
 const SearchResultListItem: FC<Props> = (props:Props) => {
-  const { page, isSelected } = props;
+  const { page: { pageData, pageMeta }, isSelected } = 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.
-  const pageId = `#${page._id}`;
+  const pageId = `#${pageData._id}`;
 
-  const dPagePath = new DevidedPagePath(page.path, false, true);
-  const pagePathElem = <PagePathLabel page={page} isFormerOnly />;
+  const dPagePath = new DevidedPagePath(pageData.path, false, true);
+  const pagePathElem = <PagePathLabel page={pageData} isFormerOnly />;
 
   const onClickInvoked = (pageId) => {
     if (props.onClickInvoked != null) {
@@ -88,11 +90,11 @@ const SearchResultListItem: FC<Props> = (props:Props) => {
   };
 
   return (
-    <li key={page._id} className={`page-list-li search-page-item w-100 border-bottom px-4 list-group-item-action ${isSelected ? 'active' : ''}`}>
+    <li key={pageData._id} className={`page-list-li search-page-item w-100 border-bottom px-4 list-group-item-action ${isSelected ? 'active' : ''}`}>
       <a
         className="d-block pt-3"
         href={pageId}
-        onClick={() => onClickInvoked(page._id)}
+        onClick={() => onClickInvoked(pageData._id)}
       >
         <div className="d-flex">
           {/* checkbox */}
@@ -108,26 +110,23 @@ const SearchResultListItem: FC<Props> = (props:Props) => {
             <div className="d-flex my-1 align-items-center">
               {/* page title */}
               <h3 className="mb-0">
-                <UserPicture user={page.lastUpdateUser} />
+                <UserPicture user={pageData.lastUpdateUser} />
                 <span className="mx-2">{dPagePath.latter}</span>
               </h3>
               {/* page meta */}
               <div className="d-flex mx-2">
-                <PageListMeta page={page} />
+                <PageListMeta page={pageData} bookmarkCount={pageMeta.bookmarkCount} />
               </div>
               {/* doropdown icon includes page control buttons */}
               <div className="ml-auto">
-                <PageItemControl page={page} />
+                <PageItemControl page={pageData} />
               </div>
             </div>
             <div className="my-2">
               <Clamp
                 lines={2}
               >
-                {page.snippet
-                  ? <div className="mt-1">page.snippet</div>
-                  : <div className="mt-1" dangerouslySetInnerHTML={{ __html: page.elasticSearchResult.snippet }}></div>
-                }
+                {pageMeta.elasticSearchResult && <div className="mt-1" dangerouslySetInnerHTML={{ __html: pageMeta.elasticSearchResult.snippet }}></div>}
               </Clamp>
             </div>
           </div>

+ 13 - 0
packages/app/src/interfaces/search.ts

@@ -1,5 +1,18 @@
+import { IPageHasId } from './page';
+
 export enum CheckboxType {
   NONE_CHECKED = 'noneChecked',
   INDETERMINATE = 'indeterminate',
   ALL_CHECKED = 'allChecked',
 }
+
+export type IPageSearchResultData = {
+  pageData: IPageHasId,
+  pageMeta: {
+    bookmarkCount: number,
+    elasticSearchResult: {
+      snippet: string,
+      matchedPath: string,
+    },
+  },
+}

+ 20 - 12
packages/app/src/server/routes/search.js

@@ -152,25 +152,34 @@ module.exports = function(crowi, app) {
 
       const ids = esResult.data.map((page) => { return page._id });
       const findResult = await Page.findListByPageIds(ids);
-      // add tags and elasticSearch data to page
-      findResult.pages.map((page) => {
+
+      // add tags data to page
+      findResult.pages.map((pageData) => {
         const data = esResult.data.find((data) => {
-          return page.id === data._id;
+          return pageData.id === data._id;
         });
-        page._doc.tags = data._source.tag_names;
-        page._doc.elasticSearchResult = data.elasticSearchResult;
-        return page;
+        pageData._doc.tags = data._source.tag_names;
+        return pageData;
       });
 
       result.meta = esResult.meta;
       result.totalCount = findResult.totalCount;
       result.data = findResult.pages
-        .map((page) => {
-          if (page.lastUpdateUser != null && page.lastUpdateUser instanceof User) {
-            page.lastUpdateUser = serializeUserSecurely(page.lastUpdateUser);
+        .map((pageData) => {
+          if (pageData.lastUpdateUser != null && pageData.lastUpdateUser instanceof User) {
+            pageData.lastUpdateUser = serializeUserSecurely(pageData.lastUpdateUser);
           }
-          page.bookmarkCount = (page._source && page._source.bookmark_count) || 0;
-          return page;
+
+          const data = esResult.data.find((data) => {
+            return pageData.id === data._id;
+          });
+
+          const pageMeta = {
+            bookmarkCount: data._source.bookmark_count || 0,
+            elasticSearchResult: data.elasticSearchResult,
+          };
+
+          return { pageData, pageMeta };
         })
         .sort((page1, page2) => {
           // note: this do not consider NaN
@@ -180,7 +189,6 @@ module.exports = function(crowi, app) {
     catch (err) {
       return res.json(ApiResponse.error(err));
     }
-
     return res.json(ApiResponse.success(result));
   };
 

+ 8 - 0
packages/ui/src/components/PagePath/PageListMeta.jsx

@@ -37,6 +37,12 @@ export class PageListMeta extends React.Component {
       locked = <span><i className="icon-lock" /></span>;
     }
 
+    let bookmarkCount;
+    if (this.props.bookmarkCount > 0) {
+      bookmarkCount = <span><i className="icon-star" />{this.props.bookmarkCount}</span>;
+    }
+
+
     return (
       <span className="page-list-meta">
         {topLabel}
@@ -44,6 +50,7 @@ export class PageListMeta extends React.Component {
         {commentCount}
         {likerCount}
         {locked}
+        {bookmarkCount}
       </span>
     );
   }
@@ -52,6 +59,7 @@ export class PageListMeta extends React.Component {
 
 PageListMeta.propTypes = {
   page: PropTypes.object.isRequired,
+  bookmarkCount: PropTypes.number,
 };
 
 PageListMeta.defaultProps = {