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

Merge pull request #4605 from weseek/feat/77543-79831-create-dropdown-icon

Feat/77543 79831 create dropdown icon
ryo-h 4 лет назад
Родитель
Сommit
c867bc14bc

+ 1 - 0
packages/app/resource/locales/en_US/translation.json

@@ -148,6 +148,7 @@
   "Sign out": "Logout",
   "Disassociate": "Disassociate",
   "No bookmarks yet": "No bookmarks yet",
+  "Add to bookmark": "Add to bookmark",
   "Recent Created": "Recent Created",
   "Recent Changes": "Recent Changes",
   "original_path":"Original path",

+ 1 - 0
packages/app/resource/locales/ja_JP/translation.json

@@ -150,6 +150,7 @@
   "Sidebar mode": "サイドバーモード",
   "Sidebar mode on Editor": "サイドバーモード(編集時)",
   "No bookmarks yet": "No bookmarks yet",
+  "Add to bookmark": "ブックマークに追加",
   "Recent Created": "最新の作成",
   "Recent Changes": "最新の変更",
   "original_path":"元のパス",

+ 1 - 0
packages/app/resource/locales/zh_CN/translation.json

@@ -156,6 +156,7 @@
 	"Sign out": "退出",
   "Disassociate": "解除关联",
   "No bookmarks yet": "暂无书签",
+  "Add to bookmark": "添加到书签",
 	"Recent Created": "最新创建",
   "Recent Changes": "最新修改",
   "original_path":"Original path",

+ 18 - 24
packages/app/src/components/PaginationWrapper.jsx → packages/app/src/components/PaginationWrapper.tsx

@@ -1,18 +1,21 @@
-import React, { useCallback, useMemo } from 'react';
-import PropTypes from 'prop-types';
+import React, {
+  FC, memo, useCallback, useMemo,
+} from 'react';
 
 import { Pagination, PaginationItem, PaginationLink } from 'reactstrap';
 
-/**
- *
- * @author Mikitaka Itizawa <itizawa@weseek.co.jp>
- *
- * @export
- * @class PaginationWrapper
- * @extends {React.Component}
- */
 
-const PaginationWrapper = React.memo((props) => {
+type Props = {
+  activePage: number,
+  changePage?: (number) => void,
+  totalItemsCount: number,
+  pagingLimit?: number,
+  align?: string,
+  size?: string,
+};
+
+
+const PaginationWrapper: FC<Props> = memo((props: Props) => {
   const {
     activePage, changePage, totalItemsCount, pagingLimit, align,
   } = props;
@@ -59,7 +62,7 @@ const PaginationWrapper = React.memo((props) => {
    * this function set << & <
    */
   const generateFirstPrev = useCallback(() => {
-    const paginationItems = [];
+    const paginationItems: JSX.Element[] = [];
     if (activePage !== 1) {
       paginationItems.push(
         <PaginationItem key="painationItemFirst">
@@ -89,7 +92,7 @@ const PaginationWrapper = React.memo((props) => {
    * this function set  numbers
    */
   const generatePaginations = useCallback(() => {
-    const paginationItems = [];
+    const paginationItems: JSX.Element[] = [];
     for (let number = paginationStart; number <= maxViewPageNum; number++) {
       paginationItems.push(
         <PaginationItem key={`paginationItem-${number}`} active={number === activePage}>
@@ -108,7 +111,7 @@ const PaginationWrapper = React.memo((props) => {
    * this function set > & >>
    */
   const generateNextLast = useCallback(() => {
-    const paginationItems = [];
+    const paginationItems: JSX.Element[] = [];
     if (totalPage !== activePage) {
       paginationItems.push(
         <PaginationItem key="painationItemNext">
@@ -133,7 +136,7 @@ const PaginationWrapper = React.memo((props) => {
   }, [activePage, changePage, totalPage]);
 
   const getListClassName = useMemo(() => {
-    const listClassNames = [];
+    const listClassNames: string[] = [];
 
     if (align === 'center') {
       listClassNames.push('justify-content-center');
@@ -157,15 +160,6 @@ const PaginationWrapper = React.memo((props) => {
 
 });
 
-PaginationWrapper.propTypes = {
-  activePage: PropTypes.number.isRequired,
-  changePage: PropTypes.func,
-  totalItemsCount: PropTypes.number.isRequired,
-  pagingLimit: PropTypes.number,
-  align: PropTypes.string,
-  size: PropTypes.string,
-};
-
 PaginationWrapper.defaultProps = {
   align: 'left',
   size: 'md',

+ 2 - 3
packages/app/src/components/SearchPage.jsx

@@ -205,10 +205,9 @@ class SearchPage extends React.Component {
   renderSearchResultList = () => {
     return (
       <SearchResultList
-        pages={this.state.searchedPages}
-        deletionMode={false}
+        pages={this.state.searchedPages || []}
         focusedPage={this.state.focusedPage}
-        selectedPages={this.state.selectedPages}
+        selectedPages={this.state.selectedPages || []}
         searchResultCount={this.state.searchResultCount}
         activePage={this.state.activePage}
         pagingLimit={this.state.pagingLimit}

+ 0 - 56
packages/app/src/components/SearchPage/SearchResultList.jsx

@@ -1,56 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import SearchResultListItem from './SearchResultListItem';
-import PaginationWrapper from '../PaginationWrapper';
-
-class SearchResultList extends React.Component {
-
-  render() {
-    const { focusedPage } = this.props;
-    const focusedPageId = focusedPage != null && focusedPage.id != null ? focusedPage.id : '';
-    return (
-      <>
-        {this.props.pages.map((page) => {
-        // TODO : send cetain length of body (revisionBody) from elastisearch by adding some settings to the query and
-        //         when keyword is not in page content, display revisionBody.
-        // TASK : https://estoc.weseek.co.jp/redmine/issues/79606
-          return (
-            <SearchResultListItem
-              key={page._id}
-              page={page}
-              onClickInvoked={this.props.onClickInvoked}
-              isSelected={page._id === focusedPageId || false}
-              noLink
-            />
-          );
-        })}
-        {this.props.searchResultCount != null && this.props.searchResultCount > 0 && (
-          <div className="my-4 mx-auto">
-            <PaginationWrapper
-              activePage={this.props.activePage}
-              changePage={this.props.onPagingNumberChanged}
-              totalItemsCount={this.props.searchResultCount || 0}
-              pagingLimit={this.props.pagingLimit}
-            />
-          </div>
-        )}
-      </>
-    );
-  }
-
-}
-
-SearchResultList.propTypes = {
-  pages: PropTypes.array.isRequired,
-  deletionMode: PropTypes.bool.isRequired,
-  focusedPage: PropTypes.object,
-  selectedPages: PropTypes.array.isRequired,
-  searchResultCount: PropTypes.number,
-  activePage: PropTypes.number.isRequired,
-  pagingLimit: PropTypes.number,
-  onClickInvoked: PropTypes.func,
-  onChangeInvoked: PropTypes.func,
-  onPagingNumberChanged: PropTypes.func,
-};
-
-export default SearchResultList;

+ 57 - 0
packages/app/src/components/SearchPage/SearchResultList.tsx

@@ -0,0 +1,57 @@
+import React, { FC } from 'react';
+import SearchResultListItem from './SearchResultListItem';
+import { IPageHasId } from '../../interfaces/page';
+import PaginationWrapper from '../PaginationWrapper';
+
+// 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[],
+  onClickInvoked?: (pageId: string) => void,
+  searchResultCount?: number,
+  activePage?: number,
+  pagingLimit?: number,
+  onPagingNumberChanged?: (activePage: number) => void,
+  focusedPage?: ISearchedPage,
+}
+
+const SearchResultList: FC<Props> = (props:Props) => {
+  const { focusedPage } = props;
+  const focusedPageId = focusedPage != null && focusedPage._id != null ? focusedPage._id : '';
+  return (
+    <>
+      {props.pages.map((page) => {
+        return (
+          <SearchResultListItem
+            key={page._id}
+            page={page}
+            onClickInvoked={props.onClickInvoked}
+            isSelected={page._id === focusedPageId || false}
+          />
+        );
+      })}
+      {props.searchResultCount != null && props.searchResultCount > 0 && (
+        <div className="my-4 mx-auto">
+          <PaginationWrapper
+            activePage={props.activePage || 1}
+            changePage={props.onPagingNumberChanged}
+            totalItemsCount={props.searchResultCount || 0}
+            pagingLimit={props.pagingLimit}
+          />
+        </div>
+      )}
+
+    </>
+  );
+
+};
+
+export default SearchResultList;

+ 75 - 43
packages/app/src/components/SearchPage/SearchResultListItem.tsx

@@ -1,29 +1,76 @@
 import React, { FC } from 'react';
-
+import { useTranslation } from 'react-i18next';
 import { UserPicture, PageListMeta, PagePathLabel } from '@growi/ui';
 import { DevidedPagePath } from '@growi/core';
+import { ISearchedPage } from './SearchResultList';
 
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:searchResultList');
 
-type Props ={
-  page: {
-    _id: string,
-    path: string,
-    noLink: boolean,
-    lastUpdateUser: any
-    elasticSearchResult: {
-      snippet: string,
-    }
-  },
+type PageItemControlProps = {
+  page: ISearchedPage,
+}
+
+const PageItemControl: FC<PageItemControlProps> = (props: {page: ISearchedPage}) => {
+
+  const { page } = props;
+  const { t } = useTranslation('');
+
+  return (
+    <>
+      <button
+        type="button"
+        className="btn-link nav-link dropdown-toggle dropdown-toggle-no-caret border-0 rounded grw-btn-page-management py-0"
+        data-toggle="dropdown"
+      >
+        <i className="fa fa-ellipsis-v text-muted"></i>
+      </button>
+      <div className="dropdown-menu dropdown-menu-right">
+
+        {/* TODO: if there is the following button in XD add it here
+        <button
+          type="button"
+          className="btn btn-link p-0"
+          value={page.path}
+          onClick={(e) => {
+            window.location.href = e.currentTarget.value;
+          }}
+        >
+          <i className="icon-login" />
+        </button>
+        */}
+
+        {/*
+          TODO: add function to the following buttons like using modal or others
+          ref: https://estoc.weseek.co.jp/redmine/issues/79026
+        */}
+        <button className="dropdown-item text-danger" type="button" onClick={() => console.log('delete modal show')}>
+          <i className="icon-fw icon-fire"></i>{t('Delete')}
+        </button>
+        <button className="dropdown-item" type="button" onClick={() => console.log('duplicate modal show')}>
+          <i className="icon-fw icon-star"></i>{t('Add to bookmark')}
+        </button>
+        <button className="dropdown-item" type="button" onClick={() => console.log('duplicate modal show')}>
+          <i className="icon-fw icon-docs"></i>{t('Duplicate')}
+        </button>
+        <button className="dropdown-item" type="button" onClick={() => console.log('rename function will be added')}>
+          <i className="icon-fw  icon-action-redo"></i>{t('Move/Rename')}
+        </button>
+      </div>
+    </>
+  );
+
+};
+
+type Props = {
+  page: ISearchedPage,
   isSelected: boolean,
-  onClickInvoked?: (data: string) => void,
+  onClickInvoked?: (pageId: string) => void,
 }
 
 const SearchResultListItem: FC<Props> = (props:Props) => {
-
-  const { page, isSelected, onClickInvoked } = props;
+  const { page, 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}`;
@@ -31,24 +78,18 @@ const SearchResultListItem: FC<Props> = (props:Props) => {
   const dPagePath = new DevidedPagePath(page.path, false, true);
   const pagePathElem = <PagePathLabel page={page} isFormerOnly />;
 
-  // TODO : send cetain  length of body (revisionBody) from elastisearch by adding some settings to the query and
-  //         when keyword is not in page content, display revisionBody.
-  // TASK : https://estoc.weseek.co.jp/redmine/issues/79606
+  const onClickInvoked = (pageId) => {
+    if (props.onClickInvoked != null) {
+      props.onClickInvoked(pageId);
+    }
+  };
 
   return (
     <li key={page._id} className={`page-list-li w-100 border-bottom pr-4 list-group-item-action ${isSelected ? 'active' : ''}`}>
       <a
         className="d-block pt-3"
         href={pageId}
-        onClick={() => {
-          try {
-            if (onClickInvoked == null) { throw new Error('onClickInvoked is null') }
-            onClickInvoked(page._id);
-          }
-          catch (error) {
-            logger.error(error);
-          }
-        }}
+        onClick={() => onClickInvoked(page._id)}
       >
         <div className="d-flex">
           {/* checkbox */}
@@ -71,30 +112,21 @@ const SearchResultListItem: FC<Props> = (props:Props) => {
               <div className="d-flex mx-2">
                 <PageListMeta page={page} />
               </div>
-              {/* doropdown icon */}
+              {/* doropdown icon includes page control buttons */}
               <div className="ml-auto">
-                <i className="fa fa-ellipsis-v text-muted"></i>
+                <PageItemControl page={page} />
               </div>
-
-              {/* Todo: add the following icon into dropdown menu */}
-              {/* <button
-                type="button"
-                className="btn btn-link p-0"
-                value={page.path}
-                onClick={(e) => {
-                  window.location.href = e.currentTarget.value;
-                }}
-              >
-                <i className="icon-login" />
-              </button> */}
-
             </div>
-            {/* eslint-disable-next-line react/no-danger */}
-            <div className="mt-1" dangerouslySetInnerHTML={{ __html: page.elasticSearchResult.snippet }}></div>
           </div>
         </div>
+        {/* TODO: adjust snippet position */}
+        {page.snippet
+          ? <div className="mt-1">page.snippet</div>
+          : <div className="mt-1" dangerouslySetInnerHTML={{ __html: page.elasticSearchResult.snippet }}></div>
+        }
       </a>
     </li>
   );
 };
+
 export default SearchResultListItem;

+ 12 - 5
packages/app/src/interfaces/page.ts

@@ -5,10 +5,17 @@ import { ITag } from './tag';
 export type IPage = {
   path: string,
   status: string,
-  revision: IRevision,
-  tags: ITag[],
-  creator: IUser,
+  revision: string | IRevision,
+  tags?: ITag[],
+  lastUpdateUser: any,
+  commentCount: number,
+  creator: string | IUser,
+  seenUsers: string[],
+  liker: string[],
   createdAt: Date,
   updatedAt: Date,
-  seenUsers: string[]
-}
+};
+
+export type IPageHasId = IPage & {
+  _id: string,
+};