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

Merge branch 'dev/7.0.x' into feat/132682-create-todays-page

ryoji-s 2 лет назад
Родитель
Сommit
f3e93813d5
61 измененных файлов с 736 добавлено и 443 удалено
  1. 1 1
      apps/app/src/components/Admin/UserGroupDetail/UserGroupDetailPage.module.scss
  2. 1 1
      apps/app/src/components/Common/CopyDropdown/CopyDropdown.jsx
  3. 18 0
      apps/app/src/components/Common/DrawerToggler/DrawerToggler.module.scss
  4. 36 0
      apps/app/src/components/Common/DrawerToggler/DrawerToggler.tsx
  5. 1 0
      apps/app/src/components/Common/DrawerToggler/index.ts
  6. 1 0
      apps/app/src/components/Common/Dropdown/PageItemControl.tsx
  7. 1 6
      apps/app/src/components/IdenticalPathPage.module.scss
  8. 0 70
      apps/app/src/components/InAppNotification/InAppNotificationElm.tsx
  9. 45 0
      apps/app/src/components/InAppNotification/PageNotification/ModelNotification.tsx
  10. 23 28
      apps/app/src/components/InAppNotification/PageNotification/PageModelNotification.tsx
  11. 18 22
      apps/app/src/components/InAppNotification/PageNotification/UserModelNotification.tsx
  12. 99 0
      apps/app/src/components/InAppNotification/PageNotification/useActionAndMsg.ts
  13. 34 0
      apps/app/src/components/Layout/PageViewLayout.module.scss
  14. 3 3
      apps/app/src/components/Layout/PageViewLayout.tsx
  15. 0 31
      apps/app/src/components/Navbar/DrawerToggler.tsx
  16. 18 19
      apps/app/src/components/Navbar/GrowiContextualSubNavigation.tsx
  17. 15 0
      apps/app/src/components/Navbar/GrowiNavbarBottom.module.scss
  18. 25 12
      apps/app/src/components/Navbar/GrowiNavbarBottom.tsx
  19. 11 10
      apps/app/src/components/Navbar/PageEditorModeManager.module.scss
  20. 4 4
      apps/app/src/components/Navbar/PageEditorModeManager.tsx
  21. 3 1
      apps/app/src/components/PageControls/BookmarkButtons.module.scss
  22. 3 1
      apps/app/src/components/PageControls/LikeButtons.module.scss
  23. 3 1
      apps/app/src/components/PageControls/PageControls.module.scss
  24. 3 1
      apps/app/src/components/PageControls/SeenUserInfo.module.scss
  25. 3 1
      apps/app/src/components/PageControls/SubscribeButton.module.scss
  26. 0 17
      apps/app/src/components/PageControls/_button-styles.scss
  27. 7 18
      apps/app/src/components/PageEditor/EditorNavbarBottom.tsx
  28. 1 11
      apps/app/src/components/PageEditor/PageEditor.tsx
  29. 1 1
      apps/app/src/components/PageList/PageList.module.scss
  30. 7 8
      apps/app/src/components/PageList/PageListItemL.tsx
  31. 1 2
      apps/app/src/components/PageList/PageListItemS.tsx
  32. 30 0
      apps/app/src/components/PageSideContents/PageAccessoriesControl.module.scss
  33. 42 0
      apps/app/src/components/PageSideContents/PageAccessoriesControl.tsx
  34. 1 4
      apps/app/src/components/PageSideContents/PageSideContents.module.scss
  35. 27 41
      apps/app/src/components/PageSideContents/PageSideContents.tsx
  36. 3 1
      apps/app/src/components/PageTags/PageTags.tsx
  37. 1 1
      apps/app/src/components/PageTags/RenderTagLabels.tsx
  38. 1 1
      apps/app/src/components/SavePageControls.tsx
  39. 1 1
      apps/app/src/components/SearchPage/SearchPageBase.module.scss
  40. 1 2
      apps/app/src/components/SearchTypeahead.tsx
  41. 20 3
      apps/app/src/components/Sidebar/AppTitle/AppTitle.module.scss
  42. 10 3
      apps/app/src/components/Sidebar/Sidebar.tsx
  43. 10 2
      apps/app/src/components/Sidebar/SidebarHead/ToggleCollapseButton.module.scss
  44. 8 5
      apps/app/src/components/Sidebar/SidebarHead/ToggleCollapseButton.tsx
  45. 1 1
      apps/app/src/components/UsersHomepageFooter.module.scss
  46. 14 52
      apps/app/src/server/routes/attachment.js
  47. 2 0
      apps/app/src/stores/page-listing.tsx
  48. 44 16
      apps/app/src/stores/ui.tsx
  49. 0 16
      apps/app/src/styles/_layout.scss
  50. 1 1
      apps/app/src/styles/_variables.scss
  51. 0 16
      apps/app/src/styles/molecules/_page-accessories-control.scss
  52. 1 1
      package.json
  53. 1 0
      packages/editor/src/services/codemirror-editor/use-codemirror-editor/use-codemirror-editor.ts
  54. 75 1
      packages/preset-themes/src/styles/default.scss
  55. 1 1
      packages/remark-lsx/src/client/components/LsxPageList/LsxListView.module.scss
  56. 12 3
      packages/remark-lsx/src/client/components/LsxPageList/LsxPage.tsx
  57. 14 1
      packages/ui/package.json
  58. 18 0
      packages/ui/scss/atoms/_btn-muted.scss
  59. 0 0
      packages/ui/scss/molecules/_page_list.scss
  60. 1 0
      packages/ui/src/components/index.ts
  61. 10 1
      yarn.lock

+ 1 - 1
apps/app/src/components/Admin/UserGroupDetail/UserGroupDetailPage.module.scss

@@ -1,2 +1,2 @@
-@use '@growi/ui/src/styles/molecules/page_list';
+@use '@growi/ui/scss/molecules/page_list';
 

+ 1 - 1
apps/app/src/components/Common/CopyDropdown/CopyDropdown.jsx

@@ -110,7 +110,7 @@ export const CopyDropdown = (props) => {
 
   return (
     <>
-      <Dropdown className={`${styles['grw-copy-dropdown']} grw-copy-dropdown`} isOpen={dropdownOpen} toggle={toggleDropdown}>
+      <Dropdown className={`${styles['grw-copy-dropdown']} grw-copy-dropdown d-print-none`} isOpen={dropdownOpen} toggle={toggleDropdown}>
         <DropdownToggle
           caret
           className={dropdownToggleClassName}

+ 18 - 0
apps/app/src/components/Common/DrawerToggler/DrawerToggler.module.scss

@@ -0,0 +1,18 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+
+@use '@growi/ui/scss/atoms/btn-muted';
+
+@use '~/styles/variables' as var;
+
+
+.grw-drawer-toggler :global {
+  .btn {
+    --bs-btn-color: rgba(var(--bs-tertiary-color-rgb), 0.5);
+    --bs-btn-bg: transparent;
+
+    --bs-btn-hover-color: rgba(var(--bs-tertiary-color-rgb), 0.7);
+
+    width: var.$grw-sidebar-nav-width;
+    height: var.$grw-sidebar-nav-width;
+  }
+}

+ 36 - 0
apps/app/src/components/Common/DrawerToggler/DrawerToggler.tsx

@@ -0,0 +1,36 @@
+import { type ReactNode } from 'react';
+
+import { useDrawerOpened } from '~/stores/ui';
+
+
+import styles from './DrawerToggler.module.scss';
+
+const moduleClass = styles['grw-drawer-toggler'];
+
+
+type Props = {
+  className?: string,
+  children?: ReactNode,
+}
+
+export const DrawerToggler = (props: Props): JSX.Element => {
+
+  const { className, children } = props;
+
+  const { data: isOpened, mutate } = useDrawerOpened();
+
+  return (
+    <div className={`${moduleClass} ${className ?? ''}`}>
+      <button
+        className="btn d-flex align-items-center border-0"
+        type="button"
+        aria-expanded="false"
+        aria-label="Toggle navigation"
+        onClick={() => mutate(!isOpened)}
+      >
+        {children}
+      </button>
+    </div>
+  );
+
+};

+ 1 - 0
apps/app/src/components/Common/DrawerToggler/index.ts

@@ -0,0 +1 @@
+export * from './DrawerToggler';

+ 1 - 0
apps/app/src/components/Common/Dropdown/PageItemControl.tsx

@@ -250,6 +250,7 @@ const PageItemControlDropdownMenu = React.memo((props: DropdownMenuProps): JSX.E
 
   return (
     <DropdownMenu
+      className="d-print-none"
       data-testid="page-item-control-menu"
       end={alignEnd}
       container="body"

+ 1 - 6
apps/app/src/components/IdenticalPathPage.module.scss

@@ -1,6 +1 @@
-@use '@growi/ui/src/styles/molecules/page_list';
-@use '~/styles/molecules/page-accessories-control';
-
-.grw-page-accessories-control :global {
-  @extend %grw-page-accessories-control;
-}
+@use '@growi/ui/scss/molecules/page_list';

+ 0 - 70
apps/app/src/components/InAppNotification/InAppNotificationElm.tsx

@@ -86,72 +86,6 @@ const InAppNotificationElm: FC<Props> = (props: Props) => {
 
   const actionUsers = getActionUsers();
 
-  const actionType: string = notification.action;
-  let actionMsg: string;
-  let actionIcon: string;
-
-  switch (actionType) {
-    case 'PAGE_LIKE':
-      actionMsg = 'liked';
-      actionIcon = 'icon-like';
-      break;
-    case 'PAGE_BOOKMARK':
-      actionMsg = 'bookmarked on';
-      actionIcon = 'icon-star';
-      break;
-    case 'PAGE_UPDATE':
-      actionMsg = 'updated on';
-      actionIcon = 'ti ti-agenda';
-      break;
-    case 'PAGE_RENAME':
-      actionMsg = 'renamed';
-      actionIcon = 'icon-action-redo';
-      break;
-    case 'PAGE_DUPLICATE':
-      actionMsg = 'duplicated';
-      actionIcon = 'icon-docs';
-      break;
-    case 'PAGE_DELETE':
-      actionMsg = 'deleted';
-      actionIcon = 'icon-trash';
-      break;
-    case 'PAGE_DELETE_COMPLETELY':
-      actionMsg = 'completely deleted';
-      actionIcon = 'icon-fire';
-      break;
-    case 'PAGE_REVERT':
-      actionMsg = 'reverted';
-      actionIcon = 'icon-action-undo';
-      break;
-    case 'PAGE_RECURSIVELY_RENAME':
-      actionMsg = 'renamed under';
-      actionIcon = 'icon-action-redo';
-      break;
-    case 'PAGE_RECURSIVELY_DELETE':
-      actionMsg = 'deleted under';
-      actionIcon = 'icon-trash';
-      break;
-    case 'PAGE_RECURSIVELY_DELETE_COMPLETELY':
-      actionMsg = 'deleted completely under';
-      actionIcon = 'icon-fire';
-      break;
-    case 'PAGE_RECURSIVELY_REVERT':
-      actionMsg = 'reverted under';
-      actionIcon = 'icon-action-undo';
-      break;
-    case 'COMMENT_CREATE':
-      actionMsg = 'commented on';
-      actionIcon = 'icon-bubble';
-      break;
-    case 'USER_REGISTRATION_APPROVAL_REQUEST':
-      actionMsg = 'requested registration approval';
-      actionIcon = 'icon-bubble';
-      break;
-    default:
-      actionMsg = '';
-      actionIcon = '';
-  }
-
   const isDropdownItem = props.type === 'dropdown-item';
 
   // determine tag
@@ -175,8 +109,6 @@ const InAppNotificationElm: FC<Props> = (props: Props) => {
           <PageModelNotification
             ref={notificationRef}
             notification={notification}
-            actionMsg={actionMsg}
-            actionIcon={actionIcon}
             actionUsers={actionUsers}
           />
         )}
@@ -184,8 +116,6 @@ const InAppNotificationElm: FC<Props> = (props: Props) => {
           <UserModelNotification
             ref={notificationRef}
             notification={notification}
-            actionMsg={actionMsg}
-            actionIcon={actionIcon}
             actionUsers={actionUsers}
           />
         )}

+ 45 - 0
apps/app/src/components/InAppNotification/PageNotification/ModelNotification.tsx

@@ -0,0 +1,45 @@
+import React, { FC, useImperativeHandle } from 'react';
+
+import type { HasObjectId } from '@growi/core';
+import { PagePathLabel } from '@growi/ui/dist/components';
+
+import type { IInAppNotificationOpenable } from '~/client/interfaces/in-app-notification-openable';
+import type { IInAppNotification } from '~/interfaces/in-app-notification';
+
+import FormattedDistanceDate from '../../FormattedDistanceDate';
+
+type Props = {
+  notification: IInAppNotification & HasObjectId
+  actionMsg: string
+  actionIcon: string
+  actionUsers: string
+  publishOpen:() => void
+  ref: React.ForwardedRef<IInAppNotificationOpenable>
+};
+
+export const ModelNotification: FC<Props> = (props) => {
+  const {
+    notification, actionMsg, actionIcon, actionUsers, publishOpen, ref,
+  } = props;
+
+  useImperativeHandle(ref, () => ({
+    open() {
+      publishOpen();
+    },
+  }));
+
+  return (
+    <div className="p-2 overflow-hidden">
+      <div className="text-truncate">
+        <b>{actionUsers}</b> {actionMsg} <PagePathLabel path={notification.parsedSnapshot?.path ?? ''} />
+      </div>
+      <i className={`${actionIcon} me-2`} />
+      <FormattedDistanceDate
+        id={notification._id}
+        date={notification.createdAt}
+        isShowTooltip={false}
+        differenceForAvoidingFormat={Number.POSITIVE_INFINITY}
+      />
+    </div>
+  );
+};

+ 23 - 28
apps/app/src/components/InAppNotification/PageNotification/PageModelNotification.tsx

@@ -1,57 +1,52 @@
 import React, {
-  forwardRef, ForwardRefRenderFunction, useImperativeHandle,
+  forwardRef, ForwardRefRenderFunction,
 } from 'react';
 
 import type { HasObjectId } from '@growi/core';
-import { PagePathLabel } from '@growi/ui/dist/components/PagePath';
 import { useRouter } from 'next/router';
 
 import type { IInAppNotificationOpenable } from '~/client/interfaces/in-app-notification-openable';
 import type { IInAppNotification } from '~/interfaces/in-app-notification';
 
-import FormattedDistanceDate from '../../FormattedDistanceDate';
+import { ModelNotification } from './ModelNotification';
+import { useActionMsgAndIconForPageModelNotification } from './useActionAndMsg';
+
 
 interface Props {
   notification: IInAppNotification & HasObjectId
-  actionMsg: string
-  actionIcon: string
   actionUsers: string
 }
 
 const PageModelNotification: ForwardRefRenderFunction<IInAppNotificationOpenable, Props> = (props: Props, ref) => {
 
   const {
-    notification, actionMsg, actionIcon, actionUsers,
+    notification, actionUsers,
   } = props;
 
+  const { actionMsg, actionIcon } = useActionMsgAndIconForPageModelNotification(notification);
+
   const router = useRouter();
 
   // publish open()
-  useImperativeHandle(ref, () => ({
-    open() {
-      if (notification.target != null) {
-        // jump to target page
-        const targetPagePath = notification.target.path;
-        if (targetPagePath != null) {
-          router.push(targetPagePath);
-        }
+  const publishOpen = () => {
+    if (notification.target != null) {
+      // jump to target page
+      const targetPagePath = notification.target.path;
+      if (targetPagePath != null) {
+        router.push(targetPagePath);
       }
-    },
-  }));
+    }
+  };
 
   return (
-    <div className="p-2 overflow-hidden">
-      <div className="text-truncate">
-        <b>{actionUsers}</b> {actionMsg} <PagePathLabel path={notification.parsedSnapshot?.path ?? ''} />
-      </div>
-      <i className={`${actionIcon} me-2`} />
-      <FormattedDistanceDate
-        id={notification._id}
-        date={notification.createdAt}
-        isShowTooltip={false}
-        differenceForAvoidingFormat={Number.POSITIVE_INFINITY}
-      />
-    </div>
+    <ModelNotification
+      notification={notification}
+      actionMsg={actionMsg}
+      actionIcon={actionIcon}
+      actionUsers={actionUsers}
+      publishOpen={publishOpen}
+      ref={ref}
+    />
   );
 };
 

+ 18 - 22
apps/app/src/components/InAppNotification/PageNotification/UserModelNotification.tsx

@@ -1,5 +1,5 @@
 import React, {
-  forwardRef, ForwardRefRenderFunction, useImperativeHandle,
+  forwardRef, ForwardRefRenderFunction,
 } from 'react';
 
 import type { HasObjectId } from '@growi/core';
@@ -8,38 +8,34 @@ import { useRouter } from 'next/router';
 import type { IInAppNotificationOpenable } from '~/client/interfaces/in-app-notification-openable';
 import type { IInAppNotification } from '~/interfaces/in-app-notification';
 
-import FormattedDistanceDate from '../../FormattedDistanceDate';
+import { ModelNotification } from './ModelNotification';
+import { useActionMsgAndIconForUserModelNotification } from './useActionAndMsg';
+
 
 const UserModelNotification: ForwardRefRenderFunction<IInAppNotificationOpenable, {
   notification: IInAppNotification & HasObjectId
-  actionMsg: string
-  actionIcon: string
   actionUsers: string
 }> = ({
-  notification, actionMsg, actionIcon, actionUsers,
+  notification, actionUsers,
 }, ref) => {
   const router = useRouter();
 
+  const { actionMsg, actionIcon } = useActionMsgAndIconForUserModelNotification(notification);
+
   // publish open()
-  useImperativeHandle(ref, () => ({
-    open() {
-      router.push('/admin/users');
-    },
-  }));
+  const publishOpen = () => {
+    router.push('/admin/users');
+  };
 
   return (
-    <div className="p-2 overflow-hidden">
-      <div className="text-truncate">
-        <b>{actionUsers}</b> {actionMsg}
-      </div>
-      <i className={`${actionIcon} me-2`} />
-      <FormattedDistanceDate
-        id={notification._id}
-        date={notification.createdAt}
-        isShowTooltip={false}
-        differenceForAvoidingFormat={Number.POSITIVE_INFINITY}
-      />
-    </div>
+    <ModelNotification
+      notification={notification}
+      actionMsg={actionMsg}
+      actionIcon={actionIcon}
+      actionUsers={actionUsers}
+      publishOpen={publishOpen}
+      ref={ref}
+    />
   );
 };
 

+ 99 - 0
apps/app/src/components/InAppNotification/PageNotification/useActionAndMsg.ts

@@ -0,0 +1,99 @@
+import type { HasObjectId } from '@growi/core';
+
+import { SupportedAction } from '~/interfaces/activity';
+import type { IInAppNotification } from '~/interfaces/in-app-notification';
+
+export type ActionMsgAndIconType = {
+  actionMsg: string
+  actionIcon: string
+}
+
+export const useActionMsgAndIconForPageModelNotification = (notification: IInAppNotification & HasObjectId): ActionMsgAndIconType => {
+  const actionType: string = notification.action;
+  let actionMsg: string;
+  let actionIcon: string;
+
+  switch (actionType) {
+    case SupportedAction.ACTION_PAGE_LIKE:
+      actionMsg = 'liked';
+      actionIcon = 'icon-like';
+      break;
+    case SupportedAction.ACTION_PAGE_BOOKMARK:
+      actionMsg = 'bookmarked on';
+      actionIcon = 'icon-star';
+      break;
+    case SupportedAction.ACTION_PAGE_UPDATE:
+      actionMsg = 'updated on';
+      actionIcon = 'ti ti-agenda';
+      break;
+    case SupportedAction.ACTION_PAGE_RENAME:
+      actionMsg = 'renamed';
+      actionIcon = 'icon-action-redo';
+      break;
+    case SupportedAction.ACTION_PAGE_DUPLICATE:
+      actionMsg = 'duplicated';
+      actionIcon = 'icon-docs';
+      break;
+    case SupportedAction.ACTION_PAGE_DELETE:
+      actionMsg = 'deleted';
+      actionIcon = 'icon-trash';
+      break;
+    case SupportedAction.ACTION_PAGE_DELETE_COMPLETELY:
+      actionMsg = 'completely deleted';
+      actionIcon = 'icon-fire';
+      break;
+    case SupportedAction.ACTION_PAGE_REVERT:
+      actionMsg = 'reverted';
+      actionIcon = 'icon-action-undo';
+      break;
+    case SupportedAction.ACTION_PAGE_RECURSIVELY_RENAME:
+      actionMsg = 'renamed under';
+      actionIcon = 'icon-action-redo';
+      break;
+    case SupportedAction.ACTION_PAGE_RECURSIVELY_DELETE:
+      actionMsg = 'deleted under';
+      actionIcon = 'icon-trash';
+      break;
+    case SupportedAction.ACTION_PAGE_RECURSIVELY_DELETE_COMPLETELY:
+      actionMsg = 'deleted completely under';
+      actionIcon = 'icon-fire';
+      break;
+    case SupportedAction.ACTION_PAGE_RECURSIVELY_REVERT:
+      actionMsg = 'reverted under';
+      actionIcon = 'icon-action-undo';
+      break;
+    case SupportedAction.ACTION_COMMENT_CREATE:
+      actionMsg = 'commented on';
+      actionIcon = 'icon-bubble';
+      break;
+    default:
+      actionMsg = '';
+      actionIcon = '';
+  }
+
+  return {
+    actionMsg,
+    actionIcon,
+  };
+};
+
+export const useActionMsgAndIconForUserModelNotification = (notification: IInAppNotification & HasObjectId): ActionMsgAndIconType => {
+  const actionType: string = notification.action;
+  let actionMsg: string;
+  let actionIcon: string;
+
+  switch (actionType) {
+    case SupportedAction.ACTION_USER_REGISTRATION_APPROVAL_REQUEST:
+      actionMsg = 'requested registration approval';
+      actionIcon = 'icon-bubble';
+      break;
+    default:
+      actionMsg = '';
+      actionIcon = '';
+  }
+
+  return {
+    actionMsg,
+    actionIcon,
+  };
+};

+ 34 - 0
apps/app/src/components/Layout/PageViewLayout.module.scss

@@ -1,3 +1,37 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+
+@use '~/styles/variables' as var;
+
+
 .page-view-layout :global {
   min-height: calc(100vh - 48px - 250px); // 100vh - subnavigation height - page-comments-row minimum height
+
+  .grw-side-contents-container {
+    margin-bottom: 1rem;
+
+    @include bs.media-breakpoint-up(lg) {
+      width: 250px;
+      min-width: 250px;
+      margin-left: 30px;
+    }
+  }
+}
+
+// md/lg layout padding
+.page-view-layout :global {
+  @include bs.media-breakpoint-between(md, xl) {
+    padding-left: var.$grw-sidebar-nav-width;
+  }
+}
+
+// sticky side contents
+.page-view-layout :global {
+  .grw-side-contents-sticky-container {
+    position: sticky;
+
+    $subnavigation-height: 50px;
+    $page-view-layout-margin-top: 32px;
+    $page-path-nav-height: 99px;
+    top: calc($subnavigation-height + $page-view-layout-margin-top + $page-path-nav-height + 4px);
+  }
 }

+ 3 - 3
apps/app/src/components/Layout/PageViewLayout.tsx

@@ -16,16 +16,16 @@ export const PageViewLayout = (props: Props): JSX.Element => {
 
   return (
     <>
-      <div id="main" className={`main page-view-layout ${styles['page-view-layout']}`}>
+      <div id="main" className={`main ${styles['page-view-layout']}`}>
         <div id="content-main" className="content-main container-lg grw-container-convertible">
           { headerContents != null && headerContents }
           { sideContents != null
             ? (
-              <div className="d-flex flex-column flex-column-reverse flex-lg-row">
+              <div className="d-flex gap-3">
                 <div className="flex-grow-1 flex-basis-0 mw-0">
                   {children}
                 </div>
-                <div className="grw-side-contents-container d-edit-none" data-vrt-blackout-side-contents>
+                <div className="grw-side-contents-container col-lg-3  d-edit-none" data-vrt-blackout-side-contents>
                   <div className="grw-side-contents-sticky-container">
                     {sideContents}
                   </div>

+ 0 - 31
apps/app/src/components/Navbar/DrawerToggler.tsx

@@ -1,31 +0,0 @@
-import React from 'react';
-
-import { useDrawerOpened } from '~/stores/ui';
-
-type Props = {
-  iconClass?: string,
-}
-
-const DrawerToggler = (props: Props): JSX.Element => {
-
-  const { data: isOpened, mutate } = useDrawerOpened();
-
-  const iconClass = props.iconClass ?? isOpened
-    ? 'icon-arrow-left'
-    : 'icon-arrow-right';
-
-  return (
-    <button
-      className="grw-drawer-toggler btn btn-secondary"
-      type="button"
-      aria-expanded="false"
-      aria-label="Toggle navigation"
-      onClick={() => mutate(!isOpened)}
-    >
-      <i className={iconClass}></i>
-    </button>
-  );
-
-};
-
-export default DrawerToggler;

+ 18 - 19
apps/app/src/components/Navbar/GrowiContextualSubNavigation.tsx

@@ -331,28 +331,27 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
     <>
       <div
         className={`${styles['grw-contextual-sub-navigation']}
-          d-flex align-items-center justify-content-end px-2 py-1 gap-2 gap-md-4
+          d-flex align-items-center justify-content-end px-2 py-1 gap-2 gap-md-4 d-print-none
         `}
         data-testid="grw-contextual-sub-nav"
       >
-        <div className="h-50">
-          {pageId != null && (
-            <PageControls
-              pageId={pageId}
-              revisionId={revisionId}
-              shareLinkId={shareLinkId}
-              path={path ?? currentPathname} // If the page is empty, "path" is undefined
-              expandContentWidth={currentPage?.expandContentWidth ?? isContainerFluid}
-              disableSeenUserInfoPopover={isSharedUser}
-              showPageControlDropdown={isAbleToShowPageManagement}
-              additionalMenuItemRenderer={additionalMenuItemsRenderer}
-              onClickDuplicateMenuItem={duplicateItemClickedHandler}
-              onClickRenameMenuItem={renameItemClickedHandler}
-              onClickDeleteMenuItem={deleteItemClickedHandler}
-              onClickSwitchContentWidth={switchContentWidthHandler}
-            />
-          )}
-        </div>
+        {pageId != null && (
+          <PageControls
+            pageId={pageId}
+            revisionId={revisionId}
+            shareLinkId={shareLinkId}
+            path={path ?? currentPathname} // If the page is empty, "path" is undefined
+            expandContentWidth={currentPage?.expandContentWidth ?? isContainerFluid}
+            disableSeenUserInfoPopover={isSharedUser}
+            showPageControlDropdown={isAbleToShowPageManagement}
+            additionalMenuItemRenderer={additionalMenuItemsRenderer}
+            onClickDuplicateMenuItem={duplicateItemClickedHandler}
+            onClickRenameMenuItem={renameItemClickedHandler}
+            onClickDeleteMenuItem={deleteItemClickedHandler}
+            onClickSwitchContentWidth={switchContentWidthHandler}
+          />
+        )}
+
         {isAbleToChangeEditorMode && (
           <PageEditorModeManager
             editorMode={editorMode}

+ 15 - 0
apps/app/src/components/Navbar/GrowiNavbarBottom.module.scss

@@ -14,3 +14,18 @@
     bottom: #{-1 * var.$grw-navbar-bottom-height};
   }
 }
+
+
+// centering icons
+.grw-navbar-bottom :global {
+  .nav-link {
+    display: flex;
+    align-items: center;
+  }
+}
+
+// == Colors
+.grw-navbar-bottom {
+  background-color: rgba(var(--bs-body-bg-rgb), 0.7);
+  backdrop-filter: blur(35px);
+}

+ 25 - 12
apps/app/src/components/Navbar/GrowiNavbarBottom.tsx

@@ -3,7 +3,7 @@ import React from 'react';
 import { useIsSearchPage } from '~/stores/context';
 import { usePageCreateModal } from '~/stores/modal';
 import { useCurrentPagePath } from '~/stores/page';
-import { useIsDeviceSmallerThanMd, useDrawerOpened } from '~/stores/ui';
+import { useIsDeviceLargerThanMd, useDrawerOpened } from '~/stores/ui';
 
 import { GlobalSearch } from './GlobalSearch';
 
@@ -13,12 +13,12 @@ import styles from './GrowiNavbarBottom.module.scss';
 export const GrowiNavbarBottom = (): JSX.Element => {
 
   const { data: isDrawerOpened, mutate: mutateDrawerOpened } = useDrawerOpened();
-  const { data: isDeviceSmallerThanMd } = useIsDeviceSmallerThanMd();
+  const { data: isDeviceLargerThanMd } = useIsDeviceLargerThanMd();
   const { open: openCreateModal } = usePageCreateModal();
   const { data: currentPagePath } = useCurrentPagePath();
   const { data: isSearchPage } = useIsSearchPage();
 
-  const additionalClasses = ['grw-navbar-bottom', styles['grw-navbar-bottom']];
+  const additionalClasses = [styles['grw-navbar-bottom']];
   if (isDrawerOpened) {
     additionalClasses.push('grw-navbar-bottom-drawer-opened');
   }
@@ -26,7 +26,7 @@ export const GrowiNavbarBottom = (): JSX.Element => {
   return (
     <div className="d-md-none d-edit-none fixed-bottom">
 
-      { isDeviceSmallerThanMd && !isSearchPage && (
+      { !isDeviceLargerThanMd && !isSearchPage && (
         <div id="grw-global-search-collapse" className="grw-global-search collapse bg-dark">
           <div className="p-3">
             <GlobalSearch dropup />
@@ -34,18 +34,29 @@ export const GrowiNavbarBottom = (): JSX.Element => {
         </div>
       ) }
 
-      <div className={`navbar navbar-expand navbar-dark bg-primary px-0 ${additionalClasses.join(' ')}`}>
+      <div className={`navbar navbar-expand px-4 px-sm-5 ${additionalClasses.join(' ')}`}>
 
-        <ul className="navbar-nav w-100">
-          <li className="nav-item me-auto">
+        <ul className="navbar-nav flex-grow-1 d-flex align-items-center justify-content-between">
+          <li className="nav-item">
             <a
               role="button"
               className="nav-link btn-lg"
               onClick={() => mutateDrawerOpened(true)}
             >
-              <i className="icon-menu"></i>
+              <span className="material-symbols-outlined fs-2">reorder</span>
             </a>
           </li>
+
+          <li className="nav-item">
+            <a
+              role="button"
+              className="nav-link btn-lg"
+              onClick={() => openCreateModal(currentPagePath || '')}
+            >
+              <span className="material-symbols-outlined fs-2">edit</span>
+            </a>
+          </li>
+
           {
             !isSearchPage && (
               <li className="nav-item">
@@ -55,20 +66,22 @@ export const GrowiNavbarBottom = (): JSX.Element => {
                   data-bs-target="#grw-global-search-collapse"
                   data-bs-toggle="collapse"
                 >
-                  <i className="icon-magnifier"></i>
+                  <span className="material-symbols-outlined fs-2">search</span>
                 </a>
               </li>
             )
           }
-          <li className="nav-item ms-auto">
+
+          <li className="nav-item">
             <a
               role="button"
               className="nav-link btn-lg"
-              onClick={() => openCreateModal(currentPagePath || '')}
+              onClick={() => {}}
             >
-              <i className="icon-pencil"></i>
+              <span className="material-symbols-outlined fs-2">notifications</span>
             </a>
           </li>
+
         </ul>
       </div>
 

+ 11 - 10
apps/app/src/components/Navbar/PageEditorModeManager.module.scss

@@ -7,11 +7,12 @@
     --bs-btn-font-size: 13px;
     --bs-btn-border-width: 2px;
 
-    width: 70px;
-    height: 30px;
-    @include bs.media-breakpoint-down(sm) {
-      width: 90px;
-      height: 38px;
+    width: 90px;
+    height: 38px;
+
+    @include bs.media-breakpoint-up(md) {
+      width: 70px;
+      height: 30px;
     }
 
     @include mixins.border-vertical('before', 70%, 1, true);
@@ -19,11 +20,11 @@
 }
 
 .grw-page-editor-mode-manager-skeleton :global {
-  width: 179px;
-  height: 30px;
-  @include bs.media-breakpoint-down(sm) {
-    width: 90px;
-    height: 38px;
+  width: 90px;
+  height: 38px;
+  @include bs.media-breakpoint-up(md) {
+    width: 179px;
+    height: 30px;
   }
 }
 

+ 4 - 4
apps/app/src/components/Navbar/PageEditorModeManager.tsx

@@ -2,7 +2,7 @@ import React, { type ReactNode, useCallback, useState } from 'react';
 
 import { useTranslation } from 'next-i18next';
 
-import { EditorMode, useIsDeviceSmallerThanMd } from '~/stores/ui';
+import { EditorMode, useIsDeviceLargerThanMd } from '~/stores/ui';
 
 import { useOnPageEditorModeButtonClicked } from './hooks';
 
@@ -61,7 +61,7 @@ export const PageEditorModeManager = (props: Props): JSX.Element => {
   const { t } = useTranslation();
   const [isCreating, setIsCreating] = useState(false);
 
-  const { data: isDeviceSmallerThanMd } = useIsDeviceSmallerThanMd();
+  const { data: isDeviceLargerThanMd } = useIsDeviceLargerThanMd();
 
   const onPageEditorModeButtonClicked = useOnPageEditorModeButtonClicked(setIsCreating, path, grant, grantUserGroupId);
   const _isBtnDisabled = isCreating || isBtnDisabled;
@@ -82,7 +82,7 @@ export const PageEditorModeManager = (props: Props): JSX.Element => {
         aria-label="page-editor-mode-manager"
         id="grw-page-editor-mode-manager"
       >
-        {(!isDeviceSmallerThanMd || editorMode !== EditorMode.View) && (
+        {(isDeviceLargerThanMd || editorMode !== EditorMode.View) && (
           <PageEditorModeButton
             currentEditorMode={editorMode}
             editorMode={EditorMode.View}
@@ -92,7 +92,7 @@ export const PageEditorModeManager = (props: Props): JSX.Element => {
             <span className="material-symbols-outlined fs-4">play_arrow</span>{t('View')}
           </PageEditorModeButton>
         )}
-        {(!isDeviceSmallerThanMd || editorMode === EditorMode.View) && (
+        {(isDeviceLargerThanMd || editorMode === EditorMode.View) && (
           <PageEditorModeButton
             currentEditorMode={editorMode}
             editorMode={EditorMode.Editor}

+ 3 - 1
apps/app/src/components/PageControls/BookmarkButtons.module.scss

@@ -1,5 +1,7 @@
 @use '@growi/core/scss/bootstrap/init' as bs;
 
+@use '@growi/ui/scss/atoms/btn-muted';
+
 @use './button-styles';
 
 .btn-group-bookmark :global {
@@ -18,7 +20,7 @@
 // == Colors
 .btn-group-bookmark :global {
   .btn-bookmark {
-    @include button-styles.btn-color(bs.$orange);
+    @include btn-muted.colorize(bs.$orange);
   }
 }
 

+ 3 - 1
apps/app/src/components/PageControls/LikeButtons.module.scss

@@ -1,5 +1,7 @@
 @use '@growi/core/scss/bootstrap/init' as bs;
 
+@use '@growi/ui/scss/atoms/btn-muted';
+
 @use './button-styles';
 
 .btn-group-like :global {
@@ -18,6 +20,6 @@
 // == Colors
 .btn-group-like :global {
   .btn-like {
-    @include button-styles.btn-color(bs.$red);
+    @include btn-muted.colorize(bs.$red);
   }
 }

+ 3 - 1
apps/app/src/components/PageControls/PageControls.module.scss

@@ -1,5 +1,7 @@
 @use '@growi/core/scss/bootstrap/init' as bs;
 
+@use '@growi/ui/scss/atoms/btn-muted';
+
 @use './button-styles';
 
 // PageItemControl styles
@@ -13,6 +15,6 @@
 // PageItemControl colors
 .grw-page-controls :global {
   .btn-page-item-control {
-    @include button-styles.btn-color(bs.$gray-500);
+    @include btn-muted.colorize(bs.$gray-500);
   }
 }

+ 3 - 1
apps/app/src/components/PageControls/SeenUserInfo.module.scss

@@ -1,5 +1,7 @@
 @use '@growi/core/scss/bootstrap/init' as bs;
 
+@use '@growi/ui/scss/atoms/btn-muted';
+
 @use './button-styles';
 
 .grw-seen-user-info :global {
@@ -18,6 +20,6 @@
   $color: #549c79;
 
   .btn-seen-user {
-    @include button-styles.btn-color($color);
+    @include btn-muted.colorize($color);
   }
 }

+ 3 - 1
apps/app/src/components/PageControls/SubscribeButton.module.scss

@@ -1,5 +1,7 @@
 @use '@growi/core/scss/bootstrap/init' as bs;
 
+@use '@growi/ui/scss/atoms/btn-muted';
+
 @use './button-styles';
 
 .btn-subscribe :global {
@@ -12,5 +14,5 @@
 
 // == Colors
 .btn-subscribe {
-  @include button-styles.btn-color(bs.$success);
+  @include btn-muted.colorize(bs.$success);
 }

+ 0 - 17
apps/app/src/components/PageControls/_button-styles.scss

@@ -15,20 +15,3 @@
 %text-total-counts-basis {
   font-size: 13px;
 }
-
-@mixin btn-color($color) {
-  $color-rgb: #{bs.to-rgb($color)};
-
-  --bs-btn-color: var(--bs-tertiary-color);
-  --bs-btn-bg: transparent;
-
-  --bs-btn-hover-color: #{$color};
-  --bs-btn-hover-bg: rgba(#{$color-rgb}, 0.2);
-
-  --bs-btn-active-color: #{$color};
-  --bs-btn-active-bg: transparent;
-
-  &:hover {
-    --bs-btn-active-bg: rgba(#{$color-rgb}, 0.2);
-  }
-}

+ 7 - 18
apps/app/src/components/PageEditor/EditorNavbarBottom.tsx

@@ -9,7 +9,7 @@ import { useIsSlackConfigured } from '~/stores/context';
 import { useSWRxSlackChannels, useIsSlackEnabled } from '~/stores/editor';
 import { useCurrentPagePath } from '~/stores/page';
 import {
-  useDrawerOpened, useEditorMode, useIsDeviceSmallerThanMd,
+  useDrawerOpened, useEditorMode, useIsDeviceLargerThanLg, useIsDeviceLargerThanMd,
 } from '~/stores/ui';
 
 
@@ -31,8 +31,8 @@ const EditorNavbarBottom = (): JSX.Element => {
 
   const { data: editorMode } = useEditorMode();
   const { data: isSlackConfigured } = useIsSlackConfigured();
-  const { mutate: mutateDrawerOpened } = useDrawerOpened();
-  const { data: isDeviceSmallerThanMd } = useIsDeviceSmallerThanMd();
+  const { data: isDeviceLargerThanMd } = useIsDeviceLargerThanMd();
+  const { data: isDeviceLargerThanLg } = useIsDeviceLargerThanLg();
   const { data: currentPagePath } = useCurrentPagePath();
   const { data: slackChannelsData } = useSWRxSlackChannels(currentPagePath);
 
@@ -58,16 +58,6 @@ const EditorNavbarBottom = (): JSX.Element => {
   }, []);
 
 
-  const renderDrawerButton = () => (
-    <button
-      type="button"
-      className="btn btn-outline-secondary border-0"
-      onClick={() => mutateDrawerOpened(true)}
-    >
-      <i className="icon-menu"></i>
-    </button>
-  );
-
   const renderExpandButton = () => (
     <div className="d-md-none ms-2">
       <button
@@ -80,13 +70,13 @@ const EditorNavbarBottom = (): JSX.Element => {
     </div>
   );
 
-  const isCollapsedOptionsSelectorEnabled = isDeviceSmallerThanMd;
+  const isCollapsedOptionsSelectorEnabled = !isDeviceLargerThanLg;
 
   return (
     <div className={`${isCollapsedOptionsSelectorEnabled ? 'fixed-bottom' : ''} `}>
       {/* Collapsed SlackNotification */}
       {isSlackConfigured && (
-        <Collapse isOpen={isSlackExpanded && isDeviceSmallerThanMd === true}>
+        <Collapse isOpen={isSlackExpanded && !isDeviceLargerThanLg}>
           <nav className={`navbar navbar-expand-lg border-top ${moduleClass}`}>
             {isSlackEnabled != null
             && (
@@ -105,13 +95,12 @@ const EditorNavbarBottom = (): JSX.Element => {
       }
       <div className={`flex-expand-horiz align-items-center border-top px-2 px-md-3 ${moduleClass}`}>
         <form>
-          { isDeviceSmallerThanMd && renderDrawerButton() }
-          { !isDeviceSmallerThanMd && <OptionsSelector /> }
+          { isDeviceLargerThanMd && <OptionsSelector /> }
         </form>
         <form className="flex-nowrap ms-auto">
           {/* Responsive Design for the SlackNotification */}
           {/* Button or the normal Slack banner */}
-          {isSlackConfigured && (isDeviceSmallerThanMd ? (
+          {isSlackConfigured && (!isDeviceLargerThanMd ? (
             <Button
               className="grw-btn-slack border me-2"
               onClick={() => (setSlackExpanded(!isSlackExpanded))}

+ 1 - 11
apps/app/src/components/PageEditor/PageEditor.tsx

@@ -336,16 +336,6 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
         // refs: https://redmine.weseek.co.jp/issues/126528
         // editorRef.current.insertText(insertText);
         codeMirrorEditor?.insertText(insertText);
-
-        // when if created newly
-        // Not using 'mutateGrant' to inherit the grant of the parent page
-        if (resAdd.pageCreated) {
-          logger.info('Page is created', resAdd.page._id);
-          mutateIsLatestRevision(true);
-          setCreatedPageRevisionIdWithAttachment(resAdd.page.revision);
-          await mutateCurrentPageId(resAdd.page._id);
-          await mutateCurrentPage();
-        }
       }
       catch (e) {
         logger.error('failed to upload', e);
@@ -358,7 +348,7 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
       }
     });
 
-  }, [codeMirrorEditor, currentPagePath, mutateCurrentPage, mutateCurrentPageId, mutateIsLatestRevision, pageId]);
+  }, [codeMirrorEditor, currentPagePath, pageId]);
 
   const acceptedFileType = useMemo(() => {
     if (!isUploadableFile) {

+ 1 - 1
apps/app/src/components/PageList/PageList.module.scss

@@ -1 +1 @@
-@use '@growi/ui/src/styles/molecules/page_list';
+@use '@growi/ui/scss/molecules/page_list';

+ 7 - 8
apps/app/src/components/PageList/PageListItemL.tsx

@@ -9,8 +9,7 @@ import type {
 import { isIPageInfoForListing, isIPageInfoForEntity } from '@growi/core';
 import { DevidedPagePath } from '@growi/core/dist/models';
 import { pathUtils } from '@growi/core/dist/utils';
-import { UserPicture } from '@growi/ui/dist/components';
-import { PageListMeta } from '@growi/ui/dist/components/PagePath';
+import { UserPicture, PageListMeta } from '@growi/ui/dist/components';
 import { format } from 'date-fns';
 import { useTranslation } from 'next-i18next';
 import Link from 'next/link';
@@ -29,7 +28,7 @@ import { useSWRMUTxCurrentUserBookmarks } from '~/stores/bookmark';
 import {
   usePageRenameModal, usePageDuplicateModal, usePageDeleteModal, usePutBackPageModal,
 } from '~/stores/modal';
-import { useIsDeviceSmallerThanLg } from '~/stores/ui';
+import { useIsDeviceLargerThanLg } from '~/stores/ui';
 
 import { useSWRMUTxPageInfo, useSWRxPageInfo } from '../../stores/page';
 import { ForceHideMenuItems, PageItemControl } from '../Common/Dropdown/PageItemControl';
@@ -83,7 +82,7 @@ const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (pr
     },
   }));
 
-  const { data: isDeviceSmallerThanLg } = useIsDeviceSmallerThanLg();
+  const { data: isDeviceLargerThanLg } = useIsDeviceLargerThanLg();
   const { open: openDuplicateModal } = usePageDuplicateModal();
   const { open: openRenameModal } = usePageRenameModal();
   const { open: openDeleteModal } = usePageDeleteModal();
@@ -117,14 +116,14 @@ const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (pr
   // click event handler
   const clickHandler = useCallback(() => {
     // do nothing if mobile
-    if (isDeviceSmallerThanLg) {
+    if (!isDeviceLargerThanLg) {
       return;
     }
 
     if (onClickItem != null) {
       onClickItem(pageData._id);
     }
-  }, [isDeviceSmallerThanLg, onClickItem, pageData._id]);
+  }, [isDeviceLargerThanLg, onClickItem, pageData._id]);
 
   const bookmarkMenuItemClickHandler = async(_pageId: string, _newValue: boolean): Promise<void> => {
     const bookmarkOperation = _newValue ? bookmark : unbookmark;
@@ -174,9 +173,9 @@ const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (pr
     openPutBackPageModal({ pageId, path }, { onPutBacked: putBackedHandler });
   }, [onPagePutBacked, openPutBackPageModal, pageData]);
 
-  const styleListGroupItem = (!isDeviceSmallerThanLg && onClickItem != null) ? 'list-group-item-action' : '';
+  const styleListGroupItem = (isDeviceLargerThanLg && onClickItem != null) ? 'list-group-item-action' : '';
   // background color of list item changes when class "active" exists under 'list-group-item'
-  const styleActive = !isDeviceSmallerThanLg && isSelected ? 'active' : '';
+  const styleActive = isDeviceLargerThanLg && isSelected ? 'active' : '';
 
   const shouldDangerouslySetInnerHTMLForPaths = elasticSearchResult != null && elasticSearchResult.highlightedPath != null;
 

+ 1 - 2
apps/app/src/components/PageList/PageListItemS.tsx

@@ -1,8 +1,7 @@
 import React from 'react';
 
 import type { IPageHasId } from '@growi/core';
-import { UserPicture } from '@growi/ui/dist/components';
-import { PageListMeta, PagePathLabel } from '@growi/ui/dist/components/PagePath';
+import { UserPicture, PageListMeta, PagePathLabel } from '@growi/ui/dist/components';
 import Link from 'next/link';
 import Clamp from 'react-multiline-clamp';
 

+ 30 - 0
apps/app/src/components/PageSideContents/PageAccessoriesControl.module.scss

@@ -0,0 +1,30 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+
+.btn-page-accessories :global {
+  display: flex;
+  align-items: center;
+  padding: 6px 8px;
+
+  .grw-labels {
+    flex-grow: 1;
+    align-items: center;
+    justify-content: space-between;
+  }
+}
+
+// apply larger font when smaller than lg
+@include bs.media-breakpoint-down(lg) {
+  .btn-page-accessories :global {
+    .material-symbols-outlined {
+      font-size: 2em;
+    }
+  }
+}
+
+// expand when larger than lg
+@include bs.media-breakpoint-up(lg) {
+  .btn-page-accessories :global {
+    flex-grow: 1;
+    padding: 1px 5px 1px 10px;
+  }
+}

+ 42 - 0
apps/app/src/components/PageSideContents/PageAccessoriesControl.tsx

@@ -0,0 +1,42 @@
+import { type ReactNode, memo } from 'react';
+
+import CountBadge from '../Common/CountBadge';
+
+
+import styles from './PageAccessoriesControl.module.scss';
+
+const moduleClass = styles['btn-page-accessories'];
+
+
+type Props = {
+  className?: string,
+  icon: ReactNode,
+  label: ReactNode,
+  count?: number,
+  onClick?: () => void,
+}
+
+export const PageAccessoriesControl = memo((props: Props): JSX.Element => {
+  const {
+    icon, label, count,
+    className,
+    onClick,
+  } = props;
+
+  return (
+    <button
+      type="button"
+      className={`btn btn-sm btn-outline-secondary ${moduleClass} ${className} rounded-pill`}
+      onClick={onClick}
+    >
+      <span className="grw-icon d-flex">{icon}</span>
+      <span className="grw-labels ms-1 d-none d-lg-flex">
+        {label}
+        {/* Do not display CountBadge if '/trash/*': https://github.com/weseek/growi/pull/7600 */}
+        { count != null
+          ? <CountBadge count={count} offset={1} />
+          : <div className="px-2"></div>}
+      </span>
+    </button>
+  );
+});

+ 1 - 4
apps/app/src/components/PageSideContents/PageSideContents.module.scss

@@ -1,5 +1,2 @@
-@use '~/styles/molecules/page-accessories-control';
-
-.grw-page-accessories-control :global {
-  @extend %grw-page-accessories-control;
+.grw-page-accessories-controls :global {
 }

+ 27 - 41
apps/app/src/components/PageSideContents/PageSideContents.tsx

@@ -4,7 +4,7 @@ import { getIdForRef, type IPageHasId, type IPageInfoForOperation } from '@growi
 import { pagePathUtils } from '@growi/core/dist/utils';
 import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
-import { Link } from 'react-scroll';
+import { scroller } from 'react-scroll';
 
 import { useUpdateStateAfterSave } from '~/client/services/page-operation';
 import { apiPost } from '~/client/util/apiv1-client';
@@ -14,12 +14,12 @@ import { useDescendantsPageListModal } from '~/stores/modal';
 import { useSWRxPageInfo, useSWRxTagsInfo } from '~/stores/page';
 import { useIsAbleToShowTagLabel } from '~/stores/ui';
 
-import CountBadge from '../Common/CountBadge';
 import { ContentLinkButtons } from '../ContentLinkButtons';
-import PageListIcon from '../Icons/PageListIcon';
 import { PageTagsSkeleton } from '../PageTags';
 import TableOfContents from '../TableOfContents';
 
+import { PageAccessoriesControl } from './PageAccessoriesControl';
+
 import styles from './PageSideContents.module.scss';
 
 
@@ -104,48 +104,34 @@ export const PageSideContents = (props: PageSideContentsProps): JSX.Element => {
       {/* Tags */}
       <Tags pageId={page._id} revisionId={getIdForRef(page.revision)} />
 
-      {/* Page list */}
-      <div className={`grw-page-accessories-control ${styles['grw-page-accessories-control']}`}>
+      <div className={`${styles['grw-page-accessories-controls']} d-flex flex-column gap-2 d-print-none`}>
+        {/* Page list */}
         {!isSharedUser && (
-          <button
-            type="button"
-            className="btn btn-outline-secondary grw-btn-page-accessories rounded-pill d-flex justify-content-between align-items-center"
-            onClick={() => openDescendantPageListModal(pagePath)}
-            data-testid="pageListButton"
-          >
-            <div className="grw-page-accessories-control-icon">
-              <PageListIcon />
-            </div>
-            {t('page_list')}
-
-            {/* Do not display CountBadge if '/trash/*': https://github.com/weseek/growi/pull/7600 */}
-            { !isTrash && pageInfo != null
-              ? <CountBadge count={(pageInfo as IPageInfoForOperation).descendantCount} offset={1} />
-              : <div className="px-2"></div>}
-          </button>
+          <div className="d-flex" data-testid="pageListButton">
+            <PageAccessoriesControl
+              icon={<span className="material-symbols-outlined">subject</span>}
+              label={t('page_list')}
+              // Do not display CountBadge if '/trash/*': https://github.com/weseek/growi/pull/7600
+              count={!isTrash && pageInfo != null ? (pageInfo as IPageInfoForOperation).descendantCount : undefined}
+              onClick={() => openDescendantPageListModal(pagePath)}
+            />
+          </div>
+        )}
+
+        {/* Comments */}
+        {!isTopPagePath && (
+          <div className="d-flex" data-testid="page-comment-button">
+            <PageAccessoriesControl
+              icon={<span className="material-symbols-outlined">chat</span>}
+              label="Comments"
+              count={pageInfo != null ? (pageInfo as IPageInfoForOperation).commentCount : undefined}
+              onClick={() => scroller.scrollTo('comments-container', { smooth: false, offset: -120 })}
+            />
+          </div>
         )}
       </div>
 
-      {/* Comments */}
-      {!isTopPagePath && (
-        <div className={`mt-2 grw-page-accessories-control ${styles['grw-page-accessories-control']}`}>
-          <Link to="page-comments" offset={-120}>
-            <button
-              type="button"
-              className="btn btn-outline-secondary grw-btn-page-accessories rounded-pill d-flex justify-content-between align-items-center"
-              data-testid="page-comment-button"
-            >
-              <i className="icon-fw icon-bubbles grw-page-accessories-control-icon"></i>
-              <span>Comments</span>
-              { pageInfo != null
-                ? <CountBadge count={(pageInfo as IPageInfoForOperation).commentCount} />
-                : <div className="px-2"></div>}
-            </button>
-          </Link>
-        </div>
-      )}
-
-      <div className="d-none d-lg-block">
+      <div className="d-none d-xl-block">
         <TableOfContents />
         {isUsersHomepagePath && <ContentLinkButtons author={page?.creator} />}
       </div>

+ 3 - 1
apps/app/src/components/PageTags/PageTags.tsx

@@ -34,9 +34,11 @@ export const PageTags:FC<Props> = (props: Props) => {
     return <PageTagsSkeleton />;
   }
 
+  const printNoneClass = tags.length === 0 ? 'd-print-none' : '';
+
   return (
     <>
-      <div className={`${styles['grw-tag-labels']} grw-tag-labels d-flex align-items-center`} data-testid="grw-tag-labels">
+      <div className={`${styles['grw-tag-labels']} grw-tag-labels d-flex align-items-center ${printNoneClass}`} data-testid="grw-tag-labels">
         <RenderTagLabels
           tags={tags}
           openEditorModal={openEditorModal}

+ 1 - 1
apps/app/src/components/PageTags/RenderTagLabels.tsx

@@ -44,7 +44,7 @@ const RenderTagLabels = React.memo((props: RenderTagLabelsProps) => {
       })}
       <NotAvailableForGuest>
         <NotAvailableForReadOnlyUser>
-          <div id="edit-tags-btn-wrapper-for-tooltip">
+          <div id="edit-tags-btn-wrapper-for-tooltip" className="d-print-none">
             <a
               className={`btn btn-link btn-edit-tags text-muted p-0 d-flex align-items-center ${isTagsEmpty && 'no-tags'} ${isTagLabelsDisabled && 'disabled'}`}
               onClick={openEditorHandler}

+ 1 - 1
apps/app/src/components/SavePageControls.tsx

@@ -70,7 +70,7 @@ export const SavePageControls = (props: SavePageControlsProps): JSX.Element | nu
   const { grant, grantedGroup } = grantData;
 
   const isGrantSelectorDisabledPage = isTopPage(currentPage?.path ?? '') || isUsersProtectedPages(currentPage?.path ?? '');
-  const labelSubmitButton = (currentPage != null && !currentPage.isEmpty) ? t('Update') : t('Create');
+  const labelSubmitButton = t('Update');
   const labelOverwriteScopes = t('page_edit.overwrite_scopes', { operation: labelSubmitButton });
 
   return (

+ 1 - 1
apps/app/src/components/SearchPage/SearchPageBase.module.scss

@@ -1 +1 @@
-@use '@growi/ui/src/styles/molecules/page_list';
+@use '@growi/ui/scss/molecules/page_list';

+ 1 - 2
apps/app/src/components/SearchTypeahead.tsx

@@ -3,8 +3,7 @@ import React, {
   KeyboardEvent, useCallback, useRef, useState, MouseEvent, useEffect,
 } from 'react';
 
-import { UserPicture } from '@growi/ui/dist/components';
-import { PageListMeta, PagePathLabel } from '@growi/ui/dist/components/PagePath';
+import { UserPicture, PageListMeta, PagePathLabel } from '@growi/ui/dist/components';
 import { AsyncTypeahead, Menu, MenuItem } from 'react-bootstrap-typeahead';
 
 import { IFocusable } from '~/client/interfaces/focusable';

+ 20 - 3
apps/app/src/components/Sidebar/AppTitle/AppTitle.module.scss

@@ -27,11 +27,28 @@
 
 // == Location
 .on-subnavigation {
-  $grw-contextual-sub-navigation-width: 500px;
+  top: 0;
 
-  left: var.$grw-sidebar-nav-width;
+  @include bs.media-breakpoint-up(md) {
+    left: var.$grw-sidebar-nav-width;
+  }
+}
+
+
+// == App title truncation
+.on-subnavigation {
   // set width for truncation
-  width: calc(100vw - $grw-contextual-sub-navigation-width);
+  $grw-page-controls-width: 226px;
+  $grw-page-editor-mode-manager-width: 90px;
+  $grw-contextual-subnavigation-padding-right: 8px;
+  $gap: 8px;
+  width: calc(100vw - #{$grw-page-controls-width + $grw-page-editor-mode-manager-width + $grw-contextual-subnavigation-padding-right + $gap * 2});
+
+  @include bs.media-breakpoint-up(md) {
+    $grw-page-editor-mode-manager-width: 140px;
+    $gap: 24px;
+    width: calc(100vw - #{var.$grw-sidebar-nav-width + $grw-page-controls-width + $grw-page-editor-mode-manager-width + $grw-contextual-subnavigation-padding-right + $gap * 2});
+  }
 }
 
 .on-sidebar-head {

+ 10 - 3
apps/app/src/components/Sidebar/Sidebar.tsx

@@ -15,6 +15,8 @@ import {
   useSidebarMode,
 } from '~/stores/ui';
 
+import { DrawerToggler } from '../Common/DrawerToggler';
+
 import { AppTitleOnSidebarHead, AppTitleOnSubnavigation } from './AppTitle/AppTitle';
 import { ResizableArea } from './ResizableArea/ResizableArea';
 import { SidebarHead } from './SidebarHead';
@@ -168,7 +170,7 @@ const DrawableContainer = memo((props: DrawableContainerProps): JSX.Element => {
 
 export const Sidebar = (): JSX.Element => {
 
-  const { data: sidebarMode, isCollapsedMode } = useSidebarMode();
+  const { data: sidebarMode, isDrawerMode, isDockMode } = useSidebarMode();
 
   // css styles
   const grwSidebarClass = styles['grw-sidebar'];
@@ -188,10 +190,15 @@ export const Sidebar = (): JSX.Element => {
 
   return (
     <>
-      { sidebarMode != null && isCollapsedMode() && <AppTitleOnSubnavigation /> }
+      { sidebarMode != null && isDrawerMode() && (
+        <DrawerToggler className="position-fixed d-none d-md-block">
+          <span className="material-symbols-outlined">reorder</span>
+        </DrawerToggler>
+      ) }
+      { sidebarMode != null && !isDockMode() && <AppTitleOnSubnavigation /> }
       <DrawableContainer className={`${grwSidebarClass} ${modeClass} border-end vh-100`} data-testid="grw-sidebar">
         <ResizableContainer>
-          { sidebarMode != null && !isCollapsedMode() && <AppTitleOnSidebarHead /> }
+          { sidebarMode != null && isDockMode() && <AppTitleOnSidebarHead /> }
           <SidebarHead />
           <CollapsibleContainer Nav={SidebarNav} className="border-top">
             <SidebarContents />

+ 10 - 2
apps/app/src/components/Sidebar/SidebarHead/ToggleCollapseButton.module.scss

@@ -28,6 +28,14 @@
   &:global {
     &.btn.btn-primary {
       @extend %btn-primary-color-vars;
+
+      --bs-btn-hover-color: color-mix(in srgb, var(
+        --grw-sidebar-nav-btn-hover-color,
+        var(
+          --grw-sidebar-nav-btn-color,
+          var(--bs-btn-color)
+        )) 90%,
+        transparent);
     }
   }
 }
@@ -35,7 +43,7 @@
   .btn-toggle-collapse {
     &:global {
       &.btn.btn-primary {
-        --bs-btn-color: var(--grw-sidebar-nav-btn-color, var(--bs-gray-500));
+        --bs-btn-color: color-mix(in srgb, var(--grw-sidebar-nav-btn-color, var(--bs-gray-500)) 50%, transparent);
         --bs-btn-hover-bg: var(--grw-sidebar-nav-btn-hover-bg, var(--grw-highlight-300));
       }
     }
@@ -45,7 +53,7 @@
   .btn-toggle-collapse {
     &:global {
       &.btn.btn-primary {
-        --bs-btn-color: var(--grw-sidebar-nav-btn-color, var(--bs-gray-600));
+        --bs-btn-color: color-mix(in srgb, var(--grw-sidebar-nav-btn-color, var(--bs-gray-600)) 50%, transparent);
         --bs-btn-hover-bg: var(--grw-sidebar-nav-btn-hover-bg, var(--grw-highlight-700));
       }
     }

+ 8 - 5
apps/app/src/components/Sidebar/SidebarHead/ToggleCollapseButton.tsx

@@ -1,4 +1,4 @@
-import { memo, useCallback } from 'react';
+import { memo, useCallback, useMemo } from 'react';
 
 import {
   useCollapsedContentsOpened, usePreferCollapsedMode, useDrawerOpened, useSidebarMode,
@@ -10,7 +10,7 @@ import styles from './ToggleCollapseButton.module.scss';
 
 export const ToggleCollapseButton = memo((): JSX.Element => {
 
-  const { isDrawerMode, isCollapsedMode, isDockMode } = useSidebarMode();
+  const { isDrawerMode, isCollapsedMode } = useSidebarMode();
   const { data: isDrawerOpened, mutate: mutateDrawerOpened } = useDrawerOpened();
   const { mutate: mutatePreferCollapsedMode } = usePreferCollapsedMode();
   const { mutate: mutateCollapsedContentsOpened } = useCollapsedContentsOpened();
@@ -25,9 +25,12 @@ export const ToggleCollapseButton = memo((): JSX.Element => {
   }, [isCollapsedMode, mutateCollapsedContentsOpened, mutatePreferCollapsedMode]);
 
   const rotationClass = isCollapsedMode() ? 'rotate180' : '';
-  const icon = isDrawerMode() || isDockMode()
-    ? 'first_page'
-    : 'keyboard_double_arrow_left';
+  const icon = useMemo(() => {
+    if (isCollapsedMode()) {
+      return 'keyboard_double_arrow_left';
+    }
+    return 'first_page';
+  }, [isCollapsedMode]);
 
   return (
     <button

+ 1 - 1
apps/app/src/components/UsersHomepageFooter.module.scss

@@ -1,4 +1,4 @@
-@use '@growi/ui/src/styles/molecules/page_list';
+@use '@growi/ui/scss/molecules/page_list';
 $grw-sidebar-content-header-height: 58px;
 $grw-sidebar-content-footer-height: 50px;
 

+ 14 - 52
apps/app/src/server/routes/attachment.js

@@ -454,10 +454,8 @@ module.exports = function(crowi, app) {
    * @apiParam {File} file
    */
   api.add = async function(req, res) {
-    let pageId = req.body.page_id || null;
+    const pageId = req.body.page_id || null;
     const pagePath = req.body.path || null;
-    const pageBody = req.body.page_body || null;
-    let pageCreated = false;
 
     // check params
     if (pageId == null && pagePath == null) {
@@ -469,67 +467,31 @@ module.exports = function(crowi, app) {
 
     const file = req.file;
 
-    let page;
-    if (pageId == null) {
-      logger.debug('Create page before file upload');
-
-      if (!isCreatablePage(pagePath)) {
-        return res.json(ApiResponse.error(`Could not use the path '${pagePath}'`));
-      }
-
-      if (isUserPage(pagePath)) {
-        const isExistUser = await User.isExistUserByUserPagePath(pagePath);
-        if (!isExistUser) {
-          return res.json(ApiResponse.error("Unable to create a page under a non-existent user's user page"));
-        }
-      }
-
-      const isAclEnabled = crowi.aclService.isAclEnabled();
-      const grant = isAclEnabled ? Page.GRANT_OWNER : Page.GRANT_PUBLIC;
-
-      page = await crowi.pageService.create(pagePath, pageBody ?? '', req.user, { grant });
-      pageCreated = true;
-      pageId = page._id;
-    }
-    else {
-      page = await Page.findById(pageId);
+    try {
+      const page = await Page.findById(pageId);
 
       // check the user is accessible
       const isAccessible = await Page.isAccessiblePageByViewer(page.id, req.user);
       if (!isAccessible) {
         return res.json(ApiResponse.error(`Forbidden to access to the page '${page.id}'`));
       }
-    }
 
-    let attachment;
-    try {
-      attachment = await attachmentService.createAttachment(file, req.user, pageId, AttachmentType.WIKI_PAGE);
+      const attachment = await attachmentService.createAttachment(file, req.user, pageId, AttachmentType.WIKI_PAGE);
+
+      const result = {
+        page: serializePageSecurely(page),
+        revision: serializeRevisionSecurely(page.revision),
+        attachment: attachment.toObject({ virtuals: true }),
+      };
+
+      activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_ATTACHMENT_ADD });
+
+      res.json(ApiResponse.success(result));
     }
     catch (err) {
       logger.error(err);
       return res.json(ApiResponse.error(err.message));
     }
-
-    const result = {
-      page: serializePageSecurely(page),
-      revision: serializeRevisionSecurely(page.revision),
-      attachment: attachment.toObject({ virtuals: true }),
-      pageCreated,
-    };
-
-    activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_ATTACHMENT_ADD });
-
-    res.json(ApiResponse.success(result));
-
-    if (pageCreated) {
-      // global notification
-      try {
-        await globalNotificationService.fire(GlobalNotificationSetting.EVENT.PAGE_CREATE, page, req.user);
-      }
-      catch (err) {
-        logger.error('Create notification failed', err);
-      }
-    }
   };
 
   /**

+ 2 - 0
apps/app/src/stores/page-listing.tsx

@@ -204,6 +204,8 @@ export const useSWRxPageChildren = (
     }),
     {
       keepPreviousData: true,
+      revalidateOnFocus: false,
+      revalidateOnRecconect: false,
     },
   );
 };

+ 44 - 16
apps/app/src/stores/ui.tsx

@@ -166,8 +166,8 @@ export const useEditorMode = (): SWRResponseWithUtils<EditorModeUtils, EditorMod
   });
 };
 
-export const useIsDeviceSmallerThanMd = (): SWRResponse<boolean, Error> => {
-  const key: Key = isClient() ? 'isDeviceSmallerThanMd' : null;
+export const useIsDeviceLargerThanMd = (): SWRResponse<boolean, Error> => {
+  const key: Key = isClient() ? 'isDeviceLargerThanMd' : null;
 
   const { cache, mutate } = useSWRConfig();
 
@@ -176,13 +176,13 @@ export const useIsDeviceSmallerThanMd = (): SWRResponse<boolean, Error> => {
       const mdOrAvobeHandler = function(this: MediaQueryList): void {
         // sm -> md: matches will be true
         // md -> sm: matches will be false
-        mutate(key, !this.matches);
+        mutate(key, this.matches);
       };
       const mql = addBreakpointListener(Breakpoint.MD, mdOrAvobeHandler);
 
       // initialize
       if (cache.get(key)?.data == null) {
-        cache.set(key, { ...cache.get(key), data: !mql.matches });
+        cache.set(key, { ...cache.get(key), data: mql.matches });
       }
 
       return () => {
@@ -191,11 +191,11 @@ export const useIsDeviceSmallerThanMd = (): SWRResponse<boolean, Error> => {
     }
   }, [cache, key, mutate]);
 
-  return useStaticSWR(key);
+  return useSWRStatic(key);
 };
 
-export const useIsDeviceSmallerThanLg = (): SWRResponse<boolean, Error> => {
-  const key: Key = isClient() ? 'isDeviceSmallerThanLg' : null;
+export const useIsDeviceLargerThanLg = (): SWRResponse<boolean, Error> => {
+  const key: Key = isClient() ? 'isDeviceLargerThanLg' : null;
 
   const { cache, mutate } = useSWRConfig();
 
@@ -204,13 +204,13 @@ export const useIsDeviceSmallerThanLg = (): SWRResponse<boolean, Error> => {
       const lgOrAvobeHandler = function(this: MediaQueryList): void {
         // md -> lg: matches will be true
         // lg -> md: matches will be false
-        mutate(key, !this.matches);
+        mutate(key, this.matches);
       };
       const mql = addBreakpointListener(Breakpoint.LG, lgOrAvobeHandler);
 
       // initialize
       if (cache.get(key)?.data == null) {
-        cache.set(key, { ...cache.get(key), data: !mql.matches });
+        cache.set(key, { ...cache.get(key), data: mql.matches });
       }
 
       return () => {
@@ -219,7 +219,35 @@ export const useIsDeviceSmallerThanLg = (): SWRResponse<boolean, Error> => {
     }
   }, [cache, key, mutate]);
 
-  return useStaticSWR(key);
+  return useSWRStatic(key);
+};
+
+export const useIsDeviceLargerThanXl = (): SWRResponse<boolean, Error> => {
+  const key: Key = isClient() ? 'isDeviceLargerThanXl' : null;
+
+  const { cache, mutate } = useSWRConfig();
+
+  useEffect(() => {
+    if (key != null) {
+      const xlOrAvobeHandler = function(this: MediaQueryList): void {
+        // lg -> xl: matches will be true
+        // xl -> lg: matches will be false
+        mutate(key, this.matches);
+      };
+      const mql = addBreakpointListener(Breakpoint.XL, xlOrAvobeHandler);
+
+      // initialize
+      if (cache.get(key)?.data == null) {
+        cache.set(key, { ...cache.get(key), data: mql.matches });
+      }
+
+      return () => {
+        cleanupBreakpointListener(mql, xlOrAvobeHandler);
+      };
+    }
+  }, [cache, key, mutate]);
+
+  return useSWRStatic(key);
 };
 
 
@@ -250,28 +278,28 @@ type DetectSidebarModeUtils = {
 }
 
 export const useSidebarMode = (): SWRResponseWithUtils<DetectSidebarModeUtils, SidebarMode> => {
-  const { data: isDeviceSmallerThanMd } = useIsDeviceSmallerThanMd();
+  const { data: isDeviceLargerThanXl } = useIsDeviceLargerThanXl();
   const { data: editorMode } = useEditorMode();
   const { data: isCollapsedModeUnderDockMode } = usePreferCollapsedMode();
 
-  const condition = isDeviceSmallerThanMd != null && editorMode != null && isCollapsedModeUnderDockMode != null;
+  const condition = isDeviceLargerThanXl != null && editorMode != null && isCollapsedModeUnderDockMode != null;
 
   const isEditorMode = editorMode === EditorMode.Editor;
 
   const fetcher = useCallback((
-      [, isDeviceSmallerThanMd, isEditorMode, isCollapsedModeUnderDockMode]: [Key, boolean|undefined, boolean|undefined, boolean|undefined],
+      [, isDeviceLargerThanXl, isEditorMode, isCollapsedModeUnderDockMode]: [Key, boolean|undefined, boolean|undefined, boolean|undefined],
   ) => {
-    if (isDeviceSmallerThanMd) {
+    if (!isDeviceLargerThanXl) {
       return SidebarMode.DRAWER;
     }
     return isEditorMode || isCollapsedModeUnderDockMode ? SidebarMode.COLLAPSED : SidebarMode.DOCK;
   }, []);
 
   const swrResponse = useSWRImmutable(
-    condition ? ['sidebarMode', isDeviceSmallerThanMd, isEditorMode, isCollapsedModeUnderDockMode] : null,
+    condition ? ['sidebarMode', isDeviceLargerThanXl, isEditorMode, isCollapsedModeUnderDockMode] : null,
     // calcDrawerMode,
     fetcher,
-    { fallbackData: fetcher(['sidebarMode', isDeviceSmallerThanMd, isEditorMode, isCollapsedModeUnderDockMode]) },
+    { fallbackData: fetcher(['sidebarMode', isDeviceLargerThanXl, isEditorMode, isCollapsedModeUnderDockMode]) },
   );
 
   const _isDrawerMode = useCallback(() => swrResponse.data === SidebarMode.DRAWER, [swrResponse.data]);

+ 0 - 16
apps/app/src/styles/_layout.scss

@@ -63,22 +63,6 @@
   }
 }
 
-.grw-side-contents-container {
-  margin-bottom: 1rem;
-
-  @include bs.media-breakpoint-up(lg) {
-    width: 250px;
-    min-width: 250px;
-    margin-left: 30px;
-  }
-}
-
-.grw-side-contents-sticky-container {
-  position: sticky;
-  // growisubnavigation + grw-navbar-boder + some spacing
-  top: calc(100px + 4px + 20px);
-}
-
 // printable style
 @media print {
   body {

+ 1 - 1
apps/app/src/styles/_variables.scss

@@ -8,7 +8,7 @@ $grw-marker-green: #6f6;
 //== Layout
 $grw-sidebar-nav-width: 48px;
 
-$grw-navbar-bottom-height: 48px;
+$grw-navbar-bottom-height: 62px;
 $grw-editor-navbar-bottom-height: 48px;
 
 $grw-scroll-margin-top-in-view: 130px;

+ 0 - 16
apps/app/src/styles/molecules/_page-accessories-control.scss

@@ -1,16 +0,0 @@
-%grw-page-accessories-control {
-  .grw-btn-page-accessories {
-    padding-right: 1rem;
-    padding-left: 1rem;
-
-    svg {
-      width: 16px;
-      height: 16px;
-    }
-  }
-  .grw-page-accessories-control-icon {
-    display: flex;
-    justify-content: center;
-    width: 20px;
-  }
-}

+ 1 - 1
package.json

@@ -75,7 +75,7 @@
     "glob": "^8.1.0",
     "mock-require": "^3.0.3",
     "path-browserify": "^1.0.1",
-    "postcss": "^8.4.5",
+    "postcss": "^8.4.31",
     "postcss-scss": "^4.0.3",
     "reg-keygen-git-hash-plugin": "^0.11.1",
     "reg-notify-github-plugin": "^0.11.1",

+ 1 - 0
packages/editor/src/services/codemirror-editor/use-codemirror-editor/use-codemirror-editor.ts

@@ -43,6 +43,7 @@ export type UseCodeMirrorEditor = {
 
 
 const defaultExtensions: Extension[] = [
+  EditorView.lineWrapping,
   markdown({ base: markdownLanguage, codeLanguages: languages }),
   keymap.of([indentWithTab]),
   Prec.lowest(keymap.of(defaultKeymap)),

+ 75 - 1
packages/preset-themes/src/styles/default.scss

@@ -1,4 +1,78 @@
-@use '@growi/core/scss/bootstrap/init' as bs;
+:root[data-bs-theme='light'] {
+  @import '@growi/core/scss/bootstrap/init-stage-1';
+  @import '@growi/core/scss/bootstrap/theming/variables';
+  @import '@growi/core/scss/bootstrap/theming/utils/color-palette';
+
+  $primary: #007eb0;
+  $highlight: #c4c2bd;
+
+  @include generate-color-palette('primary', $primary, #000010, white, 22%, 22%);
+  @include generate-color-palette('highlight', $highlight);
+
+  $body-color:                #223246;
+  $body-bg:                   white;
+
+  $body-secondary-color:      rgba($body-color, .75);
+  $body-secondary-bg:         $gray-200;
+
+  $body-tertiary-color:       rgba($body-color, .5);
+  $body-tertiary-bg:          $gray-100;
+
+  $border-color:              var(--grw-highlight-200);
+
+  $link-color:                #434240;
+
+  @import 'bootstrap/scss/variables';
+  @import 'bootstrap/scss/variables-dark';
+
+  @import '@growi/core/scss/bootstrap/init-stage-2';
+
+  @import '@growi/core/scss/bootstrap/theming/root';
+  @import '@growi/core/scss/bootstrap/theming/root-light';
+  @import '@growi/core/scss/bootstrap/theming/apply';
+
+  --grw-wiki-link-color-rgb: var(--grw-highlight-800-rgb);
+  --grw-wiki-link-hover-color-rgb: var(--grw-highlight-900-rgb);
+  --grw-sidebar-nav-btn-color: var(--grw-highlight-600);
+}
+
+:root[data-bs-theme='dark'] {
+  @import '@growi/core/scss/bootstrap/init-stage-1';
+  @import '@growi/core/scss/bootstrap/theming/variables';
+  @import '@growi/core/scss/bootstrap/theming/utils/color-palette';
+
+  $primary: #007eb0;
+  $highlight: #c4c2bd;
+
+  @include generate-color-palette('primary', $primary, #000010, white, 22%, 22%);
+  @include generate-color-palette('highlight', $highlight, black, white);
+
+  $body-color-dark:                   $gray-300;
+  $body-bg-dark:                      #1c1a1a;
+
+  $body-secondary-color-dark:         rgba($body-color-dark, .75);
+  $body-secondary-bg-dark:            $gray-800;
+
+  $body-tertiary-color-dark:          rgba($body-color-dark, .5);
+  $body-tertiary-bg-dark:             mix($gray-800, $gray-900, 50%);
+
+  $border-color-dark:                 var(--grw-highlight-200);
+
+  $link-color-dark:                   $gray-500;
+
+  @import 'bootstrap/scss/variables';
+  @import 'bootstrap/scss/variables-dark';
+
+  @import '@growi/core/scss/bootstrap/init-stage-2';
+
+  @import '@growi/core/scss/bootstrap/theming/root';
+  @import '@growi/core/scss/bootstrap/theming/root-dark';
+  @import '@growi/core/scss/bootstrap/theming/apply';
+
+  --grw-wiki-link-color-rgb: var(--grw-highlight-500-rgb);
+  --grw-wiki-link-hover-color-rgb: var(--grw-highlight-300-rgb);
+  --grw-sidebar-nav-btn-color: rgba(var(--grw-highlight-400-rgb), 0.8);
+}
 
 // @use './variables' as var;
 // @use './theme/mixins/page-editor-mode-manager';

+ 1 - 1
packages/remark-lsx/src/client/components/LsxPageList/LsxListView.module.scss

@@ -1,4 +1,4 @@
-@use '@growi/ui/src/styles/molecules/page_list';
+@use '@growi/ui/scss/molecules/page_list';
 
 .page-list :global {
   .page-list-ul > li > a:not(:hover) {

+ 12 - 3
packages/remark-lsx/src/client/components/LsxPageList/LsxPage.tsx

@@ -1,7 +1,7 @@
 import React, { useMemo } from 'react';
 
 import { pathUtils } from '@growi/core/dist/utils';
-import { PageListMeta, PagePathLabel } from '@growi/ui/dist/components/PagePath';
+import { PageListMeta, PagePathLabel } from '@growi/ui/dist/components';
 import Link from 'next/link';
 
 import type { PageNode } from '../../../interfaces/page-node';
@@ -97,8 +97,17 @@ export const LsxPage = React.memo((props: Props): JSX.Element => {
     if (pageNode.page == null) {
       return <></>;
     }
-    return <PageListMeta page={pageNode.page} basisViewersCount={basisViewersCount} />;
-  }, [basisViewersCount, pageNode.page]);
+
+    const { page } = pageNode;
+
+    return (
+      <PageListMeta
+        page={page}
+        basisViewersCount={basisViewersCount}
+        likerCount={page.liker.length}
+      />
+    );
+  }, [basisViewersCount, pageNode]);
 
   return (
     <li className={`page-list-li ${styles['page-list-li']}`}>

+ 14 - 1
packages/ui/package.json

@@ -8,8 +8,21 @@
   ],
   "type": "module",
   "files": [
-    "dist"
+    "dist",
+    "scss"
   ],
+  "exports": {
+    "./dist/components": {
+      "import": "./dist/components/index.js"
+    },
+    "./dist/interfaces": {
+      "import": "./dist/interfaces/index.js"
+    },
+    "./dist/utils": {
+      "import": "./dist/utils/index.js"
+    },
+    "./scss/*": "./scss/*.scss"
+  },
   "scripts": {
     "build": "vite build",
     "clean": "shx rm -rf dist",

+ 18 - 0
packages/ui/scss/atoms/_btn-muted.scss

@@ -0,0 +1,18 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+
+@mixin colorize($color-active, $color: var(--bs-tertiary-color)) {
+  $color-active-rgb: #{bs.to-rgb($color-active)};
+
+  --bs-btn-color: #{$color};
+  --bs-btn-bg: transparent;
+
+  --bs-btn-hover-color: #{$color-active};
+  --bs-btn-hover-bg: rgba(#{$color-active-rgb}, 0.2);
+
+  --bs-btn-active-color: #{$color-active};
+  --bs-btn-active-bg: transparent;
+
+  &:hover {
+    --bs-btn-active-bg: rgba(#{$color-active-rgb}, 0.2);
+  }
+}

+ 0 - 0
packages/ui/src/styles/molecules/_page_list.scss → packages/ui/scss/molecules/_page_list.scss


+ 1 - 0
packages/ui/src/components/index.ts

@@ -1,3 +1,4 @@
 export * from './Attachment';
 export * from './FootstampIcon';
+export * from './PagePath';
 export * from './UserPicture';

+ 10 - 1
yarn.lock

@@ -13179,7 +13179,7 @@ postcss@^7.0.0:
     picocolors "^0.2.1"
     source-map "^0.6.1"
 
-postcss@^8.3.11, postcss@^8.4.19, postcss@^8.4.21, postcss@^8.4.25, postcss@^8.4.5:
+postcss@^8.3.11, postcss@^8.4.19, postcss@^8.4.21, postcss@^8.4.25:
   version "8.4.26"
   resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.26.tgz#1bc62ab19f8e1e5463d98cf74af39702a00a9e94"
   integrity sha512-jrXHFF8iTloAenySjM/ob3gSj7pCu0Ji49hnjqzsgSRa50hkWCKD0HQ+gMNJkW38jBI68MpAAg7ZWwHwX8NMMw==
@@ -13188,6 +13188,15 @@ postcss@^8.3.11, postcss@^8.4.19, postcss@^8.4.21, postcss@^8.4.25, postcss@^8.4
     picocolors "^1.0.0"
     source-map-js "^1.0.2"
 
+postcss@^8.4.31:
+  version "8.4.31"
+  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d"
+  integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==
+  dependencies:
+    nanoid "^3.3.6"
+    picocolors "^1.0.0"
+    source-map-js "^1.0.2"
+
 precond@0.2:
   version "0.2.3"
   resolved "https://registry.yarnpkg.com/precond/-/precond-0.2.3.tgz#aa9591bcaa24923f1e0f4849d240f47efc1075ac"