Kaynağa Gözat

Merge branch 'dev/7.0.x' into feat/yjs-editor

ryoji-s 2 yıl önce
ebeveyn
işleme
aae418b9f0
65 değiştirilmiş dosya ile 583 ekleme ve 373 silme
  1. 1 1
      apps/app/_obsolete/src/components/Sidebar/AppearanceModeDropdown.tsx
  2. 1 1
      apps/app/package.json
  3. 1 1
      apps/app/src/client/services/page-operation.ts
  4. 3 4
      apps/app/src/components/Admin/Customize/CustomizePresentationSetting.tsx
  5. 1 1
      apps/app/src/components/Bookmarks/BookmarkFolderItem.tsx
  6. 4 21
      apps/app/src/components/Common/Dropdown/PageItemControl.tsx
  7. 9 0
      apps/app/src/components/Navbar/GrowiContextualSubNavigation.module.scss
  8. 9 5
      apps/app/src/components/Navbar/GrowiContextualSubNavigation.tsx
  9. 7 4
      apps/app/src/components/Navbar/PageEditorModeManager.module.scss
  10. 32 24
      apps/app/src/components/Navbar/PageEditorModeManager.tsx
  11. 58 0
      apps/app/src/components/Navbar/hooks.tsx
  12. 0 3
      apps/app/src/components/Page/DisplaySwitcher.tsx
  13. 17 10
      apps/app/src/components/PageControls/BookmarkButtons.module.scss
  14. 5 3
      apps/app/src/components/PageControls/BookmarkButtons.tsx
  15. 16 10
      apps/app/src/components/PageControls/LikeButtons.module.scss
  16. 3 3
      apps/app/src/components/PageControls/LikeButtons.tsx
  17. 11 23
      apps/app/src/components/PageControls/PageControls.module.scss
  18. 16 11
      apps/app/src/components/PageControls/SeenUserInfo.module.scss
  19. 2 4
      apps/app/src/components/PageControls/SeenUserInfo.tsx
  20. 12 10
      apps/app/src/components/PageControls/SubscribeButton.module.scss
  21. 3 1
      apps/app/src/components/PageControls/SubscribeButton.tsx
  22. 34 0
      apps/app/src/components/PageControls/_button-styles.scss
  23. 42 0
      apps/app/src/components/PageEditor/EditorNavbarBottom.module.scss
  24. 8 4
      apps/app/src/components/PageEditor/EditorNavbarBottom.tsx
  25. 51 44
      apps/app/src/components/PageEditor/PageEditor.tsx
  26. 30 0
      apps/app/src/components/PageEditor/Preview.module.scss
  27. 6 1
      apps/app/src/components/PageEditor/Preview.tsx
  28. 2 1
      apps/app/src/components/Sidebar/AppTitle/AppTitle.module.scss
  29. 44 9
      apps/app/src/components/Sidebar/PageCreateButton.tsx
  30. 1 2
      apps/app/src/components/Sidebar/Sidebar.module.scss
  31. 3 2
      apps/app/src/components/Sidebar/SidebarHead/ToggleCollapseButton.module.scss
  32. 1 1
      apps/app/src/components/Sidebar/SidebarHead/ToggleCollapseButton.tsx
  33. 3 5
      apps/app/src/components/Sidebar/SidebarNav/PersonalDropdown.tsx
  34. 1 1
      apps/app/src/components/Sidebar/SidebarNav/PrimaryItems.module.scss
  35. 2 2
      apps/app/src/components/Sidebar/SidebarNav/PrimaryItems.tsx
  36. 1 1
      apps/app/src/components/Sidebar/SidebarNav/SecondaryItems.module.scss
  37. 7 2
      apps/app/src/components/Sidebar/SidebarNav/SecondaryItems.tsx
  38. 1 1
      apps/app/src/components/Sidebar/SidebarNav/SidebarNav.module.scss
  39. 12 0
      apps/app/src/components/Sidebar/SidebarNav/SkeletonItem.module.scss
  40. 10 0
      apps/app/src/components/Sidebar/SidebarNav/SkeletonItem.tsx
  41. 1 1
      apps/app/src/components/Sidebar/_button-styles.scss
  42. 0 1
      apps/app/src/components/Sidebar/_variables.scss
  43. 1 1
      apps/app/src/components/TreeItem/SimpleItem.tsx
  44. 2 2
      apps/app/src/features/questionnaire/client/components/QuestionnaireModal.tsx
  45. 1 0
      apps/app/src/interfaces/page-operation.ts
  46. 31 3
      apps/app/src/server/routes/apiv3/pages.js
  47. 12 35
      apps/app/src/stores/ui.tsx
  48. 4 73
      apps/app/src/styles/_editor.scss
  49. 1 0
      apps/app/src/styles/_mixins.scss
  50. 2 0
      apps/app/src/styles/_variables.scss
  51. 8 3
      apps/app/src/styles/font-icons.scss
  52. 14 0
      apps/app/src/styles/mixins/_editing.scss
  53. 1 1
      packages/editor/index.html
  54. 1 1
      packages/editor/package.json
  55. 2 2
      packages/editor/src/components/CodeMirrorEditor/Toolbar/AttachmentsButton.tsx
  56. 3 3
      packages/editor/src/components/CodeMirrorEditor/Toolbar/AttachmentsDropup.tsx
  57. 1 1
      packages/editor/src/components/CodeMirrorEditor/Toolbar/DiagramButton.tsx
  58. 1 1
      packages/editor/src/components/CodeMirrorEditor/Toolbar/EmojiButton.tsx
  59. 1 1
      packages/editor/src/components/CodeMirrorEditor/Toolbar/TableButton.tsx
  60. 1 1
      packages/editor/src/components/CodeMirrorEditor/Toolbar/TemplateButton.tsx
  61. 10 10
      packages/editor/src/components/CodeMirrorEditor/Toolbar/TextFormatTools.tsx
  62. 5 6
      packages/editor/src/components/CodeMirrorEditor/Toolbar/scss/toolbar-button.scss
  63. 2 2
      packages/editor/src/components/playground/Playground.tsx
  64. 3 4
      packages/editor/src/main.scss
  65. 5 5
      yarn.lock

+ 1 - 1
apps/app/_obsolete/src/components/Sidebar/AppearanceModeDropdown.tsx

@@ -106,7 +106,7 @@ export const AppearanceModeDropdown:FC<AppearanceModeDropdownProps> = (props: Ap
       {/* remove .dropdown-toggle for hide caret */}
       {/* See https://stackoverflow.com/a/44577512/13183572 */}
       <button className="btn btn-primary" type="button" data-bs-toggle="dropdown" ref={buttonRef} aria-haspopup="true">
-        <i className="material-icons">settings</i>
+        <span className="material-symbols-outlined">settings</span>
       </button>
 
       {/* dropdown */}

+ 1 - 1
apps/app/package.json

@@ -212,6 +212,7 @@
     "@growi/ui": "link:../../packages/ui",
     "@handsontable/react": "=2.1.0",
     "@icon/themify-icons": "1.0.1-alpha.3",
+    "@material-symbols/font-300": "^0.13.1",
     "@next/bundle-analyzer": "^13.2.3",
     "@swc-node/jest": "^1.6.2",
     "@swc/jest": "^0.2.24",
@@ -236,7 +237,6 @@
     "jest-date-mock": "^1.0.8",
     "jest-localstorage-mock": "^2.4.14",
     "load-css-file": "^1.0.0",
-    "material-icons": "^1.13.10",
     "mongodb-memory-server": "^8.12.2",
     "morgan": "^1.10.0",
     "null-loader": "^4.0.1",

+ 1 - 1
apps/app/src/client/services/page-operation.ts

@@ -88,7 +88,7 @@ export const resumeRenameOperation = async(pageId: string): Promise<void> => {
 };
 
 // TODO: define return type
-const createPage = async(pagePath: string, markdown: string, tmpParams: OptionsToSave) => {
+export const createPage = async(pagePath: string, markdown: string, tmpParams: OptionsToSave) => {
   // clone
   const params = Object.assign(tmpParams, {
     path: pagePath,

+ 3 - 4
apps/app/src/components/Admin/Customize/CustomizePresentationSetting.tsx

@@ -16,10 +16,8 @@ type Props = {
 
 const CustomizePresentationSetting = (props: Props): JSX.Element => {
   const { adminCustomizeContainer } = props;
-
-  console.log(adminCustomizeContainer);
-
   const { t } = useTranslation();
+
   const onClickSubmit = useCallback(async() => {
     try {
       await adminCustomizeContainer.updateCustomizePresentation();
@@ -28,7 +26,8 @@ const CustomizePresentationSetting = (props: Props): JSX.Element => {
     catch (err) {
       toastError(err);
     }
-  }, [adminCustomizeContainer]);
+  }, [adminCustomizeContainer, t]);
+
   return (
     <React.Fragment>
       <h2 className="admin-setting-header">{t('admin:customize_settings.custom_presentation')}</h2>

+ 1 - 1
apps/app/src/components/Bookmarks/BookmarkFolderItem.tsx

@@ -233,7 +233,7 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
                 onClick={loadChildFolder}
               >
                 <div className="d-flex justify-content-center">
-                  <span className="material-icons-round">arrow_right</span>
+                  <span className="material-symbols-rounded">arrow_right</span>
                 </div>
               </button>
             )}

+ 4 - 21
apps/app/src/components/Common/Dropdown/PageItemControl.tsx

@@ -266,7 +266,6 @@ PageItemControlDropdownMenu.displayName = 'PageItemControl';
 
 type PageItemControlSubstanceProps = CommonProps & {
   pageId: string,
-  fetchOnInit?: boolean,
   children?: React.ReactNode,
   operationProcessData?: IPageOperationProcessData,
 }
@@ -274,12 +273,12 @@ type PageItemControlSubstanceProps = CommonProps & {
 export const PageItemControlSubstance = (props: PageItemControlSubstanceProps): JSX.Element => {
 
   const {
-    pageId, pageInfo: presetPageInfo, fetchOnInit, children, onClickBookmarkMenuItem, onClickRenameMenuItem,
+    pageId, pageInfo: presetPageInfo, children, onClickBookmarkMenuItem, onClickRenameMenuItem,
     onClickDuplicateMenuItem, onClickDeleteMenuItem, onClickPathRecoveryMenuItem,
   } = props;
 
   const [isOpen, setIsOpen] = useState(false);
-  const [shouldFetch, setShouldFetch] = useState(fetchOnInit ?? false);
+  const [shouldFetch, setShouldFetch] = useState(false);
 
   const { data: fetchedPageInfo, mutate: mutatePageInfo } = useSWRxPageInfo(shouldFetch ? pageId : null);
 
@@ -336,10 +335,10 @@ export const PageItemControlSubstance = (props: PageItemControlSubstanceProps):
 
   return (
     <NotAvailableForGuest>
-      <Dropdown isOpen={isOpen} toggle={() => setIsOpen(!isOpen)} data-testid="open-page-item-control-btn">
+      <Dropdown isOpen={isOpen} toggle={() => setIsOpen(!isOpen)} className="grw-page-item-control" data-testid="open-page-item-control-btn">
         { children ?? (
           <DropdownToggle color="transparent" className="border-0 rounded btn-page-item-control d-flex align-items-center justify-content-center">
-            <i className="icon-options"></i>
+            <span className="material-symbols-outlined">more_vert</span>
           </DropdownToggle>
         ) }
 
@@ -377,19 +376,3 @@ export const PageItemControl = (props: PageItemControlProps): JSX.Element => {
 
   return <PageItemControlSubstance pageId={pageId} {...props} />;
 };
-
-
-type AsyncPageItemControlProps = Omit<CommonProps, 'pageInfo'> & {
-  pageId?: string,
-  children?: React.ReactNode,
-}
-
-export const AsyncPageItemControl = (props: AsyncPageItemControlProps): JSX.Element => {
-  const { pageId } = props;
-
-  if (pageId == null) {
-    return <></>;
-  }
-
-  return <PageItemControlSubstance pageId={pageId} fetchOnInit {...props} />;
-};

+ 9 - 0
apps/app/src/components/Navbar/GrowiContextualSubNavigation.module.scss

@@ -1,4 +1,13 @@
+@use '~/styles/mixins';
+
 .grw-contextual-sub-navigation :global {
   background-color: rgba(var(--bs-body-bg-rgb), 0.7);
   backdrop-filter: blur(35px);
 }
+
+@include mixins.editing() {
+  .grw-contextual-sub-navigation {
+    position: fixed;
+    right: 0;
+  }
+}

+ 9 - 5
apps/app/src/components/Navbar/GrowiContextualSubNavigation.tsx

@@ -28,6 +28,7 @@ import { mutatePageTree } from '~/stores/page-listing';
 import {
   useEditorMode, useIsAbleToShowPageManagement,
   useIsAbleToChangeEditorMode,
+  useSelectedGrant,
 } from '~/stores/ui';
 
 import CreateTemplateModal from '../CreateTemplateModal';
@@ -41,7 +42,6 @@ import { Skeleton } from '../Skeleton';
 import styles from './GrowiContextualSubNavigation.module.scss';
 import PageEditorModeManagerStyles from './PageEditorModeManager.module.scss';
 
-
 const PageEditorModeManager = dynamic(
   () => import('./PageEditorModeManager').then(mod => mod.PageEditorModeManager),
   { ssr: false, loading: () => <Skeleton additionalClass={`${PageEditorModeManagerStyles['grw-page-editor-mode-manager-skeleton']}`} /> },
@@ -190,13 +190,14 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
   const revision = currentPage?.revision;
   const revisionId = (revision != null && isPopulated(revision)) ? revision._id : undefined;
 
-  const { data: editorMode, mutate: mutateEditorMode } = useEditorMode();
+  const { data: editorMode } = useEditorMode();
   const { data: pageId } = useCurrentPageId();
   const { data: currentUser } = useCurrentUser();
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isReadOnlyUser } = useIsReadOnlyUser();
   const { data: isSharedUser } = useIsSharedUser();
   const { data: isContainerFluid } = useIsContainerFluid();
+  const { data: grantData } = useSelectedGrant();
 
   const { data: isAbleToShowPageManagement } = useIsAbleToShowPageManagement();
   const { data: isAbleToChangeEditorMode } = useIsAbleToChangeEditorMode();
@@ -213,6 +214,8 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
   const { mutate: mutatePageInfo } = useSWRxPageInfo(pageId);
 
   const path = currentPage?.path ?? currentPathname;
+  const grant = currentPage?.grant ?? grantData?.grant;
+  const grantUserGroupId = currentPage?.grantedGroup?._id ?? grantData?.grantedGroup?.id;
 
   // TODO: implement tags for editor
   // refs: https://redmine.weseek.co.jp/issues/132125
@@ -236,7 +239,6 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
 
   const { isLinkSharingDisabled } = props;
 
-
   // TODO: implement tags for editor
   // refs: https://redmine.weseek.co.jp/issues/132125
   // const tagsUpdatedHandlerForEditMode = useCallback((newTags: string[]): void => {
@@ -328,7 +330,7 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
   return (
     <>
       <div
-        className={`grw-contextual-sub-navigation ${styles['grw-contextual-sub-navigation']}
+        className={`${styles['grw-contextual-sub-navigation']}
           d-flex align-items-center justify-content-end px-2 py-1 gap-2 gap-md-4
         `}
         data-testid="grw-contextual-sub-nav"
@@ -355,7 +357,9 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
           <PageEditorModeManager
             editorMode={editorMode}
             isBtnDisabled={!!isGuestUser || !!isReadOnlyUser}
-            onPageEditorModeButtonClicked={viewType => mutateEditorMode(viewType)}
+            path={path}
+            grant={grant}
+            grantUserGroupId={grantUserGroupId}
           />
         )}
       </div>

+ 7 - 4
apps/app/src/components/Navbar/PageEditorModeManager.module.scss

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

+ 32 - 24
apps/app/src/components/Navbar/PageEditorModeManager.tsx

@@ -1,26 +1,27 @@
-import React, { type ReactNode, useCallback } from 'react';
+import React, { type ReactNode, useCallback, useState } from 'react';
 
 import { useTranslation } from 'next-i18next';
 
 import { EditorMode, useIsDeviceSmallerThanMd } from '~/stores/ui';
 
+import { useOnPageEditorModeButtonClicked } from './hooks';
+
 import styles from './PageEditorModeManager.module.scss';
 
 
 type PageEditorModeButtonProps = {
   currentEditorMode: EditorMode,
   editorMode: EditorMode,
-  icon: ReactNode,
-  label: ReactNode,
+  children?: ReactNode,
   isBtnDisabled?: boolean,
   onClick?: (mode: EditorMode) => void,
 }
 const PageEditorModeButton = React.memo((props: PageEditorModeButtonProps) => {
   const {
-    currentEditorMode, isBtnDisabled, editorMode, icon, label, onClick,
+    currentEditorMode, isBtnDisabled, editorMode, children, onClick,
   } = props;
 
-  const classNames = ['btn btn-outline-primary px-1'];
+  const classNames = ['btn btn-outline-primary py-1 px-2 d-flex align-items-center justify-content-center'];
   if (currentEditorMode === editorMode) {
     classNames.push('active');
   }
@@ -35,36 +36,43 @@ const PageEditorModeButton = React.memo((props: PageEditorModeButtonProps) => {
       onClick={() => onClick?.(editorMode)}
       data-testid={`${editorMode}-button`}
     >
-      <span className="me-1">{icon}</span>
-      <span>{label}</span>
+      {children}
     </button>
   );
 });
 
 type Props = {
   editorMode: EditorMode | undefined,
-  onPageEditorModeButtonClicked?: (editorMode: EditorMode) => void,
-  isBtnDisabled?: boolean,
+  isBtnDisabled: boolean,
+  path?: string,
+  grant?: number,
+  grantUserGroupId?: string
 }
 
 export const PageEditorModeManager = (props: Props): JSX.Element => {
   const {
     editorMode = EditorMode.View,
     isBtnDisabled,
-    onPageEditorModeButtonClicked,
+    path,
+    grant,
+    grantUserGroupId,
   } = props;
 
   const { t } = useTranslation();
+  const [isCreating, setIsCreating] = useState(false);
+
   const { data: isDeviceSmallerThanMd } = useIsDeviceSmallerThanMd();
 
-  const pageEditorModeButtonClickedHandler = useCallback((viewType) => {
-    if (isBtnDisabled ?? false) {
+  const onPageEditorModeButtonClicked = useOnPageEditorModeButtonClicked(setIsCreating, path, grant, grantUserGroupId);
+  const _isBtnDisabled = isCreating || isBtnDisabled;
+
+  const pageEditorModeButtonClickedHandler = useCallback((viewType: EditorMode) => {
+    if (_isBtnDisabled) {
       return;
     }
-    if (onPageEditorModeButtonClicked != null) {
-      onPageEditorModeButtonClicked(viewType);
-    }
-  }, [isBtnDisabled, onPageEditorModeButtonClicked]);
+
+    onPageEditorModeButtonClicked?.(viewType);
+  }, [_isBtnDisabled, onPageEditorModeButtonClicked]);
 
   return (
     <>
@@ -78,21 +86,21 @@ export const PageEditorModeManager = (props: Props): JSX.Element => {
           <PageEditorModeButton
             currentEditorMode={editorMode}
             editorMode={EditorMode.View}
-            isBtnDisabled={isBtnDisabled}
+            isBtnDisabled={_isBtnDisabled}
             onClick={pageEditorModeButtonClickedHandler}
-            icon={<i className="icon-control-play" />}
-            label={t('view')}
-          />
+          >
+            <span className="material-symbols-outlined fs-4">play_arrow</span>{t('View')}
+          </PageEditorModeButton>
         )}
         {(!isDeviceSmallerThanMd || editorMode === EditorMode.View) && (
           <PageEditorModeButton
             currentEditorMode={editorMode}
             editorMode={EditorMode.Editor}
-            isBtnDisabled={isBtnDisabled}
+            isBtnDisabled={_isBtnDisabled}
             onClick={pageEditorModeButtonClickedHandler}
-            icon={<i className="icon-note" />}
-            label={t('Edit')}
-          />
+          >
+            <span className="material-symbols-outlined me-1 fs-5">edit_square</span>{t('Edit')}
+          </PageEditorModeButton>
         )}
       </div>
     </>

+ 58 - 0
apps/app/src/components/Navbar/hooks.tsx

@@ -0,0 +1,58 @@
+import { useCallback } from 'react';
+
+import { useTranslation } from 'next-i18next';
+import { useRouter } from 'next/router';
+
+import { createPage } from '~/client/services/page-operation';
+import { toastError } from '~/client/util/toastr';
+import { useIsNotFound } from '~/stores/page';
+import { EditorMode, useEditorMode } from '~/stores/ui';
+import loggerFactory from '~/utils/logger';
+
+const logger = loggerFactory('growi:Navbar:GrowiContextualSubNavigation');
+
+export const useOnPageEditorModeButtonClicked = (
+    setIsCreating:React.Dispatch<React.SetStateAction<boolean>>,
+    path?: string,
+    grant?: number,
+    grantUserGroupId?: string,
+): (editorMode: EditorMode) => Promise<void> => {
+  const router = useRouter();
+  const { t } = useTranslation('commons');
+  const { data: isNotFound } = useIsNotFound();
+  const { mutate: mutateEditorMode } = useEditorMode();
+
+  return useCallback(async(editorMode: EditorMode) => {
+    if (isNotFound == null || path == null || grant == null) {
+      return;
+    }
+
+    if (editorMode === EditorMode.Editor && isNotFound) {
+      try {
+        setIsCreating(true);
+
+        const params = {
+          isSlackEnabled: false,
+          slackChannels: '',
+          grant,
+          pageTags: [],
+          grantUserGroupId,
+        };
+
+        const response = await createPage(path, '', params);
+
+        // Should not mutateEditorMode as it might prevent transitioning during mutation
+        router.push(`${response.page.id}#edit`);
+      }
+      catch (err) {
+        logger.warn(err);
+        toastError(t('toaster.create_failed', { target: path }));
+      }
+      finally {
+        setIsCreating(false);
+      }
+    }
+
+    mutateEditorMode(editorMode);
+  }, [grant, grantUserGroupId, isNotFound, mutateEditorMode, path, router, setIsCreating, t]);
+};

+ 0 - 3
apps/app/src/components/Page/DisplaySwitcher.tsx

@@ -12,7 +12,6 @@ import { LazyRenderer } from '../Common/LazyRenderer';
 
 
 const PageEditor = dynamic(() => import('../PageEditor'), { ssr: false });
-const EditorNavbarBottom = dynamic(() => import('../PageEditor/EditorNavbarBottom'), { ssr: false });
 
 
 type Props = {
@@ -37,8 +36,6 @@ export const DisplaySwitcher = (props: Props): JSX.Element => {
       <LazyRenderer shouldRender={isEditable === true && editorMode === EditorMode.Editor}>
         <PageEditor />
       </LazyRenderer>
-
-      { isEditable && !isViewMode && <EditorNavbarBottom /> }
     </>
   );
 };

+ 17 - 10
apps/app/src/components/PageControls/BookmarkButtons.module.scss

@@ -1,17 +1,24 @@
 @use '@growi/core/scss/bootstrap/init' as bs;
 
+@use './button-styles';
+
 .btn-group-bookmark :global {
   .btn-bookmark {
-    box-shadow: none !important;
-
-    @include bs.button-outline-variant(rgba(bs.$secondary, 50%), bs.$orange, rgba(lighten(bs.$orange, 20%), 0.5), rgba(lighten(bs.$orange, 20%), 0.5));
+    @extend %btn-basis;
+  }
+  .dropdown .btn-bookmark {
+    padding-right: 1px;
+  }
+  .total-counts {
+    @extend %btn-total-counts-basis;
+    padding-left: 5px;
+  }
+}
 
-    &:not(:disabled):not(.disabled):active,
-    &:not(:disabled):not(.disabled).active {
-      color: bs.$orange;
-    }
-    &:not(:disabled):not(.disabled):not(:hover) {
-      background-color: transparent;
-    }
+// == Colors
+.btn-group-bookmark :global {
+  .btn-bookmark {
+    @include button-styles.btn-color(bs.$orange);
   }
 }
+

+ 5 - 3
apps/app/src/components/PageControls/BookmarkButtons.tsx

@@ -73,10 +73,12 @@ export const BookmarkButtons: FC<Props> = (props: Props) => {
         <DropdownToggle
           id="bookmark-dropdown-btn"
           color="transparent"
-          className={`shadow-none btn btn-bookmark border-0 rounded-end-0
+          className={`btn btn-bookmark rounded-end-0
           ${isBookmarked ? 'active' : ''} ${isGuestUser ? 'disabled' : ''}`}
         >
-          <i className={`fa ${isBookmarked ? 'fa-bookmark' : 'fa-bookmark-o'}`}></i>
+          <span className={`material-symbols-outlined ${isBookmarked ? 'fill' : ''}`}>
+            bookmark
+          </span>
         </DropdownToggle>
       </BookmarkFolderMenu>
       <UncontrolledTooltip placement="top" data-testid="bookmark-button-tooltip" target="bookmark-dropdown-btn" fade={false}>
@@ -86,7 +88,7 @@ export const BookmarkButtons: FC<Props> = (props: Props) => {
       <button
         type="button"
         id="po-total-bookmarks"
-        className={`shadow-none btn btn-bookmark border-0
+        className={`btn btn-bookmark
           total-counts ${isBookmarked ? 'active' : ''}`}
       >
         {bookmarkCount}

+ 16 - 10
apps/app/src/components/PageControls/LikeButtons.module.scss

@@ -1,17 +1,23 @@
 @use '@growi/core/scss/bootstrap/init' as bs;
 
+@use './button-styles';
+
 .btn-group-like :global {
   .btn-like {
-    box-shadow: none !important;
-
-    @include bs.button-outline-variant(rgba(bs.$secondary, 50%), lighten(bs.$red, 15%), rgba(lighten(bs.$red, 10%), 0.15), rgba(lighten(bs.$red, 10%), 0.5));
+    @extend %btn-basis;
+  }
+  .btn-like#like-button {
+    padding-right: 3px;
+  }
+  .total-counts {
+    @extend %btn-total-counts-basis;
+    padding-left: 5px;
+  }
+}
 
-    &:not(:disabled):not(.disabled):active,
-    &:not(:disabled):not(.disabled).active {
-      color: lighten(bs.$red, 15%);
-    }
-    &:not(:disabled):not(.disabled):not(:hover) {
-      background-color: transparent;
-    }
+// == Colors
+.btn-group-like :global {
+  .btn-like {
+    @include button-styles.btn-color(bs.$red);
   }
 }

+ 3 - 3
apps/app/src/components/PageControls/LikeButtons.tsx

@@ -46,10 +46,10 @@ const LikeButtons: FC<LikeButtonsProps> = (props: LikeButtonsProps) => {
         type="button"
         id="like-button"
         onClick={onLikeClicked}
-        className={`shadow-none btn btn-like border-0
+        className={`btn btn-like
             ${isLiked ? 'active' : ''} ${isGuestUser ? 'disabled' : ''}`}
       >
-        <i className={`fa ${isLiked ? 'fa-heart' : 'fa-heart-o'}`}></i>
+        <span className={`material-symbols-outlined ${isLiked ? 'fill' : ''}`}>favorite</span>
       </button>
 
       <UncontrolledTooltip data-testid="like-button-tooltip" placement="top" target="like-button" autohide={false} fade={false}>
@@ -59,7 +59,7 @@ const LikeButtons: FC<LikeButtonsProps> = (props: LikeButtonsProps) => {
       <button
         type="button"
         id="po-total-likes"
-        className={`shadow-none btn btn-like border-0
+        className={`btn btn-like
           total-counts ${isLiked ? 'active' : ''}`}
       >
         {sumOfLikers}

+ 11 - 23
apps/app/src/components/PageControls/PageControls.module.scss

@@ -1,30 +1,18 @@
-%page-controls-buttons-height {
-  height: 40px;
-}
-
-.grw-page-controls :global {
-
-  .btn-subscribe {
-    --bs-btn-font-size: 18px;
-    @extend %page-controls-buttons-height;
-  }
-
-  .btn-like,
-  .btn-bookmark,
-  .btn-seen-user {
-    --bs-btn-font-size: 18px;
+@use '@growi/core/scss/bootstrap/init' as bs;
 
-    @extend %page-controls-buttons-height;
-    padding-right: 6px;
-    padding-left: 8px;
-  }
+@use './button-styles';
 
-  .total-counts {
-    font-size: 13px;
+// PageItemControl styles
+.grw-page-controls :global {
+  .btn-page-item-control {
+    @extend %btn-basis;
   }
+}
 
+// == Colors
+// PageItemControl colors
+.grw-page-controls :global {
   .btn-page-item-control {
-    @extend %page-controls-buttons-height;
+    @include button-styles.btn-color(bs.$gray-500);
   }
-
 }

+ 16 - 11
apps/app/src/components/PageControls/SeenUserInfo.module.scss

@@ -1,18 +1,23 @@
 @use '@growi/core/scss/bootstrap/init' as bs;
-@use '~/styles/atoms/mixins/buttons' as mixins-buttons;
+
+@use './button-styles';
 
 .grw-seen-user-info :global {
-  .btn.btn-seen-user {
-    $color-seen-user: #549c79;
+  .btn-seen-user {
+    @extend %btn-basis;
+  }
+  .total-counts {
+    @extend %text-total-counts-basis;
+  }
+}
 
-    @include bs.button-outline-variant($color-seen-user, $color-seen-user, rgba(lighten($color-seen-user, 10%), 0.15), rgba(lighten($color-seen-user, 10%), 0.5));
 
-    &:not(:disabled):not(.disabled):active,
-    &:not(:disabled):not(.disabled).active {
-      color: $color-seen-user;
-    }
-    &:not(:disabled):not(.disabled):not(:hover) {
-      background-color: transparent;
-    }
+// == Colors
+
+.grw-seen-user-info :global {
+  $color: #549c79;
+
+  .btn-seen-user {
+    @include button-styles.btn-color($color);
   }
 }

+ 2 - 4
apps/app/src/components/PageControls/SeenUserInfo.tsx

@@ -28,10 +28,8 @@ const SeenUserInfo: FC<Props> = (props: Props) => {
 
   return (
     <div className={`grw-seen-user-info ${styles['grw-seen-user-info']}`}>
-      <button type="button" id="btn-seen-user" className="shadow-none btn btn-seen-user border-0">
-        <span className="me-1 footstamp-icon">
-          <FootstampIcon />
-        </span>
+      <button type="button" id="btn-seen-user" className="shadow-none btn btn-seen-user border-0 d-flex align-items-center">
+        <span className="material-symbols-outlined me-1">footprint</span>
         <span className="total-counts">{sumOfSeenUsers || seenUsers.length}</span>
       </button>
       <Popover placement="bottom" isOpen={isPopoverOpen} target="btn-seen-user" toggle={togglePopover} trigger="legacy" disabled={disabled}>

+ 12 - 10
apps/app/src/components/PageControls/SubscribeButton.module.scss

@@ -1,14 +1,16 @@
 @use '@growi/core/scss/bootstrap/init' as bs;
 
-.btn-subscribe {
-  &:global {
-    @include bs.button-outline-variant(rgba(bs.$secondary, 50%), bs.$success, rgba(lighten(bs.$success, 10%), 0.15), rgba(lighten(bs.$success, 10%), 0.5));
-    &:not(:disabled):not(.disabled):active,
-    &:not(:disabled):not(.disabled).active {
-      color: lighten(bs.$success, 15%);
-    }
-    &:not(:disabled):not(.disabled):not(:hover) {
-      background-color: transparent;
-    }
+@use './button-styles';
+
+.btn-subscribe :global {
+  @extend %btn-basis;
+
+  .total-counts {
+    @extend %btn-total-counts-basis;
   }
 }
+
+// == Colors
+.btn-subscribe {
+  @include button-styles.btn-color(bs.$success);
+}

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

@@ -36,7 +36,9 @@ const SubscribeButton: FC<Props> = (props: Props) => {
         className={`shadow-none btn btn-subscribe ${styles['btn-subscribe']} border-0
           ${isSubscribing ? 'active' : ''} ${isGuestUser ? 'disabled' : ''}`}
       >
-        <i className={`fa ${isSubscribing ? 'fa-bell' : 'fa-bell-slash-o'}`}></i>
+        <span className={`material-symbols-outlined ${isSubscribing ? 'fill' : ''}`}>
+          {isSubscribing ? 'notifications' : 'notifications_off'}
+        </span>
       </button>
 
       <UncontrolledTooltip data-testid="subscribe-button-tooltip" placement="top" target="subscribe-button" fade={false}>

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

@@ -0,0 +1,34 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+
+%btn-basis {
+  --bs-btn-padding-x: 6px;
+  --bs-btn-padding-y: 8px;
+  --bs-btn-line-height: 1em;
+  --bs-btn-border-width: 0;
+  --bs-btn-box-shadow: none;
+}
+
+%btn-total-counts-basis {
+  --bs-btn-font-size: 13px;
+}
+
+%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);
+  }
+}

+ 42 - 0
apps/app/src/components/PageEditor/EditorNavbarBottom.module.scss

@@ -0,0 +1,42 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+@use '~/styles/variables' as var;
+@use '~/styles/mixins';
+
+@include mixins.editing() {
+  .grw-editor-navbar-bottom :global {
+    height: var.$grw-editor-navbar-bottom-height;
+
+    .grw-grant-selector {
+      @include bs.media-breakpoint-down(sm) {
+        .btn .label {
+          display: none;
+        }
+      }
+      @include bs.media-breakpoint-up(md) {
+        .dropdown-toggle {
+          min-width: 100px;
+
+          // caret
+          &::after {
+            margin-left: 1em;
+          }
+        }
+      }
+    }
+
+    .btn-submit {
+      width: 100px;
+    }
+
+    .btn-expand {
+      // rotate icon
+      i {
+        display: inline-block;
+        transition: transform 200ms;
+      }
+      &.expand i {
+        transform: rotate(-180deg);
+      }
+    }
+  }
+}

+ 8 - 4
apps/app/src/components/PageEditor/EditorNavbarBottom.tsx

@@ -13,6 +13,11 @@ import {
 } from '~/stores/ui';
 
 
+import styles from './EditorNavbarBottom.module.scss';
+
+const moduleClass = styles['grw-editor-navbar-bottom'];
+
+
 const SavePageControls = dynamic<SavePageControlsProps>(() => import('~/components/SavePageControls').then(mod => mod.SavePageControls), { ssr: false });
 const SlackLogo = dynamic(() => import('~/components/SlackLogo').then(mod => mod.SlackLogo), { ssr: false });
 const SlackNotification = dynamic(() => import('~/components/SlackNotification').then(mod => mod.SlackNotification), { ssr: false });
@@ -32,7 +37,6 @@ const EditorNavbarBottom = (): JSX.Element => {
   const { data: slackChannelsData } = useSWRxSlackChannels(currentPagePath);
 
   const { data: isSlackEnabled, mutate: mutateIsSlackEnabled } = useIsSlackEnabled();
-  const additionalClasses = ['grw-editor-navbar-bottom'];
 
   const [slackChannelsStr, setSlackChannelsStr] = useState<string>('');
 
@@ -83,7 +87,7 @@ const EditorNavbarBottom = (): JSX.Element => {
       {/* Collapsed SlackNotification */}
       {isSlackConfigured && (
         <Collapse isOpen={isSlackExpanded && isDeviceSmallerThanMd === true}>
-          <nav className={`navbar navbar-expand-lg border-top ${additionalClasses.join(' ')}`}>
+          <nav className={`navbar navbar-expand-lg border-top ${moduleClass}`}>
             {isSlackEnabled != null
             && (
               <SlackNotification
@@ -99,7 +103,7 @@ const EditorNavbarBottom = (): JSX.Element => {
         </Collapse>
       )
       }
-      <div className={`flex-expand-horiz align-items-center border-top px-2 px-md-3 ${additionalClasses.join(' ')}`}>
+      <div className={`flex-expand-horiz align-items-center border-top px-2 px-md-3 ${moduleClass}`}>
         <form>
           { isDeviceSmallerThanMd && renderDrawerButton() }
           { !isDeviceSmallerThanMd && <OptionsSelector /> }
@@ -139,7 +143,7 @@ const EditorNavbarBottom = (): JSX.Element => {
       { isCollapsedOptionsSelectorEnabled && (
         <Collapse isOpen={isExpanded}>
           <div className="px-2"> {/* set padding for border-top */}
-            <div className={`navbar navbar-expand border-top px-0 ${additionalClasses.join(' ')}`}>
+            <div className={`navbar navbar-expand border-top px-0 ${moduleClass}`}>
               <form className="ms-auto">
                 <OptionsSelector />
               </form>

+ 51 - 44
apps/app/src/components/PageEditor/PageEditor.tsx

@@ -59,6 +59,7 @@ import Preview from './Preview';
 import scrollSyncHelper from './ScrollSyncHelper';
 
 import '@growi/editor/dist/style.css';
+import EditorNavbarBottom from './EditorNavbarBottom';
 
 
 const logger = loggerFactory('growi:PageEditor');
@@ -559,53 +560,59 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
   }
 
   return (
-    <div data-testid="page-editor" id="page-editor" className={`flex-expand-horiz ${props.visibility ? '' : 'd-none'}`}>
-      <div className="page-editor-editor-container flex-expand-vert">
-        {/* <Editor
-          ref={editorRef}
-          value={initialValue}
-          isUploadable={isUploadable}
-          isUploadableFile={isUploadableFile}
-          indentSize={currentIndentSize}
-          onScroll={editorScrolledHandler}
-          onScrollCursorIntoView={editorScrollCursorIntoViewHandler}
-          onChange={markdownChangedHandler}
-          onUpload={uploadHandler}
-          onSave={saveWithShortcut}
-        /> */}
-        <CodeMirrorEditorMain
-          onChange={markdownChangedHandler}
-          onSave={saveWithShortcut}
-          onUpload={uploadHandler}
-          indentSize={currentIndentSize ?? defaultIndentSize}
-          pageId={pageId}
-          userName={user?.name}
-          socket={socket}
-          initialValue={initialValue}
-          setMarkdownToPreview={setMarkdownToPreview}
-          acceptedFileType={acceptedFileType}
-        />
+    <div data-testid="page-editor" id="page-editor" className={`flex-expand-vert ${props.visibility ? '' : 'd-none'}`}>
+      <div className="flex-expand-vert justify-content-center align-items-center" style={{ minHeight: '72px' }}>
+        <div>Header</div>
       </div>
-      <div className="page-editor-preview-container flex-expand-vert d-none d-lg-flex">
-        <Preview
-          ref={previewRef}
-          rendererOptions={rendererOptions}
-          markdown={markdownToPreview}
-          pagePath={currentPagePath}
-          // TODO: implement
-          // refs: https://redmine.weseek.co.jp/issues/126519
-          // onScroll={offset => scrollEditorByPreviewScrollWithThrottle(offset)}
+      <div className={`flex-expand-horiz ${props.visibility ? '' : 'd-none'}`}>
+        <div className="page-editor-editor-container flex-expand-vert">
+          {/* <Editor
+            ref={editorRef}
+            value={initialValue}
+            isUploadable={isUploadable}
+            isUploadableFile={isUploadableFile}
+            indentSize={currentIndentSize}
+            onScroll={editorScrolledHandler}
+            onScrollCursorIntoView={editorScrollCursorIntoViewHandler}
+            onChange={markdownChangedHandler}
+            onUpload={uploadHandler}
+            onSave={saveWithShortcut}
+          /> */}
+          <CodeMirrorEditorMain
+            onChange={markdownChangedHandler}
+            onSave={saveWithShortcut}
+            onUpload={uploadHandler}
+            indentSize={currentIndentSize ?? defaultIndentSize}
+            pageId={pageId}
+            userName={user?.name}
+            socket={socket}
+            initialValue={initialValue}
+            setMarkdownToPreview={setMarkdownToPreview}
+            acceptedFileType={acceptedFileType}
+          />
+        </div>
+        <div className="page-editor-preview-container flex-expand-vert d-none d-lg-flex">
+          <Preview
+            ref={previewRef}
+            rendererOptions={rendererOptions}
+            markdown={markdownToPreview}
+            pagePath={currentPagePath}
+            // TODO: implement
+            // refs: https://redmine.weseek.co.jp/issues/126519
+            // onScroll={offset => scrollEditorByPreviewScrollWithThrottle(offset)}
+          />
+        </div>
+        {/*
+        <ConflictDiffModal
+          isOpen={conflictDiffModalStatus?.isOpened}
+          onClose={() => closeConflictDiffModal()}
+          markdownOnEdit={markdownToPreview}
+          optionsToSave={optionsToSave}
+          afterResolvedHandler={afterResolvedHandler}
         />
+        */}
       </div>
-      {/*
-      <ConflictDiffModal
-        isOpen={conflictDiffModalStatus?.isOpened}
-        onClose={() => closeConflictDiffModal()}
-        markdownOnEdit={markdownToPreview}
-        optionsToSave={optionsToSave}
-        afterResolvedHandler={afterResolvedHandler}
-      />
-       */}
+      <EditorNavbarBottom />
     </div>
   );
 });

+ 30 - 0
apps/app/src/components/PageEditor/Preview.module.scss

@@ -0,0 +1,30 @@
+@use '~/styles/variables' as var;
+@use '~/styles/mixins';
+
+@include mixins.editing(true) {
+  .page-editor-preview-body :global {
+  }
+}
+
+// modify width for fluid layout
+@include mixins.editing(true) {
+  .dynamic-layout-root:not(.growi-layout-fluid) {
+    :local {
+      .page-editor-preview-body :global {
+        .wiki {
+          max-width: 980px;
+          margin: 0 auto;
+        }
+      }
+    }
+  }
+  .dynamic-layout-root.growi-layout-fluid {
+    :local {
+      .page-editor-preview-body :global {
+        .wiki {
+          margin: 0 auto;
+        }
+      }
+    }
+  }
+}

+ 6 - 1
apps/app/src/components/PageEditor/Preview.tsx

@@ -7,6 +7,11 @@ import type { RendererOptions } from '~/interfaces/renderer-options';
 import RevisionRenderer from '../Page/RevisionRenderer';
 
 
+import styles from './Preview.module.scss';
+
+const moduleClass = styles['page-editor-preview-body'];
+
+
 type Props = {
   rendererOptions: RendererOptions,
   markdown?: string,
@@ -23,7 +28,7 @@ const Preview = React.forwardRef((props: Props, ref: RefObject<HTMLDivElement>):
 
   return (
     <div
-      className={`page-editor-preview-body ${pagePath === '/Sidebar' ? 'preview-sidebar' : ''}`}
+      className={`${moduleClass} ${pagePath === '/Sidebar' ? 'preview-sidebar' : ''}`}
       ref={ref}
       onScroll={(event: SyntheticEvent<HTMLDivElement>) => {
         if (props.onScroll != null) {

+ 2 - 1
apps/app/src/components/Sidebar/AppTitle/AppTitle.module.scss

@@ -2,8 +2,9 @@
 
 @use '@growi/core/scss/growi-official-colors';
 
+@use '~/styles/variables' as var;
+
 @use '../button-styles';
-@use '../variables' as var;
 
 // GROWI Logo
 .grw-app-title :global {

+ 44 - 9
apps/app/src/components/Sidebar/PageCreateButton.tsx

@@ -2,10 +2,19 @@ import React, { useCallback, useState } from 'react';
 
 import { useRouter } from 'next/router';
 
+import { createPage } from '~/client/services/page-operation';
+import { toastError } from '~/client/util/toastr';
+import { useSWRxCurrentPage } from '~/stores/page';
+import loggerFactory from '~/utils/logger';
+
+const logger = loggerFactory('growi:cli:PageCreateButton');
+
 export const PageCreateButton = React.memo((): JSX.Element => {
   const router = useRouter();
+  const { data: currentPage, isLoading } = useSWRxCurrentPage();
 
   const [isHovered, setIsHovered] = useState(false);
+  const [isCreating, setIsCreating] = useState(false);
 
   const onMouseEnterHandler = () => {
     setIsHovered(true);
@@ -15,13 +24,37 @@ export const PageCreateButton = React.memo((): JSX.Element => {
     setIsHovered(false);
   };
 
-  const iconName = 'create';
-  const isSelected = true;
-  // TODO: create page directly
-  // TODO: https://redmine.weseek.co.jp/issues/132680s
-  const onCreateNewPageButtonHandler = useCallback(() => {
-    // router.push(`${router.pathname}#edit`);
-  }, [router]);
+  const onCreateNewPageButtonHandler = useCallback(async() => {
+    if (isLoading) return;
+
+    try {
+      setIsCreating(true);
+
+      const parentPath = currentPage == null
+        ? '/'
+        : currentPage.path;
+
+      const params = {
+        isSlackEnabled: false,
+        slackChannels: '',
+        grant: currentPage?.grant || 1,
+        pageTags: [],
+        grantUserGroupId: currentPage?.grantedGroup?._id,
+        shouldGeneratePath: true,
+      };
+
+      const response = await createPage(parentPath, '', params);
+
+      router.push(`${response.page.id}#edit`);
+    }
+    catch (err) {
+      logger.warn(err);
+      toastError(err);
+    }
+    finally {
+      setIsCreating(false);
+    }
+  }, [currentPage, isLoading, router]);
   const onCreateTodaysButtonHandler = useCallback(() => {
     // router.push(`${router.pathname}#edit`);
   }, [router]);
@@ -44,12 +77,13 @@ export const PageCreateButton = React.memo((): JSX.Element => {
     >
       <div className="btn-group">
         <button
-          className={`d-block btn btn-primary ${isSelected ? 'active' : ''}`}
+          className="d-block btn btn-primary"
           onClick={onCreateNewPageButtonHandler}
           type="button"
           data-testid="grw-sidebar-nav-page-create-button"
+          disabled={isCreating}
         >
-          <i className="material-icons">{iconName}</i>
+          <i className="material-symbols-outlined">edit</i>
         </button>
       </div>
       {isHovered && (
@@ -66,6 +100,7 @@ export const PageCreateButton = React.memo((): JSX.Element => {
                 className="dropdown-item"
                 onClick={onCreateNewPageButtonHandler}
                 type="button"
+                disabled={isCreating}
               >
                 Create New Page
               </button>

+ 1 - 2
apps/app/src/components/Sidebar/Sidebar.module.scss

@@ -1,9 +1,8 @@
 @use '@growi/core/scss/bootstrap/init' as bs;
 
+@use '~/styles/variables' as var;
 @use '~/styles/mixins';
 
-@use './variables' as var;
-
 .grw-sidebar :global {
   top: 0;
 

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

@@ -1,7 +1,8 @@
 @use '@growi/core/scss/bootstrap/init' as bs;
 
+@use '~/styles/variables' as var;
+
 @use '../button-styles';
-@use '../variables' as var;
 
 .btn-toggle-collapse :global {
   @extend %btn-primary-basis;
@@ -12,7 +13,7 @@
 
 // icon
 .btn-toggle-collapse :global {
-  .material-icons {
+  .material-symbols-outlined {
     transition: transform 0.25s;
 
     // rotation

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

@@ -35,7 +35,7 @@ export const ToggleCollapseButton = memo((): JSX.Element => {
       className={`btn btn-primary ${styles['btn-toggle-collapse']} p-2`}
       onClick={isDrawerMode() ? toggleDrawer : toggleCollapsed}
     >
-      <span className={`material-icons fs-2 ${rotationClass}`}>{icon}</span>
+      <span className={`material-symbols-outlined fs-2 ${rotationClass}`}>{icon}</span>
     </button>
   );
 });

+ 3 - 5
apps/app/src/components/Sidebar/PersonalDropdown.tsx → apps/app/src/components/Sidebar/SidebarNav/PersonalDropdown.tsx

@@ -10,6 +10,8 @@ import { apiv3Post } from '~/client/util/apiv3-client';
 import { toastError } from '~/client/util/toastr';
 import { useCurrentUser } from '~/stores/context';
 
+import { SkeletonItem } from './SkeletonItem';
+
 const ProactiveQuestionnaireModal = dynamic(() => import('~/features/questionnaire/client/components/ProactiveQuestionnaireModal'), { ssr: false });
 
 export const PersonalDropdown = (): JSX.Element => {
@@ -19,11 +21,7 @@ export const PersonalDropdown = (): JSX.Element => {
   const [isQuestionnaireModalOpen, setQuestionnaireModalOpen] = useState(false);
 
   if (currentUser == null) {
-    return (
-      <div className="text-muted text-center mb-5">
-        <i className="fa fa-2x fa-spinner fa-pulse me-1" />
-      </div>
-    );
+    return <SkeletonItem />;
   }
 
   const logoutHandler = async() => {

+ 1 - 1
apps/app/src/components/Sidebar/SidebarNav/PrimaryItems.module.scss

@@ -1,7 +1,7 @@
 @use '@growi/core/scss/bootstrap/init' as bs;
 
+@use '~/styles/variables' as var;
 @use '../button-styles';
-@use '../variables' as var;
 
 @use './variables' as sidebarNavVar;
 

+ 2 - 2
apps/app/src/components/Sidebar/SidebarNav/PrimaryItems.tsx

@@ -80,7 +80,7 @@ const PrimaryItem: FC<PrimaryItemProps> = (props: PrimaryItemProps) => {
       onClick={itemClickedHandler}
       onMouseEnter={mouseEnteredHandler}
     >
-      <i className="material-icons">{iconName}</i>
+      <span className="material-symbols-outlined">{iconName}</span>
     </button>
   );
 };
@@ -104,7 +104,7 @@ export const PrimaryItems = memo((props: Props) => {
       <PrimaryItem sidebarMode={sidebarMode} contents={SidebarContentsType.TREE} label="Page Tree" iconName="format_list_bulleted" onHover={onItemHover} />
       <PrimaryItem sidebarMode={sidebarMode} contents={SidebarContentsType.CUSTOM} label="Custom Sidebar" iconName="code" onHover={onItemHover} />
       <PrimaryItem sidebarMode={sidebarMode} contents={SidebarContentsType.RECENT} label="Recent Changes" iconName="update" onHover={onItemHover} />
-      <PrimaryItem sidebarMode={sidebarMode} contents={SidebarContentsType.BOOKMARKS} label="Bookmarks" iconName="bookmark" onHover={onItemHover} />
+      <PrimaryItem sidebarMode={sidebarMode} contents={SidebarContentsType.BOOKMARKS} label="Bookmarks" iconName="bookmarks" onHover={onItemHover} />
       <PrimaryItem sidebarMode={sidebarMode} contents={SidebarContentsType.TAG} label="Tags" iconName="local_offer" onHover={onItemHover} />
       <InAppNotificationDropdown />
     </div>

+ 1 - 1
apps/app/src/components/Sidebar/SidebarNav/SecondaryItems.module.scss

@@ -31,7 +31,7 @@
   .grw-secondary-items :global {
     .btn-primary {
       --bs-btn-color: var(--grw-sidebar-nav-btn-color, var(--grw-primary-600));
-      --bs-btn-hover-bg: var(--grw-sidebar-nav-btn-hover-bg, var(--grw-highlight-700));
+      --bs-btn-hover-bg: var(--grw-sidebar-nav-btn-hover-bg, var(--grw-highlight-300));
     }
   }
 }

+ 7 - 2
apps/app/src/components/Sidebar/SidebarNav/SecondaryItems.tsx

@@ -5,10 +5,15 @@ import Link from 'next/link';
 
 import { useGrowiCloudUri, useIsAdmin } from '~/stores/context';
 
+import { SkeletonItem } from './SkeletonItem';
+
 import styles from './SecondaryItems.module.scss';
 
 
-const PersonalDropdown = dynamic(() => import('../PersonalDropdown').then(mod => mod.PersonalDropdown), { ssr: false });
+const PersonalDropdown = dynamic(() => import('./PersonalDropdown').then(mod => mod.PersonalDropdown), {
+  ssr: false,
+  loading: () => <SkeletonItem />,
+});
 
 
 type SecondaryItemProps = {
@@ -28,7 +33,7 @@ const SecondaryItem: FC<SecondaryItemProps> = (props: SecondaryItemProps) => {
       target={`${isBlank ? '_blank' : ''}`}
       prefetch={false}
     >
-      <i className="material-icons">{iconName}</i>
+      <i className="material-symbols-outlined">{iconName}</i>
     </Link>
   );
 };

+ 1 - 1
apps/app/src/components/Sidebar/SidebarNav/SidebarNav.module.scss

@@ -1,6 +1,6 @@
 @use '@growi/core/scss/bootstrap/init' as bs;
 
-@use '../variables' as var;
+@use '~/styles/variables' as var;
 
 .grw-sidebar-nav :global {
   // set position and z-index to prevent dropdowns covered by other element

+ 12 - 0
apps/app/src/components/Sidebar/SidebarNav/SkeletonItem.module.scss

@@ -0,0 +1,12 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+
+@use './variables' as sidebarNavVar;
+
+.grw-skeleton-item :global {
+  height: sidebarNavVar.$grw-sidebar-primary-button-height;
+  padding: .75rem;
+
+  .grw-skeleton {
+    background-color: var(--bs-secondary-bg-subtle);
+  }
+}

+ 10 - 0
apps/app/src/components/Sidebar/SidebarNav/SkeletonItem.tsx

@@ -0,0 +1,10 @@
+import { memo } from 'react';
+
+import { Skeleton } from '~/components/Skeleton';
+
+import styles from './SkeletonItem.module.scss';
+
+
+export const SkeletonItem = memo(() => {
+  return <Skeleton additionalClass={styles['grw-skeleton-item']} roundedPill />;
+});

+ 1 - 1
apps/app/src/components/Sidebar/_button-styles.scss

@@ -1,4 +1,4 @@
-@use './variables' as var;
+@use '~/styles/variables' as var;
 
 %btn-primary-basis {
   padding-top: .75rem;

+ 0 - 1
apps/app/src/components/Sidebar/_variables.scss

@@ -1 +0,0 @@
-$grw-sidebar-nav-width: 48px;

+ 1 - 1
apps/app/src/components/TreeItem/SimpleItem.tsx

@@ -247,7 +247,7 @@ export const SimpleItem: FC<SimpleItemProps> = (props) => {
               onClick={onClickLoadChildren}
             >
               <div className="d-flex justify-content-center">
-                <span className="material-icons-round">arrow_right</span>
+                <span className="material-symbols-rounded">arrow_right</span>
               </div>
             </button>
           )}

+ 2 - 2
apps/app/src/features/questionnaire/client/components/QuestionnaireModal.tsx

@@ -149,12 +149,12 @@ const QuestionnaireModal = ({ questionnaireOrder }: QuestionnaireModalProps): JS
 
             {currentUser?.admin && (
               <a href="/admin/app#questionnaire-settings">
-                <i className="material-icons me-1">admin_panel_settings</i>
+                <i className="material-symbols-outlined me-1">admin_panel_settings</i>
               </a>
             )}
             {currentUser != null && (
               <a href="/me#other_settings">
-                <i className="material-icons">settings</i>
+                <i className="material-symbols-outlined">settings</i>
               </a>
             )}
           </div>

+ 1 - 0
apps/app/src/interfaces/page-operation.ts

@@ -34,4 +34,5 @@ export type OptionsToSave = {
   pageTags: string[] | null;
   grantUserGroupId?: string | null;
   grantUserGroupName?: string | null;
+  shouldGeneratePath?: boolean | null;
 };

+ 31 - 3
apps/app/src/server/routes/apiv3/pages.js

@@ -175,6 +175,7 @@ module.exports = (crowi) => {
       body('isSlackEnabled').if(value => value != null).isBoolean().withMessage('isSlackEnabled must be boolean'),
       body('slackChannels').if(value => value != null).isString().withMessage('slackChannels must be string'),
       body('pageTags').if(value => value != null).isArray().withMessage('pageTags must be array'),
+      body('shouldGeneratePath').optional().isBoolean().withMessage('shouldGeneratePath is must be boolean or undefined'),
     ],
     renamePage: [
       body('pageId').isMongoId().withMessage('pageId is required'),
@@ -238,6 +239,17 @@ module.exports = (crowi) => {
     return [];
   }
 
+  async function generateUniquePath(basePath, index = 1) {
+    const Page = mongoose.model('Page');
+    const path = basePath + index;
+    const response = await Page.findByPath(path);
+    const isPathExists = response != null;
+    if (isPathExists) {
+      return generateUniquePath(basePath, index + 1);
+    }
+    return path;
+  }
+
   /**
    * @swagger
    *
@@ -266,9 +278,9 @@ module.exports = (crowi) => {
    *                    type: array
    *                    items:
    *                      $ref: '#/components/schemas/Tag'
-   *                  createFromPageTree:
+   *                  shouldGeneratePath:
    *                    type: boolean
-   *                    description: Whether the page was created from the page tree or not
+   *                    description: Determine whether a new path should be generated
    *                required:
    *                  - body
    *                  - path
@@ -295,7 +307,7 @@ module.exports = (crowi) => {
    */
   router.post('/', accessTokenParser, loginRequiredStrictly, excludeReadOnlyUser, addActivity, validator.createPage, apiV3FormValidator, async(req, res) => {
     const {
-      body, grant, grantUserGroupId, overwriteScopesOfDescendants, isSlackEnabled, slackChannels, pageTags,
+      body, grant, grantUserGroupId, overwriteScopesOfDescendants, isSlackEnabled, slackChannels, pageTags, shouldGeneratePath,
     } = req.body;
 
     let { path } = req.body;
@@ -303,6 +315,22 @@ module.exports = (crowi) => {
     // check whether path starts slash
     path = addHeadingSlash(path);
 
+    if (shouldGeneratePath) {
+      try {
+        const rootPath = '/';
+        const defaultTitle = '/Untitled';
+        const basePath = path === rootPath ? defaultTitle : path + defaultTitle;
+        path = await generateUniquePath(basePath);
+
+        if (!isCreatablePage(path)) {
+          path = await generateUniquePath(defaultTitle);
+        }
+      }
+      catch (err) {
+        return res.apiv3Err(new ErrorV3('Failed to generate unique path'));
+      }
+    }
+
     if (!isCreatablePage(path)) {
       return res.apiv3Err(`Could not use the path '${path}'`);
     }

+ 12 - 35
apps/app/src/stores/ui.tsx

@@ -231,34 +231,6 @@ export const useCurrentProductNavWidth = (initialData?: number): SWRResponse<num
   return useSWRStatic('productNavWidth', initialData, { fallbackData: 320 });
 };
 
-export const useDrawerMode = (): SWRResponse<boolean, Error> => {
-  const { data: editorMode } = useEditorMode();
-  const { data: isDeviceSmallerThanMd } = useIsDeviceSmallerThanMd();
-
-  const condition = editorMode != null && isDeviceSmallerThanMd != null;
-
-  const calcDrawerMode = (
-      _keyString: string,
-      editorMode: EditorMode,
-      isDeviceSmallerThanMd: boolean,
-  ): boolean => {
-    return isDeviceSmallerThanMd
-      ? true
-      : editorMode === EditorMode.Editor;
-  };
-
-  return useSWRImmutable(
-    condition ? ['isDrawerMode', editorMode, isDeviceSmallerThanMd] : null,
-    // calcDrawerMode,
-    key => calcDrawerMode(...key),
-    condition
-      ? {
-        fallbackData: calcDrawerMode('isDrawerMode', editorMode, isDeviceSmallerThanMd),
-      }
-      : undefined,
-  );
-};
-
 export const useDrawerOpened = (isOpened?: boolean): SWRResponse<boolean, Error> => {
   return useSWRStatic('isDrawerOpened', isOpened, { fallbackData: false });
 };
@@ -278,23 +250,28 @@ type DetectSidebarModeUtils = {
 }
 
 export const useSidebarMode = (): SWRResponseWithUtils<DetectSidebarModeUtils, SidebarMode> => {
-  const { data: isDrawerMode } = useDrawerMode();
+  const { data: isDeviceSmallerThanMd } = useIsDeviceSmallerThanMd();
+  const { data: editorMode } = useEditorMode();
   const { data: isCollapsedModeUnderDockMode } = usePreferCollapsedMode();
 
-  const condition = isDrawerMode != null && isCollapsedModeUnderDockMode != null;
+  const condition = isDeviceSmallerThanMd != null && editorMode != null && isCollapsedModeUnderDockMode != null;
+
+  const isEditorMode = editorMode === EditorMode.Editor;
 
-  const fetcher = useCallback(([, isDrawerMode, isCollapsedModeUnderDockMode]: [Key, boolean|undefined, boolean|undefined]) => {
-    if (isDrawerMode) {
+  const fetcher = useCallback((
+      [, isDeviceSmallerThanMd, isEditorMode, isCollapsedModeUnderDockMode]: [Key, boolean|undefined, boolean|undefined, boolean|undefined],
+  ) => {
+    if (isDeviceSmallerThanMd) {
       return SidebarMode.DRAWER;
     }
-    return isCollapsedModeUnderDockMode ? SidebarMode.COLLAPSED : SidebarMode.DOCK;
+    return isEditorMode || isCollapsedModeUnderDockMode ? SidebarMode.COLLAPSED : SidebarMode.DOCK;
   }, []);
 
   const swrResponse = useSWRImmutable(
-    condition ? ['sidebarMode', isDrawerMode, isCollapsedModeUnderDockMode] : null,
+    condition ? ['sidebarMode', isDeviceSmallerThanMd, isEditorMode, isCollapsedModeUnderDockMode] : null,
     // calcDrawerMode,
     fetcher,
-    { fallbackData: fetcher(['sidebarMode', isDrawerMode, isCollapsedModeUnderDockMode]) },
+    { fallbackData: fetcher(['sidebarMode', isDeviceSmallerThanMd, isEditorMode, isCollapsedModeUnderDockMode]) },
   );
 
   const _isDrawerMode = useCallback(() => swrResponse.data === SidebarMode.DRAWER, [swrResponse.data]);

+ 4 - 73
apps/app/src/styles/_editor.scss

@@ -5,10 +5,9 @@
 
 // global imported
 .layout-root.editing {
-  overflow-y: hidden !important;
+  overflow: hidden !important;
 
   .page-wrapper {
-    top: 0;
     height: 100vh;
   }
 
@@ -26,65 +25,11 @@
   /*****************
    * Expand Editor
    *****************/
-  .grw-editor-navbar-bottom {
-    height: var.$grw-editor-navbar-bottom-height;
-
-    .grw-grant-selector {
-      @include bs.media-breakpoint-down(sm) {
-        .btn .label {
-          display: none;
-        }
-      }
-      @include bs.media-breakpoint-up(md) {
-        .dropdown-toggle {
-          min-width: 100px;
-
-          // caret
-          &::after {
-            margin-left: 1em;
-          }
-        }
-      }
-    }
-
-    .btn-submit {
-      width: 100px;
-    }
-
-    .btn-expand {
-      // rotate icon
-      i {
-        display: inline-block;
-        transition: transform 200ms;
-      }
-      &.expand i {
-        transform: rotate(-180deg);
-      }
-    }
-  }
-
-  /*********************
-   * Navigation styles
-   */
-  .grw-subnav {
-    padding-bottom: 0;
-
-    h1 {
-      font-size: 16px;
-    }
-
-    .grw-drawer-toggler {
-      width: 38px;
-      height: 38px;
-      font-size: 18px;
-    }
+  .dynamic-layout-root {
+    width: calc(100vw - var.$grw-sidebar-nav-width);
+    height: 100vh;
   }
 
-  .grw-copy-dropdown {
-    .btn-copy {
-      padding: 3px !important; // overwrite padding
-    }
-  }
 
   &.builtin-editor {
     /*****************
@@ -171,20 +116,6 @@
 
 }
 
-.layout-root.editing {
-  &:not(.growi-layout-fluid) .page-editor-preview-body {
-    .wiki {
-      max-width: 980px;
-      margin: 0 auto;
-    }
-  }
-  &.growi-layout-fluid .page-editor-preview-body {
-    .wiki {
-      margin: 0 auto;
-    }
-  }
-}
-
 // TODO: Never used this id class
 #tag-edit-button-tooltip {
   .tooltip-inner {

+ 1 - 0
apps/app/src/styles/_mixins.scss

@@ -1,6 +1,7 @@
 @use '@growi/core/scss/bootstrap/init' as bs;
 @use './variables' as var;
 
+@import './mixins/editing';
 
 @mixin apply-navigation-transition() {
   transition-timing-function: cubic-bezier(0.25, 1, 0.5, 1);

+ 2 - 0
apps/app/src/styles/_variables.scss

@@ -6,6 +6,8 @@ $grw-marker-cyan: #6ff;
 $grw-marker-green: #6f6;
 
 //== Layout
+$grw-sidebar-nav-width: 48px;
+
 $grw-navbar-bottom-height: 48px;
 $grw-editor-navbar-bottom-height: 48px;
 

+ 8 - 3
apps/app/src/styles/font-icons.scss

@@ -2,7 +2,12 @@
 // font-familiy used in simple-line-icons has to be prioritized than the one used in font-awesome.
 @import 'font-awesome';
 @import 'simple-line-icons';
-@import 'material-icons/iconfont/filled';
-@import 'material-icons/iconfont/outlined';
-@import 'material-icons/iconfont/round';
+@import '@material-symbols/font-300/outlined';
+@import '@material-symbols/font-300/rounded';
 @import '@icon/themify-icons/themify-icons';
+
+.material-symbols-outlined {
+  &.fill {
+    font-variation-settings: 'FILL' 1;
+  }
+}

+ 14 - 0
apps/app/src/styles/mixins/_editing.scss

@@ -0,0 +1,14 @@
+@mixin editing($global: false) {
+  :global {
+    .layout-root.editing {
+      @if ($global) {
+        @content;
+      }
+      @else {
+        :local {
+          @content;
+        }
+      }
+    }
+  }
+}

+ 1 - 1
packages/editor/index.html

@@ -7,7 +7,7 @@
     <title>Vite + React + TS</title>
   </head>
   <body>
-    <div id="root" class="d-flex flex-column vw-100 vh-100"></div>
+    <div id="root"></div>
     <script type="module" src="/src/main.tsx"></script>
   </body>
 </html>

+ 1 - 1
packages/editor/package.json

@@ -26,6 +26,7 @@
     "@codemirror/language": "^6.8.0",
     "@codemirror/state": "^6.2.1",
     "@codemirror/view": "^6.15.3",
+    "@material-symbols/font-300": "^0.13.1",
     "@popperjs/core": "^2.11.8",
     "@types/react": "^18.2.14",
     "@types/react-dom": "^18.2.6",
@@ -33,7 +34,6 @@
     "bootstrap": "^5.3.1",
     "codemirror": "^6.0.1",
     "eslint-plugin-react-refresh": "^0.4.1",
-    "material-icons": "^1.13.10",
     "react-dropzone": "^14.2.3",
     "react-hook-form": "^7.45.4",
     "react-toastify": "^9.1.3",

+ 2 - 2
packages/editor/src/components/CodeMirrorEditor/Toolbar/AttachmentsButton.tsx

@@ -17,7 +17,7 @@ export const AttachmentsButton = (props: Props): JSX.Element => {
     return (
       <>
         <DropdownItem className="d-flex gap-1 align-items-center" onClick={onFileOpen}>
-          <span className="material-icons-outlined fs-5">attach_file</span>
+          <span className="material-symbols-outlined fs-5">attach_file</span>
           Files
         </DropdownItem>
       </>
@@ -27,7 +27,7 @@ export const AttachmentsButton = (props: Props): JSX.Element => {
     return (
       <>
         <DropdownItem className="d-flex gap-1 align-items-center" onClick={onFileOpen}>
-          <span className="material-icons-outlined fs-5">image</span>
+          <span className="material-symbols-outlined fs-5">image</span>
           Images
         </DropdownItem>
       </>

+ 3 - 3
packages/editor/src/components/CodeMirrorEditor/Toolbar/AttachmentsDropup.tsx

@@ -22,17 +22,17 @@ export const AttachmentsDropup = (props: Props): JSX.Element => {
     <>
       <UncontrolledDropdown direction="up" className="lh-1">
         <DropdownToggle className="btn-toolbar-button rounded-circle">
-          <span className="material-icons fs-6">add</span>
+          <span className="material-symbols-outlined fs-6">add</span>
         </DropdownToggle>
         <DropdownMenu>
           <DropdownItem className="d-flex gap-1 align-items-center" header>
-            <span className="material-icons-outlined fs-5">add_circle_outline</span>
+            <span className="material-symbols-outlined fs-5">add_circle_outline</span>
             Attachments
           </DropdownItem>
           <DropdownItem divider />
           <AttachmentsButton onFileOpen={onFileOpen} acceptedFileType={acceptedFileType} />
           <DropdownItem className="d-flex gap-1 align-items-center">
-            <span className="material-icons-outlined fs-5">link</span>
+            <span className="material-symbols-outlined fs-5">link</span>
             Link
           </DropdownItem>
         </DropdownMenu>

+ 1 - 1
packages/editor/src/components/CodeMirrorEditor/Toolbar/DiagramButton.tsx

@@ -1,7 +1,7 @@
 export const DiagramButton = (): JSX.Element => {
   return (
     <button type="button" className="btn btn-toolbar-button">
-      <span className="material-icons-outlined fs-6">lan</span>
+      <span className="material-symbols-outlined fs-5">lan</span>
     </button>
   );
 };

+ 1 - 1
packages/editor/src/components/CodeMirrorEditor/Toolbar/EmojiButton.tsx

@@ -1,7 +1,7 @@
 export const EmojiButton = (): JSX.Element => {
   return (
     <button type="button" className="btn btn-toolbar-button">
-      <span className="material-icons-outlined fs-6">emoji_emotions</span>
+      <span className="material-symbols-outlined fs-5">emoji_emotions</span>
     </button>
   );
 };

+ 1 - 1
packages/editor/src/components/CodeMirrorEditor/Toolbar/TableButton.tsx

@@ -1,7 +1,7 @@
 export const TableButton = (): JSX.Element => {
   return (
     <button type="button" className="btn btn-toolbar-button">
-      <span className="material-icons-outlined fs-6">table_chart</span>
+      <span className="material-symbols-outlined fs-5">table_chart</span>
     </button>
   );
 };

+ 1 - 1
packages/editor/src/components/CodeMirrorEditor/Toolbar/TemplateButton.tsx

@@ -1,7 +1,7 @@
 export const TemplateButton = (): JSX.Element => {
   return (
     <button type="button" className="btn btn-toolbar-button">
-      <span className="material-icons-outlined fs-6">file_copy</span>
+      <span className="material-symbols-outlined fs-5">file_copy</span>
     </button>
   );
 };

+ 10 - 10
packages/editor/src/components/CodeMirrorEditor/Toolbar/TextFormatTools.tsx

@@ -20,7 +20,7 @@ const TextFormatToolsToggler = (props: TogglarProps): JSX.Element => {
       className="btn btn-toolbar-button"
       onClick={onClick}
     >
-      <span className="material-icons fs-5">text_increase</span>
+      <span className="material-symbols-outlined fs-5">match_case</span>
     </button>
   );
 };
@@ -39,31 +39,31 @@ export const TextFormatTools = (): JSX.Element => {
       <Collapse isOpen={isOpen} horizontal>
         <div className="d-flex px-1 gap-1" style={{ width: '220px' }}>
           <button type="button" className="btn btn-toolbar-button">
-            <span className="material-icons-outlined fs-5">format_bold</span>
+            <span className="material-symbols-outlined fs-5">format_bold</span>
           </button>
           <button type="button" className="btn btn-toolbar-button">
-            <span className="material-icons-outlined fs-5">format_italic</span>
+            <span className="material-symbols-outlined fs-5">format_italic</span>
           </button>
           <button type="button" className="btn btn-toolbar-button">
-            <span className="material-icons-outlined fs-5">format_strikethrough</span>
+            <span className="material-symbols-outlined fs-5">format_strikethrough</span>
           </button>
           <button type="button" className="btn btn-toolbar-button">
-            <span className="material-icons-outlined fs-5">block</span>
+            <span className="material-symbols-outlined fs-5">block</span>
           </button>
           <button type="button" className="btn btn-toolbar-button">
-            <span className="material-icons-outlined fs-5">code</span>
+            <span className="material-symbols-outlined fs-5">code</span>
           </button>
           <button type="button" className="btn btn-toolbar-button">
-            <span className="material-icons-outlined fs-5">format_list_bulleted</span>
+            <span className="material-symbols-outlined fs-5">format_list_bulleted</span>
           </button>
           <button type="button" className="btn btn-toolbar-button">
-            <span className="material-icons-outlined fs-5">format_list_numbered</span>
+            <span className="material-symbols-outlined fs-5">format_list_numbered</span>
           </button>
           <button type="button" className="btn btn-toolbar-button">
-            <span className="material-icons-outlined fs-5">block</span>
+            <span className="material-symbols-outlined fs-5">block</span>
           </button>
           <button type="button" className="btn btn-toolbar-button">
-            <span className="material-icons-outlined fs-5">checklist</span>
+            <span className="material-symbols-outlined fs-5">checklist</span>
           </button>
         </div>
       </Collapse>

+ 5 - 6
packages/editor/src/components/CodeMirrorEditor/Toolbar/scss/toolbar-button.scss

@@ -1,10 +1,9 @@
 .btn-toolbar-button {
+  --bs-btn-padding-x: 0;
+  --bs-btn-padding-y: 0;
+  --bs-btn-line-height: 1;
+  --bs-btn-border-width: 0;
+
   width: 24px !important;
   height: 24px !important;
-  padding: 0 !important;
-  font-size: 1rem !important;
-  line-height: 1 !important;
-  text-align: center !important;
-
-  border: 0;
 }

+ 2 - 2
packages/editor/src/components/playground/Playground.tsx

@@ -49,7 +49,7 @@ export const Playground = (): JSX.Element => {
   }, [codeMirrorEditor]);
 
   return (
-    <>
+    <div className="d-flex flex-column vw-100 vh-100">
       <div className="flex-expand-vert justify-content-center align-items-center bg-dark" style={{ minHeight: '83px' }}>
         <div className="text-white">GrowiSubNavigation</div>
       </div>
@@ -74,6 +74,6 @@ export const Playground = (): JSX.Element => {
       <div className="flex-expand-vert justify-content-center align-items-center bg-dark" style={{ minHeight: '50px' }}>
         <div className="text-white">EditorNavbarBottom</div>
       </div>
-    </>
+    </div>
   );
 };

+ 3 - 4
packages/editor/src/main.scss

@@ -1,9 +1,8 @@
 @import 'bootstrap';
 @import 'react-toastify/scss/main';
 
-$material-icons-font-path: 'material-icons/iconfont/';
-@import 'material-icons/iconfont/filled';
-@import 'material-icons/iconfont/outlined';
-@import 'material-icons/iconfont/round';
+$material-symbols-font-path: '@material-symbols/font-300/';
+@import '@material-symbols/font-300/outlined';
+@import '@material-symbols/font-300/rounded';
 
 @import '@growi/core/scss/flex-expand';

+ 5 - 5
yarn.lock

@@ -3229,6 +3229,11 @@
     markdown-it-front-matter "^0.2.3"
     postcss "^8.4.19"
 
+"@material-symbols/font-300@^0.13.1":
+  version "0.13.1"
+  resolved "https://registry.yarnpkg.com/@material-symbols/font-300/-/font-300-0.13.1.tgz#33e1914565a8a8e421cb9de502ec5f6ccdc80256"
+  integrity sha512-3UcU9kw/1hKDyjkeOuv2wx9nwr5XSpbl/GG+o9+TY5xZ3ogeruNQ5aS7mRXqTxQiizLXtmkYeNUcS3N4fLQonQ==
+
 "@microsoft/api-extractor-model@7.27.5":
   version "7.27.5"
   resolved "https://registry.yarnpkg.com/@microsoft/api-extractor-model/-/api-extractor-model-7.27.5.tgz#2220cf20c8587cd4cf78f82c20c4011a9e36a60f"
@@ -11270,11 +11275,6 @@ markdown-table@^3.0.0:
   resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-3.0.2.tgz#9b59eb2c1b22fe71954a65ff512887065a7bb57c"
   integrity sha512-y8j3a5/DkJCmS5x4dMCQL+OR0+2EAq3DOtio1COSHsmW2BGXnNCK3v12hJt1LrUz5iZH5g0LmuYOjDdI+czghA==
 
-material-icons@^1.13.10:
-  version "1.13.12"
-  resolved "https://registry.yarnpkg.com/material-icons/-/material-icons-1.13.12.tgz#eed4082bf0426642edeb027e75397e3064adc536"
-  integrity sha512-/2YoaB79IjUK2B2JB+vIXXYGtBfHb/XG66LvoKVM5ykHW7yfrV5SP6d7KLX6iijY6/G9GqwgtPQ/sbhFnOURVA==
-
 mathjax-full@^3.2.2:
   version "3.2.2"
   resolved "https://registry.yarnpkg.com/mathjax-full/-/mathjax-full-3.2.2.tgz#43f02e55219db393030985d2b6537ceae82f1fa7"