Procházet zdrojové kódy

Merge remote-tracking branch 'origin/show-identical-page-list' into feat/identical-pages

Yuki Takei před 4 roky
rodič
revize
b8b6f4f15d

+ 34 - 5
packages/app/src/components/IdenticalPathPage.tsx

@@ -1,16 +1,45 @@
-import React, { FC } from 'react';
+import React, {
+  FC,
+} from 'react';
+
+import { withUnstatedContainers } from './UnstatedUtils';
+import AppContainer from '~/client/services/AppContainer';
+import PageListItem from './Page/PageListItem';
 
 
 type IdenticalPathPageProps= {
 type IdenticalPathPageProps= {
   // add props and types here
   // add props and types here
 }
 }
+const jsonNull = 'null';
+
 const IdenticalPathPage:FC<IdenticalPathPageProps> = (props:IdenticalPathPageProps) => {
 const IdenticalPathPage:FC<IdenticalPathPageProps> = (props:IdenticalPathPageProps) => {
+  const identicalPageDocument = document.getElementById('identical-path-page-list');
+  const pageDataList = JSON.parse(identicalPageDocument?.getAttribute('data-identical-page-data-list') || jsonNull);
+  const shortbodyMap = JSON.parse(identicalPageDocument?.getAttribute('data-shortody-map') || jsonNull);
+
   return (
   return (
-    <div>
+    <div className="container">
       {/* Todo: show alert */}
       {/* Todo: show alert */}
-      {/* Todo: show identical path page list */}
-      IdenticalPathPageList
+
+      {/* identical page list */}
+      <div className="page-list">
+        <ul className="page-list-ul list-group-flush border px-3">
+          {pageDataList.map((data) => {
+            return (
+              <PageListItem
+                key={data.pageData._id}
+                page={data}
+                isSelected={false}
+                isChecked={false}
+                isEnableActions
+                shortBody={shortbodyMap[data.pageData._id]}
+              // Todo: add onClickDeleteButton when delete feature implemented
+              />
+            );
+          })}
+        </ul>
+      </div>
     </div>
     </div>
   );
   );
 };
 };
 
 
-export default IdenticalPathPage;
+export default withUnstatedContainers(IdenticalPathPage, [AppContainer]);

+ 9 - 4
packages/app/src/components/Page/PageListItem.tsx

@@ -62,12 +62,16 @@ const PageListItem: FC<Props> = memo((props:Props) => {
     }
     }
   }, [isDeviceSmallerThanLg, onClickSearchResultItem, pageData._id]);
   }, [isDeviceSmallerThanLg, onClickSearchResultItem, pageData._id]);
 
 
+  const styleListGroupItem = (!isDeviceSmallerThanLg && onClickCheckbox != null) ? 'list-group-item-action' : '';
   // background color of list item changes when class "active" exists under 'grw-search-result-item'
   // background color of list item changes when class "active" exists under 'grw-search-result-item'
-  const responsiveListStyleClass = `${isDeviceSmallerThanLg ? '' : `list-group-item-action ${isSelected ? 'active' : ''}`}`;
+  const styleActive = !isDeviceSmallerThanLg && isSelected ? 'active' : '';
+  const styleBorder = onClickCheckbox != null ? 'border-bottom' : 'list-group-item p-0';
+
   return (
   return (
     <li
     <li
       key={pageData._id}
       key={pageData._id}
-      className={`w-100 grw-search-result-item border-bottom ${responsiveListStyleClass}`}
+      className={`w-100 grw-search-result-item search-result-list ${styleListGroupItem} ${styleActive} ${styleBorder}}`
+      }
     >
     >
       <div
       <div
         className="h-100 text-break"
         className="h-100 text-break"
@@ -98,7 +102,7 @@ const PageListItem: FC<Props> = memo((props:Props) => {
             <div className="d-flex align-items-center mb-2">
             <div className="d-flex align-items-center mb-2">
               {/* Picture */}
               {/* Picture */}
               <span className="mr-2 d-none d-md-block">
               <span className="mr-2 d-none d-md-block">
-                <UserPicture user={pageData.lastUpdateUser} size="sm" />
+                <UserPicture user={pageData.lastUpdateUser} size="custom-md" />
               </span>
               </span>
               {/* page title */}
               {/* page title */}
               <Clamp lines={1}>
               <Clamp lines={1}>
@@ -109,7 +113,7 @@ const PageListItem: FC<Props> = memo((props:Props) => {
 
 
               {/* page meta */}
               {/* page meta */}
               <div className="d-none d-md-flex item-meta py-0 px-1">
               <div className="d-none d-md-flex item-meta py-0 px-1">
-                <PageListMeta page={pageData} bookmarkCount={pageMeta.bookmarkCount} />
+                <PageListMeta page={pageData} bookmarkCount={pageMeta.bookmarkCount} shouldSpaceOutIcon />
               </div>
               </div>
               {/* doropdown icon includes page control buttons */}
               {/* doropdown icon includes page control buttons */}
               <div className="item-control ml-auto">
               <div className="item-control ml-auto">
@@ -118,6 +122,7 @@ const PageListItem: FC<Props> = memo((props:Props) => {
                   onClickDeleteButtonHandler={props.onClickDeleteButton}
                   onClickDeleteButtonHandler={props.onClickDeleteButton}
                   isEnableActions={isEnableActions}
                   isEnableActions={isEnableActions}
                   isDeletable={!isTopPage(pageData.path)}
                   isDeletable={!isTopPage(pageData.path)}
+                  // Todo: add onClickRenameButtonHandler
                 />
                 />
               </div>
               </div>
             </div>
             </div>

+ 4 - 1
packages/app/src/server/models/page.ts

@@ -7,7 +7,7 @@ import mongoosePaginate from 'mongoose-paginate-v2';
 import uniqueValidator from 'mongoose-unique-validator';
 import uniqueValidator from 'mongoose-unique-validator';
 import nodePath from 'path';
 import nodePath from 'path';
 
 
-import { getOrCreateModel, pagePathUtils } from '@growi/core';
+import { getOrCreateModel, pagePathUtils, getModelSafely } from '@growi/core';
 import loggerFactory from '../../utils/logger';
 import loggerFactory from '../../utils/logger';
 import Crowi from '../crowi';
 import Crowi from '../crowi';
 import { IPage } from '../../interfaces/page';
 import { IPage } from '../../interfaces/page';
@@ -218,6 +218,9 @@ schema.statics.findByPathAndViewer = async function(
 
 
   const baseQuery = useFindOne ? this.findOne({ path }) : this.find({ path });
   const baseQuery = useFindOne ? this.findOne({ path }) : this.find({ path });
   const queryBuilder = new PageQueryBuilder(baseQuery, includeEmpty);
   const queryBuilder = new PageQueryBuilder(baseQuery, includeEmpty);
+
+  const User = getModelSafely('User') || require('~/server/models/user')();
+  queryBuilder.populateDataToList(User.USER_FIELDS_EXCEPT_CONFIDENTIAL);
   await addViewerCondition(queryBuilder, user, userGroups);
   await addViewerCondition(queryBuilder, user, userGroups);
 
 
   return queryBuilder.query.exec();
   return queryBuilder.query.exec();

+ 15 - 1
packages/app/src/server/routes/page.js

@@ -142,6 +142,7 @@ module.exports = function(crowi, app) {
 
 
   const Page = crowi.model('Page');
   const Page = crowi.model('Page');
   const User = crowi.model('User');
   const User = crowi.model('User');
+  const Bookmark = crowi.model('Bookmark');
   const PageTagRelation = crowi.model('PageTagRelation');
   const PageTagRelation = crowi.model('PageTagRelation');
   const GlobalNotificationSetting = crowi.model('GlobalNotificationSetting');
   const GlobalNotificationSetting = crowi.model('GlobalNotificationSetting');
   const ShareLink = crowi.model('ShareLink');
   const ShareLink = crowi.model('ShareLink');
@@ -613,8 +614,21 @@ module.exports = function(crowi, app) {
     const { redirectFrom } = req.query;
     const { redirectFrom } = req.query;
 
 
     if (pages.length >= 2) {
     if (pages.length >= 2) {
+
+      const pageIds = pages.map(p => p._id);
+      const shortBodyMap = await crowi.pageService.shortBodiesMapByPageIds(pageIds);
+      const identicalPageDataList = await Promise.all(pages.map(async(page) => {
+        const bookmarkCount = await Bookmark.countByPageId(page._id);
+        page._doc.seenUserCount = (page.seenUsers && page.seenUsers.length) || 0;
+        return {
+          pageData: page,
+          pageMeta: {
+            bookmarkCount,
+          },
+        };
+      }));
       return res.render('layout-growi/identical-path-page-list', {
       return res.render('layout-growi/identical-path-page-list', {
-        pages, redirectFrom,
+        identicalPageDataList, shortBodyMap, redirectFrom,
       });
       });
     }
     }
 
 

+ 6 - 1
packages/app/src/server/views/layout-growi/identical-path-page-list.html

@@ -2,5 +2,10 @@
 
 
 {% block content_main %}
 {% block content_main %}
 <div id="grw-fav-sticky-trigger" class="sticky-top"></div>
 <div id="grw-fav-sticky-trigger" class="sticky-top"></div>
-<div id="identical-path-page-list"></div>
+<div
+  id="identical-path-page-list"
+  data-identical-page-data-list="{{ identicalPageDataList|json }}"
+  data-shortody-map="{{ shortBodyMap|json }}"
+>
+</div>
 {% endblock %}
 {% endblock %}

+ 4 - 0
packages/app/src/styles/_page_list.scss

@@ -25,6 +25,10 @@ body .page-list {
       width: 16px;
       width: 16px;
       height: 16px;
       height: 16px;
       vertical-align: text-bottom;
       vertical-align: text-bottom;
+      &-custom-md {
+        width: 20px;
+        height: 20px;
+      }
     }
     }
 
 
     .page-list-meta {
     .page-list-meta {

+ 0 - 4
packages/app/src/styles/_search.scss

@@ -218,10 +218,6 @@
     }
     }
   }
   }
   .search-item-text {
   .search-item-text {
-    .picture-sm {
-      width: 20px;
-      height: 20px;
-    }
     .item-meta {
     .item-meta {
       .top-label {
       .top-label {
         display: none; // not show top label in search result list
         display: none; // not show top label in search result list

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

@@ -9,39 +9,39 @@ const { checkTemplatePath } = templateChecker;
 export class PageListMeta extends React.Component {
 export class PageListMeta extends React.Component {
 
 
   render() {
   render() {
-    const { page } = this.props;
+    const { page, shouldSpaceOutIcon } = this.props;
 
 
     // top check
     // top check
     let topLabel;
     let topLabel;
     if (isTopPage(page.path)) {
     if (isTopPage(page.path)) {
-      topLabel = <span className="badge badge-info meta-icon top-label">TOP</span>;
+      topLabel = <span className={`badge badge-info ${shouldSpaceOutIcon ? 'mr-3' : ''} top-label`}>TOP</span>;
     }
     }
 
 
     // template check
     // template check
     let templateLabel;
     let templateLabel;
     if (checkTemplatePath(page.path)) {
     if (checkTemplatePath(page.path)) {
-      templateLabel = <span className="badge badge-info meta-icon">TMPL</span>;
+      templateLabel = <span className={`badge badge-info ${shouldSpaceOutIcon ? 'mr-3' : ''}`}>TMPL</span>;
     }
     }
 
 
     let commentCount;
     let commentCount;
     if (page.commentCount != null && page.commentCount > 0) {
     if (page.commentCount != null && page.commentCount > 0) {
-      commentCount = <span className="meta-icon"><i className="icon-bubble" />{page.commentCount}</span>;
+      commentCount = <span className={`${shouldSpaceOutIcon ? 'mr-3' : ''}`}><i className="icon-bubble" />{page.commentCount}</span>;
     }
     }
 
 
     let likerCount;
     let likerCount;
     if (page.liker != null && page.liker.length > 0) {
     if (page.liker != null && page.liker.length > 0) {
-      likerCount = <span className="meta-icon"><i className="fa fa-heart-o" />{page.liker.length}</span>;
+      likerCount = <span className={`${shouldSpaceOutIcon ? 'mr-3' : ''}`}><i className="fa fa-heart-o" />{page.liker.length}</span>;
     }
     }
 
 
     let locked;
     let locked;
     if (page.grant !== 1) {
     if (page.grant !== 1) {
-      locked = <span className="meta-icon"><i className="icon-lock" /></span>;
+      locked = <span className={`${shouldSpaceOutIcon ? 'mr-3' : ''}`}><i className="icon-lock" /></span>;
     }
     }
 
 
     let seenUserCount;
     let seenUserCount;
     if (page.seenUserCount > 0) {
     if (page.seenUserCount > 0) {
       seenUserCount = (
       seenUserCount = (
-        <span className="meta-icon">
+        <span className={`${shouldSpaceOutIcon ? 'mr-3' : ''}`}>
           <i className="footstamp-icon"><FootstampIcon /></i>
           <i className="footstamp-icon"><FootstampIcon /></i>
           {page.seenUsers.length}
           {page.seenUsers.length}
         </span>
         </span>
@@ -50,7 +50,7 @@ export class PageListMeta extends React.Component {
 
 
     let bookmarkCount;
     let bookmarkCount;
     if (this.props.bookmarkCount > 0) {
     if (this.props.bookmarkCount > 0) {
-      bookmarkCount = <span className="meta-icon"><i className="fa fa-bookmark-o" />{this.props.bookmarkCount}</span>;
+      bookmarkCount = <span className={`${shouldSpaceOutIcon ? 'mr-3' : ''}`}><i className="fa fa-bookmark-o" />{this.props.bookmarkCount}</span>;
     }
     }
 
 
     return (
     return (
@@ -71,4 +71,5 @@ export class PageListMeta extends React.Component {
 PageListMeta.propTypes = {
 PageListMeta.propTypes = {
   page: PropTypes.object.isRequired,
   page: PropTypes.object.isRequired,
   bookmarkCount: PropTypes.number,
   bookmarkCount: PropTypes.number,
+  shouldSpaceOutIcon: PropTypes.bool,
 };
 };