Przeglądaj źródła

Merge branch 'feat/77525-enable-to-delete-pages' into feat/77525-80783-control-selected-pages

# Conflicts:
#	packages/app/src/components/SearchPage/SearchResultList.jsx
#	packages/app/src/components/SearchPage/SearchResultListItem.tsx
#	packages/app/src/interfaces/page.ts
SULLEY\ryo-h 4 lat temu
rodzic
commit
1f4b78d5d3

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

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

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

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

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

@@ -156,6 +156,7 @@
 	"Sign out": "退出",
 	"Sign out": "退出",
   "Disassociate": "解除关联",
   "Disassociate": "解除关联",
   "No bookmarks yet": "暂无书签",
   "No bookmarks yet": "暂无书签",
+  "Add to bookmark": "添加到书签",
 	"Recent Created": "最新创建",
 	"Recent Created": "最新创建",
   "Recent Changes": "最新修改",
   "Recent Changes": "最新修改",
   "original_path":"Original path",
   "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';
 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 {
   const {
     activePage, changePage, totalItemsCount, pagingLimit, align,
     activePage, changePage, totalItemsCount, pagingLimit, align,
   } = props;
   } = props;
@@ -59,7 +62,7 @@ const PaginationWrapper = React.memo((props) => {
    * this function set << & <
    * this function set << & <
    */
    */
   const generateFirstPrev = useCallback(() => {
   const generateFirstPrev = useCallback(() => {
-    const paginationItems = [];
+    const paginationItems: JSX.Element[] = [];
     if (activePage !== 1) {
     if (activePage !== 1) {
       paginationItems.push(
       paginationItems.push(
         <PaginationItem key="painationItemFirst">
         <PaginationItem key="painationItemFirst">
@@ -89,7 +92,7 @@ const PaginationWrapper = React.memo((props) => {
    * this function set  numbers
    * this function set  numbers
    */
    */
   const generatePaginations = useCallback(() => {
   const generatePaginations = useCallback(() => {
-    const paginationItems = [];
+    const paginationItems: JSX.Element[] = [];
     for (let number = paginationStart; number <= maxViewPageNum; number++) {
     for (let number = paginationStart; number <= maxViewPageNum; number++) {
       paginationItems.push(
       paginationItems.push(
         <PaginationItem key={`paginationItem-${number}`} active={number === activePage}>
         <PaginationItem key={`paginationItem-${number}`} active={number === activePage}>
@@ -108,7 +111,7 @@ const PaginationWrapper = React.memo((props) => {
    * this function set > & >>
    * this function set > & >>
    */
    */
   const generateNextLast = useCallback(() => {
   const generateNextLast = useCallback(() => {
-    const paginationItems = [];
+    const paginationItems: JSX.Element[] = [];
     if (totalPage !== activePage) {
     if (totalPage !== activePage) {
       paginationItems.push(
       paginationItems.push(
         <PaginationItem key="painationItemNext">
         <PaginationItem key="painationItemNext">
@@ -133,7 +136,7 @@ const PaginationWrapper = React.memo((props) => {
   }, [activePage, changePage, totalPage]);
   }, [activePage, changePage, totalPage]);
 
 
   const getListClassName = useMemo(() => {
   const getListClassName = useMemo(() => {
-    const listClassNames = [];
+    const listClassNames: string[] = [];
 
 
     if (align === 'center') {
     if (align === 'center') {
       listClassNames.push('justify-content-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 = {
 PaginationWrapper.defaultProps = {
   align: 'left',
   align: 'left',
   size: 'md',
   size: 'md',

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

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

+ 3 - 3
packages/app/src/components/SearchPage/DeleteSelectedPageGroup.tsx

@@ -37,19 +37,19 @@ const DeleteSelectedPageGroup:FC<Props> = (props:Props) => {
         id="check-all-pages"
         id="check-all-pages"
         type="checkbox"
         type="checkbox"
         name="check-all-pages"
         name="check-all-pages"
-        className="custom-control custom-checkbox ml-1"
+        className="custom-control custom-checkbox ml-1 align-self-center"
         onChange={changeCheckboxStateHandler}
         onChange={changeCheckboxStateHandler}
         checked={checkboxState === CheckboxType.INDETERMINATE || checkboxState === CheckboxType.ALL_CHECKED}
         checked={checkboxState === CheckboxType.INDETERMINATE || checkboxState === CheckboxType.ALL_CHECKED}
       />
       />
       <button
       <button
         type="button"
         type="button"
-        className="text-danger font-weight-light"
+        className="btn text-danger font-weight-light p-0 ml-3"
         onClick={() => {
         onClick={() => {
           if (onClickInvoked == null) { logger.error('onClickInvoked is null') }
           if (onClickInvoked == null) { logger.error('onClickInvoked is null') }
           else { onClickInvoked() }
           else { onClickInvoked() }
         }}
         }}
       >
       >
-        <i className="icon-trash ml-3"></i>
+        <i className="icon-trash"></i>
         {t('search_result.delete_all_selected_page')}
         {t('search_result.delete_all_selected_page')}
       </button>
       </button>
     </>
     </>

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

@@ -0,0 +1,59 @@
+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,
+  onChangedInvoked?: (page: ISearchedPage) => 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}
+            onChangedInvoked={props.onChangedInvoked}
+            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;

+ 68 - 27
packages/app/src/components/SearchPage/SearchResultListItem.tsx

@@ -1,19 +1,73 @@
-import React, { FC } from 'react';
-
+import React, { FC } from 'react';
+import { useTranslation } from 'react-i18next';
 import { UserPicture, PageListMeta, PagePathLabel } from '@growi/ui';
 import { UserPicture, PageListMeta, PagePathLabel } from '@growi/ui';
 import { DevidedPagePath } from '@growi/core';
 import { DevidedPagePath } from '@growi/core';
+import { ISearchedPage } from './SearchResultList';
 
 
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
-import { ISearchedPage } from '../../interfaces/page';
-
 const logger = loggerFactory('growi:searchResultList');
 const logger = loggerFactory('growi:searchResultList');
 
 
-type Props ={
+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,
   page: ISearchedPage,
   isSelected: boolean,
   isSelected: boolean,
-  onClickInvoked?: (data: string) => void,
   onChangedInvoked?: (page: ISearchedPage) => void,
   onChangedInvoked?: (page: ISearchedPage) => void,
+  onClickInvoked?: (pageId: string) => void,
 }
 }
 
 
 const SearchResultListItem: FC<Props> = (props:Props) => {
 const SearchResultListItem: FC<Props> = (props:Props) => {
@@ -28,10 +82,6 @@ const SearchResultListItem: FC<Props> = (props:Props) => {
   const dPagePath = new DevidedPagePath(page.path, false, true);
   const dPagePath = new DevidedPagePath(page.path, false, true);
   const pagePathElem = <PagePathLabel page={page} isFormerOnly />;
   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
-
   return (
   return (
     <li key={page._id} className={`page-list-li w-100 border-bottom pr-4 list-group-item-action ${isSelected ? 'active' : ''}`}>
     <li key={page._id} className={`page-list-li w-100 border-bottom pr-4 list-group-item-action ${isSelected ? 'active' : ''}`}>
       <a
       <a
@@ -81,30 +131,21 @@ const SearchResultListItem: FC<Props> = (props:Props) => {
               <div className="d-flex mx-2">
               <div className="d-flex mx-2">
                 <PageListMeta page={page} />
                 <PageListMeta page={page} />
               </div>
               </div>
-              {/* doropdown icon */}
+              {/* doropdown icon includes page control buttons */}
               <div className="ml-auto">
               <div className="ml-auto">
-                <i className="fa fa-ellipsis-v text-muted"></i>
+                <PageItemControl page={page} />
               </div>
               </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>
             </div>
-            {/* eslint-disable-next-line react/no-danger */}
-            <div className="mt-1" dangerouslySetInnerHTML={{ __html: page.elasticSearchResult.snippet }}></div>
           </div>
           </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>
       </a>
     </li>
     </li>
   );
   );
 };
 };
+
 export default SearchResultListItem;
 export default SearchResultListItem;

+ 11 - 13
packages/app/src/interfaces/page.ts

@@ -1,23 +1,21 @@
-import { IUser } from './user';
+import { IUser } from './user';
 import { IRevision } from './revision';
 import { IRevision } from './revision';
 import { ITag } from './tag';
 import { ITag } from './tag';
 
 
 export type IPage = {
 export type IPage = {
   path: string,
   path: string,
   status: 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,
   createdAt: Date,
   updatedAt: Date,
   updatedAt: Date,
-  seenUsers: string[]
-}
+};
 
 
-export type ISearchedPage = IPage & {
+export type IPageHasId = IPage & {
   _id: string,
   _id: string,
-  noLink: boolean,
-  lastUpdateUser: any,
-  elasticSearchResult: {
-    snippet: string,
-  }
-}
+};