ソースを参照

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

feat: create dropdown icon
Yuki Takei 4 年 前
コミット
7aed3af0ba

+ 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",

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

@@ -1,32 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import SearchResultListItem from './SearchResultListItem';
-
-class SearchResultList extends React.Component {
-
-  render() {
-    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
-          page={page}
-          onClickInvoked={this.props.onClickInvoked}
-          noLink
-        />
-      );
-    });
-  }
-
-}
-
-SearchResultList.propTypes = {
-  pages: PropTypes.array.isRequired,
-  deletionMode: PropTypes.bool.isRequired,
-  selectedPages: PropTypes.array.isRequired,
-  onClickInvoked: PropTypes.func,
-  onChangeInvoked: PropTypes.func,
-};
-
-export default SearchResultList;

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

@@ -0,0 +1,37 @@
+import React, { FC } from 'react';
+import SearchResultListItem from './SearchResultListItem';
+import { IPageHasId } from '../../interfaces/page';
+
+// 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[],
+  deletionMode: boolean,
+  selectedPages: ISearchedPage[],
+  onClickInvoked?: (pageId: string) => void,
+}
+
+const SearchResultList: FC<Props> = (props:Props) => {
+  return (
+    <>
+      {props.pages.map((page) => {
+        return (
+          <SearchResultListItem
+            page={page}
+            onClickInvoked={props.onClickInvoked}
+          />
+        );
+      })}
+    </>
+  );
+
+};
+
+export default SearchResultList;

+ 72 - 42
packages/app/src/components/SearchPage/SearchResultListItem.tsx

@@ -1,28 +1,73 @@
 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,
-    }
-  },
-  onClickInvoked: (data: string) => void,
+  page: ISearchedPage,
+  onClickInvoked?: (pageId: string) => void,
 }
 
-const SearchResultListItem: FC<Props> = (props:Props) => {
 
-  const { page, onClickInvoked } = props;
+const PageItemControl: FC<Props> = (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>
+    </>
+  );
+
+};
+
+
+const SearchResultListItem: FC<Props> = (props:Props) => {
+  const { page } = 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}`;
@@ -30,24 +75,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">
       <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 */}
@@ -70,30 +109,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,
+};