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

Merge branch 'dev/5.0.x' into fix/83698-dot-button-design

* dev/5.0.x: (22 commits)
  Improved code
  remove role and btn-group
  remove font-size middium to apply default font size 14px
  expand range of triangle icon active color
  refs #83365: search select checkbox indeterminate - rename file
  remove margin
  refs #83365: search select checkbox indeterminate - sepalate interface for indeterminate input. - clean the source code.
  refs #83365: search select checkbox indeterminate - prepare interface for indeterminate input.
  refs #83365: search select checkbox indeterminate - WIP: remove unused func import
  refs #83365: search select checkbox indeterminate - WIP: set indeterminate attribute
  Improved to not use config
  Improved config type
  Renamed config key
  Improved process
  remove icon and modify border
  Improved condition
  Improved conditions
  Throw err
  WIP
  refs #83365: search select checkbox indeterminate - WIP: set indeterminate attribute
  ...
Mao 4 лет назад
Родитель
Сommit
1080a9ec54

+ 12 - 4
packages/app/src/components/SearchPage/DeleteSelectedPageGroup.tsx

@@ -1,5 +1,6 @@
-import React, { FC } from 'react';
+import React, { FC, useEffect, useRef } from 'react';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
+import { IndeterminateInputElement } from '~/interfaces/indeterminate-input-elm';
 import { CheckboxType } from '../../interfaces/search';
 import { CheckboxType } from '../../interfaces/search';
 
 
 type Props = {
 type Props = {
@@ -26,18 +27,25 @@ const DeleteSelectedPageGroup:FC<Props> = (props:Props) => {
     if (onClickDeleteAllButton != null) { onClickDeleteAllButton() }
     if (onClickDeleteAllButton != null) { onClickDeleteAllButton() }
   };
   };
 
 
+  const selectAllCheckboxElm = useRef<IndeterminateInputElement>(null);
+  useEffect(() => {
+    if (selectAllCheckboxElm.current != null) {
+      selectAllCheckboxElm.current.indeterminate = selectAllCheckboxType === CheckboxType.INDETERMINATE;
+    }
+  }, [selectAllCheckboxType]);
+
   return (
   return (
 
 
     <div className="d-flex align-items-center">
     <div className="d-flex align-items-center">
-      {/** todo: implement the design for CheckboxType = INDETERMINATE */}
       <input
       <input
         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 align-self-center"
+        className="grw-indeterminate-checkbox"
+        ref={selectAllCheckboxElm}
         disabled={props.isSelectAllCheckboxDisabled}
         disabled={props.isSelectAllCheckboxDisabled}
         onClick={onClickCheckbox}
         onClick={onClickCheckbox}
-        checked={selectAllCheckboxType !== CheckboxType.NONE_CHECKED}
+        checked={selectAllCheckboxType === CheckboxType.ALL_CHECKED}
       />
       />
       <button
       <button
         type="button"
         type="button"

+ 4 - 13
packages/app/src/components/SearchPage/SortControl.tsx

@@ -25,15 +25,6 @@ const SortControl: FC <Props> = (props: Props) => {
     return <i className={iconClassName} aria-hidden="true" />;
     return <i className={iconClassName} aria-hidden="true" />;
   };
   };
 
 
-  const renderSortItem = (sort, order) => {
-    return (
-      <div className="d-flex align-items-center justify-content-between w-100">
-        <span className="mr-3">{t(`search_result.sort_axis.${sort}`)}</span>
-        {renderOrderIcon(order)}
-      </div>
-    );
-  };
-
   return (
   return (
     <>
     <>
       <div className="input-group">
       <div className="input-group">
@@ -42,10 +33,10 @@ const SortControl: FC <Props> = (props: Props) => {
             {renderOrderIcon(props.order)}
             {renderOrderIcon(props.order)}
           </div>
           </div>
         </div>
         </div>
-        <div className="btn-group" role="group">
+        <div className="border rounded-right">
           <button
           <button
             type="button"
             type="button"
-            className="btn border dropdown-toggle"
+            className="btn dropdown-toggle"
             data-toggle="dropdown"
             data-toggle="dropdown"
           >
           >
             <span className="mr-4 text-secondary">{t(`search_result.sort_axis.${props.sort}`)}</span>
             <span className="mr-4 text-secondary">{t(`search_result.sort_axis.${props.sort}`)}</span>
@@ -56,11 +47,11 @@ const SortControl: FC <Props> = (props: Props) => {
               return (
               return (
                 <button
                 <button
                   key={sortAxis}
                   key={sortAxis}
-                  className="dropdown-item d-flex justify-content-between"
+                  className="dropdown-item"
                   type="button"
                   type="button"
                   onClick={() => { onClickChangeSort(sortAxis, nextOrder) }}
                   onClick={() => { onClickChangeSort(sortAxis, nextOrder) }}
                 >
                 >
-                  {renderSortItem(sortAxis, nextOrder)}
+                  <span>{t(`search_result.sort_axis.${sortAxis}`)}</span>
                 </button>
                 </button>
               );
               );
             })}
             })}

+ 1 - 1
packages/app/src/components/Sidebar/PageTree/Item.tsx

@@ -218,7 +218,7 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
       )}
       )}
       {
       {
         isOpen && hasChildren() && currentChildren.map(node => (
         isOpen && hasChildren() && currentChildren.map(node => (
-          <div key={node.page._id} className="grw-pagetree-item-container mt-2">
+          <div key={node.page._id} className="grw-pagetree-item-container">
             <Item
             <Item
               isEnableActions={isEnableActions}
               isEnableActions={isEnableActions}
               itemNode={node}
               itemNode={node}

+ 3 - 0
packages/app/src/interfaces/indeterminate-input-elm.ts

@@ -0,0 +1,3 @@
+export interface IndeterminateInputElement extends HTMLInputElement {
+  indeterminate:boolean
+}

+ 51 - 19
packages/app/src/server/service/page.js

@@ -865,23 +865,57 @@ class PageService {
     }
     }
   }
   }
 
 
+  async _isPagePathIndexUnique() {
+    const Page = this.crowi.model('Page');
+    const now = (new Date()).toString();
+    const path = `growi_check_is_path_index_unique_${now}`;
+
+    let isUnique = false;
+
+    try {
+      await Page.insertMany([
+        { path },
+        { path },
+      ]);
+    }
+    catch (err) {
+      if (err?.code === 11000) { // Error code 11000 indicates the index is unique
+        isUnique = true;
+        logger.info('Page path index is unique.');
+      }
+      else {
+        throw err;
+      }
+    }
+    finally {
+      await Page.deleteMany({ path: { $regex: new RegExp('growi_check_is_path_index_unique', 'g') } });
+    }
+
+
+    return isUnique;
+  }
+
+  // TODO: use socket to send status to the client
   async v5InitialMigration(grant) {
   async v5InitialMigration(grant) {
     // const socket = this.crowi.socketIoService.getAdminSocket();
     // const socket = this.crowi.socketIoService.getAdminSocket();
-    const Page = this.crowi.model('Page');
-    const indexStatus = await Page.aggregate([{ $indexStats: {} }]);
-    const pathIndexStatus = indexStatus.filter(status => status.name === 'path_1')?.[0];
-    const isPathIndexExists = pathIndexStatus != null;
-    const isUnique = isPathIndexExists && pathIndexStatus.spec?.unique === true;
+
+    let isUnique;
+    try {
+      isUnique = await this._isPagePathIndexUnique();
+    }
+    catch (err) {
+      logger.error('Failed to check path index status', err);
+      throw err;
+    }
 
 
     // drop unique index first
     // drop unique index first
-    if (isUnique || !isPathIndexExists) {
+    if (isUnique) {
       try {
       try {
-        await this._v5NormalizeIndex(isPathIndexExists);
+        await this._v5NormalizeIndex();
       }
       }
       catch (err) {
       catch (err) {
         logger.error('V5 index normalization failed.', err);
         logger.error('V5 index normalization failed.', err);
         // socket.emit('v5IndexNormalizationFailed', { error: err.message });
         // socket.emit('v5IndexNormalizationFailed', { error: err.message });
-
         throw err;
         throw err;
       }
       }
     }
     }
@@ -1078,19 +1112,17 @@ class PageService {
 
 
   }
   }
 
 
-  async _v5NormalizeIndex(isPathIndexExists) {
+  async _v5NormalizeIndex() {
     const collection = mongoose.connection.collection('pages');
     const collection = mongoose.connection.collection('pages');
 
 
-    if (isPathIndexExists) {
-      try {
-        // drop pages.path_1 indexes
-        await collection.dropIndex('path_1');
-        logger.info('Succeeded to drop unique indexes from pages.path.');
-      }
-      catch (err) {
-        logger.warn('Failed to drop unique indexes from pages.path.', err);
-        throw err;
-      }
+    try {
+      // drop pages.path_1 indexes
+      await collection.dropIndex('path_1');
+      logger.info('Succeeded to drop unique indexes from pages.path.');
+    }
+    catch (err) {
+      logger.warn('Failed to drop unique indexes from pages.path.', err);
+      throw err;
     }
     }
 
 
     try {
     try {

+ 9 - 10
packages/app/src/stores/bookmark.ts

@@ -3,14 +3,13 @@ import { apiv3Get } from '../client/util/apiv3-client';
 import { IBookmarkInfo } from '../interfaces/bookmark-info';
 import { IBookmarkInfo } from '../interfaces/bookmark-info';
 
 
 
 
-export const useSWRBookmarkInfo = (pageId: string): SWRResponse<IBookmarkInfo, Error> => {
-  return useSWR(
-    `/bookmarks/info?pageId=${pageId}`,
-    endpoint => apiv3Get(endpoint).then((response) => {
-      return {
-        sumOfBookmarks: response.data.sumOfBookmarks,
-        isBookmarked: response.data.isBookmarked,
-      };
-    }),
-  );
+export const useSWRBookmarkInfo = (pageId: string | null): SWRResponse<IBookmarkInfo, Error> => {
+  return useSWR(pageId != null
+    ? `/bookmarks/info?pageId=${pageId}` : null,
+  endpoint => apiv3Get(endpoint).then((response) => {
+    return {
+      sumOfBookmarks: response.data.sumOfBookmarks,
+      isBookmarked: response.data.isBookmarked,
+    };
+  }));
 };
 };

+ 2 - 2
packages/app/src/stores/page.tsx

@@ -46,8 +46,8 @@ export const useSWRxPageList = (
   );
   );
 };
 };
 
 
-export const useSWRPageInfo = (pageId: string): SWRResponse<IPageInfo, Error> => {
-  return useSWR(`/page/info?pageId=${pageId}`, endpoint => apiv3Get(endpoint).then((response) => {
+export const useSWRPageInfo = (pageId: string | null): SWRResponse<IPageInfo, Error> => {
+  return useSWR(pageId != null ? `/page/info?pageId=${pageId}` : null, endpoint => apiv3Get(endpoint).then((response) => {
     return {
     return {
       sumOfLikers: response.data.sumOfLikers,
       sumOfLikers: response.data.sumOfLikers,
       likerIds: response.data.likerIds,
       likerIds: response.data.likerIds,

+ 0 - 1
packages/app/src/styles/_page-tree.scss

@@ -37,7 +37,6 @@ $grw-pagetree-item-padding-left: 10px;
 
 
       .grw-pagetree-title {
       .grw-pagetree-title {
         overflow: hidden;
         overflow: hidden;
-        font-size: medium;
         text-overflow: ellipsis;
         text-overflow: ellipsis;
       }
       }
     }
     }

+ 1 - 1
packages/app/src/styles/theme/_apply-colors-dark.scss

@@ -259,7 +259,7 @@ ul.pagination {
         background: $bgcolor-list-hover;
         background: $bgcolor-list-hover;
       }
       }
 
 
-      .grw-triangle-icon {
+      .grw-pagetree-button {
         &:not(:hover) {
         &:not(:hover) {
           svg {
           svg {
             fill: $gray-500;
             fill: $gray-500;

+ 1 - 1
packages/app/src/styles/theme/_apply-colors-light.scss

@@ -176,7 +176,7 @@ $border-color: $border-color-global;
         background: $bgcolor-list-hover;
         background: $bgcolor-list-hover;
       }
       }
 
 
-      .grw-triangle-icon {
+      .grw-pagetree-button {
         &:not(:hover) {
         &:not(:hover) {
           svg {
           svg {
             fill: $gray-400;
             fill: $gray-400;