Преглед изворни кода

Merge branch 'dev/7.0.x' into feat/133066-able-to-change-text-format-using-button

soumaeda пре 2 година
родитељ
комит
7c91a13b54
47 измењених фајлова са 650 додато и 295 уклоњено
  1. 3 0
      .github/workflows/reusable-app-prod.yml
  2. 0 17
      apps/app/_obsolete/src/components/Sidebar/NavigationResizeHexagon.tsx
  3. 13 0
      apps/app/public/static/locales/en_US/commons.json
  4. 0 2
      apps/app/public/static/locales/en_US/translation.json
  5. 13 0
      apps/app/public/static/locales/ja_JP/commons.json
  6. 0 2
      apps/app/public/static/locales/ja_JP/translation.json
  7. 13 0
      apps/app/public/static/locales/zh_CN/commons.json
  8. 0 2
      apps/app/public/static/locales/zh_CN/translation.json
  9. 17 1
      apps/app/src/client/services/page-operation.ts
  10. 3 3
      apps/app/src/components/Common/CopyDropdown/CopyDropdown.jsx
  11. 8 0
      apps/app/src/components/Common/CopyDropdown/CopyDropdown.module.scss
  12. 0 2
      apps/app/src/components/Common/DrawerToggler/DrawerToggler.module.scss
  13. 7 0
      apps/app/src/components/Common/PagePathNav/PagePathNav.module.scss
  14. 1 2
      apps/app/src/components/Common/PagePathNav/PagePathNav.tsx
  15. 9 9
      apps/app/src/components/PageDeleteModal.tsx
  16. 1 1
      apps/app/src/components/SearchPage/SearchPageBase.tsx
  17. 3 1
      apps/app/src/components/SearchPage/SearchResultContent.tsx
  18. 0 147
      apps/app/src/components/Sidebar/PageCreateButton.tsx
  19. 46 0
      apps/app/src/components/Sidebar/PageCreateButton/CreateButton.module.scss
  20. 24 0
      apps/app/src/components/Sidebar/PageCreateButton/CreateButton.tsx
  21. 69 0
      apps/app/src/components/Sidebar/PageCreateButton/DropendMenu.tsx
  22. 60 0
      apps/app/src/components/Sidebar/PageCreateButton/DropendToggle.module.scss
  23. 24 0
      apps/app/src/components/Sidebar/PageCreateButton/DropendToggle.tsx
  24. 18 0
      apps/app/src/components/Sidebar/PageCreateButton/Hexagon.tsx
  25. 2 0
      apps/app/src/components/Sidebar/PageCreateButton/PageCreateButton.module.scss
  26. 205 0
      apps/app/src/components/Sidebar/PageCreateButton/PageCreateButton.tsx
  27. 1 0
      apps/app/src/components/Sidebar/PageCreateButton/index.ts
  28. 1 1
      apps/app/src/components/Sidebar/SidebarHead/ToggleCollapseButton.module.scss
  29. 3 6
      apps/app/src/components/Sidebar/SidebarNav/PrimaryItems.module.scss
  30. 1 1
      apps/app/src/components/Sidebar/SidebarNav/PrimaryItems.tsx
  31. 2 5
      apps/app/src/components/Sidebar/SidebarNav/SecondaryItems.module.scss
  32. 2 2
      apps/app/src/components/Sidebar/SidebarNav/SecondaryItems.tsx
  33. 2 2
      apps/app/src/components/Sidebar/SidebarNav/SkeletonItem.module.scss
  34. 0 1
      apps/app/src/components/Sidebar/SidebarNav/_variables.scss
  35. 10 3
      apps/app/src/components/Sidebar/_button-styles.scss
  36. 1 0
      apps/app/src/components/Sidebar/_variables.scss
  37. 2 4
      apps/app/src/server/routes/apiv3/pages.js
  38. 2 1
      apps/app/src/styles/_editor.scss
  39. 1 11
      apps/app/src/styles/_layout.scss
  40. 60 0
      apps/app/turbo.json
  41. 17 4
      packages/core/scss/_flex-expand.scss
  42. 1 0
      packages/core/scss/bootstrap/_variables.scss
  43. 0 17
      packages/core/scss/placeholders/_flex-expand.scss
  44. 1 1
      packages/editor/src/components/playground/Playground.tsx
  45. 1 0
      packages/editor/turbo.json
  46. 2 0
      packages/ui/scss/atoms/_btn-muted.scss
  47. 1 47
      turbo.json

+ 3 - 0
.github/workflows/reusable-app-prod.yml

@@ -27,6 +27,9 @@ jobs:
 
 
     steps:
     steps:
     - uses: actions/checkout@v3
     - uses: actions/checkout@v3
+      with:
+        # retrieve local font files
+        lfs: true
 
 
     - uses: actions/setup-node@v3
     - uses: actions/setup-node@v3
       with:
       with:

+ 0 - 17
apps/app/_obsolete/src/components/Sidebar/NavigationResizeHexagon.tsx

@@ -1,17 +0,0 @@
-import React from 'react';
-
-export const NavigationResizeHexagon = React.memo((): JSX.Element => (
-  <svg
-    xmlns="http://www.w3.org/2000/svg"
-    viewBox="0 0 27.691 23.999"
-  >
-    <g className="background" transform="translate(0 0)">
-      <path d="M20.768,0l6.923,12L20.768,24H6.923L0,12,6.923,0Z" transform="translate(0)"></path>
-    </g>
-    <g className="icon" transform="translate(10 6)">
-      { /* eslint-disable-next-line max-len */ }
-      <path d="M2.124,9.114l5.28,5.34a.647.647,0,0,0,.922,0l.616-.623a.665.665,0,0,0,0-.932L4.759,8.648,8.943,4.4a.665.665,0,0,0,0-.932l-.616-.623a.647.647,0,0,0-.922,0l-5.28,5.34A.665.665,0,0,0,2.124,9.114Z" transform="translate(-1.933 -2.648)"></path>
-    </g>
-  </svg>
-));
-NavigationResizeHexagon.displayName = 'NavigationResizeHexagon';

+ 13 - 0
apps/app/public/static/locales/en_US/commons.json

@@ -68,6 +68,19 @@
     "feedback": "Feedback"
     "feedback": "Feedback"
   },
   },
 
 
+  "create_page_dropdown": {
+    "new_page": "Create New Page",
+    "todays": {
+      "desc": "Create today's ...",
+      "memo": "memo"
+    },
+    "template": {
+      "desc": "Create/Edit template page",
+      "children": "Template for children",
+      "descendants": "Template for descendants"
+    }
+  },
+
   "copy_to_clipboard": {
   "copy_to_clipboard": {
     "Copy to clipboard": "Copy to clipboard",
     "Copy to clipboard": "Copy to clipboard",
     "Page path": "Page path",
     "Page path": "Page path",

+ 0 - 2
apps/app/public/static/locales/en_US/translation.json

@@ -106,8 +106,6 @@
   "Disclose E-mail": "Disclose E-mail",
   "Disclose E-mail": "Disclose E-mail",
   "page exists": "this page already exists",
   "page exists": "this page already exists",
   "Error occurred": "Error occurred",
   "Error occurred": "Error occurred",
-  "Create today's": "Create today's ...",
-  "Memo": "memo",
   "Input page name": "Input page name",
   "Input page name": "Input page name",
   "Input page name (optional)": "Input page name (optional)",
   "Input page name (optional)": "Input page name (optional)",
   "New Page": "New page",
   "New Page": "New page",

+ 13 - 0
apps/app/public/static/locales/ja_JP/commons.json

@@ -70,6 +70,19 @@
     "feedback": "ご意見・ご要望"
     "feedback": "ご意見・ご要望"
   },
   },
 
 
+  "create_page_dropdown": {
+    "new_page": "新規ページ作成",
+    "todays": {
+      "desc": "今日の◯◯を作成",
+      "memo": "メモ"
+    },
+    "template": {
+      "desc": "テンプレートページの作成/編集",
+      "children": "同一階層テンプレート",
+      "decendants": "下位層テンプレート"
+    }
+  },
+
   "copy_to_clipboard": {
   "copy_to_clipboard": {
     "Copy to clipboard": "クリップボードにコピー",
     "Copy to clipboard": "クリップボードにコピー",
     "Page path": "ページ名",
     "Page path": "ページ名",

+ 0 - 2
apps/app/public/static/locales/ja_JP/translation.json

@@ -105,8 +105,6 @@
   "Disclose E-mail": "メールアドレスの公開",
   "Disclose E-mail": "メールアドレスの公開",
   "page exists": "このページはすでに存在しています",
   "page exists": "このページはすでに存在しています",
   "Error occurred": "エラーが発生しました",
   "Error occurred": "エラーが発生しました",
-  "Create today's": "今日の◯◯を作成",
-  "Memo": "メモ",
   "Input page name": "ページ名を入力",
   "Input page name": "ページ名を入力",
   "Input page name (optional)": "ページ名を入力(空欄OK)",
   "Input page name (optional)": "ページ名を入力(空欄OK)",
   "New Page": "新規ページ",
   "New Page": "新規ページ",

+ 13 - 0
apps/app/public/static/locales/zh_CN/commons.json

@@ -71,6 +71,19 @@
     "feedback": "意见和要求"
     "feedback": "意见和要求"
   },
   },
 
 
+  "create_page_dropdown": {
+    "new_page": "新页面",
+    "todays": {
+      "desc": "Create today's ...",
+      "memo": "memo"
+    },
+    "template": {
+      "desc": "创建/编辑模板页",
+      "children": "子模板",
+      "descendants": "子代模板"
+    }
+  },
+
 	"copy_to_clipboard": {
 	"copy_to_clipboard": {
 		"Copy to clipboard": "复制到剪贴板",
 		"Copy to clipboard": "复制到剪贴板",
 		"Page path": "页面路径",
 		"Page path": "页面路径",

+ 0 - 2
apps/app/public/static/locales/zh_CN/translation.json

@@ -111,8 +111,6 @@
 	"Disclose E-mail": "显示邮箱",
 	"Disclose E-mail": "显示邮箱",
 	"page exists": "页面已存在",
 	"page exists": "页面已存在",
 	"Error occurred": "Error occurred",
 	"Error occurred": "Error occurred",
-	"Create today's": "Create today's ...",
-	"Memo": "memo",
 	"Input page name": "Input page name",
 	"Input page name": "Input page name",
 	"Input page name (optional)": "Input page name (optional)",
 	"Input page name (optional)": "Input page name (optional)",
 	"New Page": "新页面",
 	"New Page": "新页面",

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

@@ -9,7 +9,7 @@ import { useCurrentPageId, useSWRMUTxCurrentPage, useSWRxTagsInfo } from '~/stor
 import { useSetRemoteLatestPageData } from '~/stores/remote-latest-page';
 import { useSetRemoteLatestPageData } from '~/stores/remote-latest-page';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
-import { apiPost } from '../util/apiv1-client';
+import { apiGet, apiPost } from '../util/apiv1-client';
 import { apiv3Post, apiv3Put } from '../util/apiv3-client';
 import { apiv3Post, apiv3Put } from '../util/apiv3-client';
 import { toastError } from '../util/toastr';
 import { toastError } from '../util/toastr';
 
 
@@ -203,3 +203,19 @@ export const useUpdateStateAfterSave = (pageId: string|undefined|null, opts?: Up
 export const unlink = async(path: string): Promise<void> => {
 export const unlink = async(path: string): Promise<void> => {
   await apiPost('/pages.unlink', { path });
   await apiPost('/pages.unlink', { path });
 };
 };
+
+
+interface PageExistRequest {
+  pagePaths: string;
+}
+
+interface PageExistResponse {
+  pages: Record<string, boolean>;
+  ok: boolean
+}
+
+export const exist = async(pagePaths: string): Promise<PageExistResponse> => {
+  const request: PageExistRequest = { pagePaths };
+  const res = await apiGet<PageExistResponse>('/pages.exist', request);
+  return res;
+};

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

@@ -110,10 +110,10 @@ export const CopyDropdown = (props) => {
 
 
   return (
   return (
     <>
     <>
-      <Dropdown className={`${styles['grw-copy-dropdown']} grw-copy-dropdown d-print-none`} isOpen={dropdownOpen} toggle={toggleDropdown}>
+      <Dropdown className={`${styles['grw-copy-dropdown']} grw-copy-dropdown d-print-none`} isOpen={dropdownOpen} size="sm" toggle={toggleDropdown}>
         <DropdownToggle
         <DropdownToggle
-          caret
-          className={dropdownToggleClassName}
+          caret={isShareLinkMode}
+          className={`btn-copy ${dropdownToggleClassName}`}
         >
         >
           <span id={dropdownToggleId}>{children}</span>
           <span id={dropdownToggleId}>{children}</span>
         </DropdownToggle>
         </DropdownToggle>

+ 8 - 0
apps/app/src/components/Common/CopyDropdown/CopyDropdown.module.scss

@@ -1,4 +1,12 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+
+@use '@growi/ui/scss/atoms/btn-muted';
+
 .grw-copy-dropdown :global {
 .grw-copy-dropdown :global {
+  .btn.btn-copy {
+    @include btn-muted.colorize(bs.$gray-500);
+  }
+
   .dropdown-menu {
   .dropdown-menu {
     min-width: 310px;
     min-width: 310px;
 
 

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

@@ -1,7 +1,5 @@
 @use '@growi/core/scss/bootstrap/init' as bs;
 @use '@growi/core/scss/bootstrap/init' as bs;
 
 
-@use '@growi/ui/scss/atoms/btn-muted';
-
 @use '~/styles/variables' as var;
 @use '~/styles/variables' as var;
 
 
 
 

+ 7 - 0
apps/app/src/components/Common/PagePathNav/PagePathNav.module.scss

@@ -1,5 +1,7 @@
 @use '@growi/core/scss/bootstrap/init' as bs;
 @use '@growi/core/scss/bootstrap/init' as bs;
 
 
+@use '@growi/ui/scss/atoms/btn-muted';
+
 .grw-mr-02em {
 .grw-mr-02em {
   margin-right: 0.2em;
   margin-right: 0.2em;
 }
 }
@@ -19,3 +21,8 @@
   }
   }
 }
 }
 
 
+.grw-page-path-nav :global {
+  .btn-copy {
+    @include btn-muted.colorize(bs.$orange);
+  }
+}

+ 1 - 2
apps/app/src/components/Common/PagePathNav/PagePathNav.tsx

@@ -76,7 +76,6 @@ export const PagePathNav: FC<Props> = (props: Props) => {
   }
   }
 
 
   const copyDropdownId = `copydropdown-${pageId}`;
   const copyDropdownId = `copydropdown-${pageId}`;
-  const copyDropdownToggleClassName = 'd-block btn-outline-secondary btn-copy border-0 text-muted p-2';
 
 
   return (
   return (
     <div>
     <div>
@@ -87,7 +86,7 @@ export const PagePathNav: FC<Props> = (props: Props) => {
         </h1>
         </h1>
         { pageId != null && !isNotFound && (
         { pageId != null && !isNotFound && (
           <div className="mx-2">
           <div className="mx-2">
-            <CopyDropdown pageId={pageId} pagePath={pagePath} dropdownToggleId={copyDropdownId} dropdownToggleClassName={copyDropdownToggleClassName}>
+            <CopyDropdown pageId={pageId} pagePath={pagePath} dropdownToggleId={copyDropdownId} dropdownToggleClassName="p-2">
               <i className="ti ti-clipboard"></i>
               <i className="ti ti-clipboard"></i>
             </CopyDropdown>
             </CopyDropdown>
           </div>
           </div>

+ 9 - 9
apps/app/src/components/PageDeleteModal.tsx

@@ -32,12 +32,12 @@ const logger = loggerFactory('growi:cli:PageDeleteModal');
 const deleteIconAndKey = {
 const deleteIconAndKey = {
   completely: {
   completely: {
     color: 'danger',
     color: 'danger',
-    icon: 'fire',
+    icon: 'delete_forever',
     translationKey: 'completely',
     translationKey: 'completely',
   },
   },
   temporary: {
   temporary: {
-    color: 'primary',
-    icon: 'trash',
+    color: 'warning',
+    icon: 'delete',
     translationKey: 'page',
     translationKey: 'page',
   },
   },
 };
 };
@@ -245,10 +245,10 @@ const PageDeleteModal: FC = () => {
     }
     }
 
 
     return (
     return (
-      <>
-        <i className={`icon-fw icon-${deleteIconAndKey[deleteMode].icon}`}></i>
-        { t(`modal_delete.delete_${deleteIconAndKey[deleteMode].translationKey}`) }
-      </>
+      <span className={`text-${deleteIconAndKey[deleteMode].color} d-flex align-items-center`}>
+        <span className="material-symbols-outlined me-1">{deleteIconAndKey[deleteMode].icon}</span>
+        <b>{ t(`modal_delete.delete_${deleteIconAndKey[deleteMode].translationKey}`) }</b>
+      </span>
     );
     );
   };
   };
 
 
@@ -280,7 +280,7 @@ const PageDeleteModal: FC = () => {
         <ApiErrorMessageList errs={errs} />
         <ApiErrorMessageList errs={errs} />
         <button
         <button
           type="button"
           type="button"
-          className={`btn btn-${deleteIconAndKey[deleteMode].color}`}
+          className={`btn btn-outline-${deleteIconAndKey[deleteMode].color}`}
           disabled={!isDeletable}
           disabled={!isDeletable}
           onClick={deleteButtonHandler}
           onClick={deleteButtonHandler}
           data-testid="delete-page-button"
           data-testid="delete-page-button"
@@ -294,7 +294,7 @@ const PageDeleteModal: FC = () => {
 
 
   return (
   return (
     <Modal size="lg" isOpen={isOpened} toggle={closeDeleteModal} data-testid="page-delete-modal">
     <Modal size="lg" isOpen={isOpened} toggle={closeDeleteModal} data-testid="page-delete-modal">
-      <ModalHeader tag="h4" toggle={closeDeleteModal} className={`bg-${deleteIconAndKey[deleteMode].color} text-light`}>
+      <ModalHeader toggle={closeDeleteModal}>
         {headerContent()}
         {headerContent()}
       </ModalHeader>
       </ModalHeader>
       <ModalBody>
       <ModalBody>

+ 1 - 1
apps/app/src/components/SearchPage/SearchPageBase.tsx

@@ -169,7 +169,7 @@ const SearchPageBaseSubstance: ForwardRefRenderFunction<ISelectableAll & IReturn
     : undefined;
     : undefined;
 
 
   return (
   return (
-    <div className="search-result-base flex-expand-horiz" data-testid="search-result-base">
+    <div className="search-result-base flex-grow-1 d-flex flex-expand-vh-100" data-testid="search-result-base">
 
 
       <div className="flex-expand-vert border boder-gray search-result-list" id="search-result-list">
       <div className="flex-expand-vert border boder-gray search-result-list" id="search-result-list">
 
 

+ 3 - 1
apps/app/src/components/SearchPage/SearchResultContent.tsx

@@ -31,6 +31,8 @@ import type { PageContentFooterProps } from '../PageContentFooter';
 
 
 import styles from './SearchResultContent.module.scss';
 import styles from './SearchResultContent.module.scss';
 
 
+const moduleClass = styles['search-result-content'];
+
 
 
 const SubNavButtons = dynamic(() => import('../PageControls').then(mod => mod.PageControls), { ssr: false });
 const SubNavButtons = dynamic(() => import('../PageControls').then(mod => mod.PageControls), { ssr: false });
 const RevisionLoader = dynamic<RevisionLoaderProps>(() => import('../Page/RevisionLoader').then(mod => mod.RevisionLoader), { ssr: false });
 const RevisionLoader = dynamic<RevisionLoaderProps>(() => import('../Page/RevisionLoader').then(mod => mod.RevisionLoader), { ssr: false });
@@ -210,7 +212,7 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
     <div
     <div
       key={page._id}
       key={page._id}
       data-testid="search-result-content"
       data-testid="search-result-content"
-      className={`dynamic-layout-root ${growiLayoutFluidClass} search-result-content ${styles['search-result-content']}`}
+      className={`dynamic-layout-root ${growiLayoutFluidClass} ${moduleClass}`}
     >
     >
       <RightComponent />
       <RightComponent />
 
 

+ 0 - 147
apps/app/src/components/Sidebar/PageCreateButton.tsx

@@ -1,147 +0,0 @@
-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);
-  };
-
-  const onMouseLeaveHandler = () => {
-    setIsHovered(false);
-  };
-
-  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]);
-  const onTemplateForChildrenButtonHandler = useCallback(() => {
-    // router.push(`${router.pathname}/_template#edit`);
-  }, [router]);
-  const onTemplateForDescendantsButtonHandler = useCallback(() => {
-    // router.push(`${router.pathname}/__template#edit`);
-  }, [router]);
-
-  // TODO: update button design
-  // https://redmine.weseek.co.jp/issues/132683
-  // TODO: i18n
-  // https://redmine.weseek.co.jp/issues/132681
-  return (
-    <div
-      className="d-flex flex-row"
-      onMouseEnter={onMouseEnterHandler}
-      onMouseLeave={onMouseLeaveHandler}
-    >
-      <div className="btn-group">
-        <button
-          className="d-block btn btn-primary"
-          onClick={onCreateNewPageButtonHandler}
-          type="button"
-          data-testid="grw-sidebar-nav-page-create-button"
-          disabled={isCreating}
-        >
-          <i className="material-symbols-outlined">edit</i>
-        </button>
-      </div>
-      {isHovered && (
-        <div className="btn-group dropend">
-          <button
-            className="btn btn-secondary dropdown-toggle dropdown-toggle-split position-absolute"
-            type="button"
-            data-bs-toggle="dropdown"
-            aria-expanded="false"
-          />
-          <ul className="dropdown-menu">
-            <li>
-              <button
-                className="dropdown-item"
-                onClick={onCreateNewPageButtonHandler}
-                type="button"
-                disabled={isCreating}
-              >
-                Create New Page
-              </button>
-            </li>
-            <li><hr className="dropdown-divider" /></li>
-            <li><span className="text-muted px-3">Create today&apos;s ...</span></li>
-            {/* TODO: show correct create today's page path */}
-            {/* https://redmine.weseek.co.jp/issues/132682 */}
-            <li>
-              <button
-                className="dropdown-item"
-                onClick={onCreateTodaysButtonHandler}
-                type="button"
-              >
-                Create today&apos;s
-              </button>
-            </li>
-            <li><hr className="dropdown-divider" /></li>
-            <li><span className="text-muted px-3">Child page template</span></li>
-            <li>
-              <button
-                className="dropdown-item"
-                onClick={onTemplateForChildrenButtonHandler}
-                type="button"
-              >
-                Template for children
-              </button>
-            </li>
-            <li>
-              <button
-                className="dropdown-item"
-                onClick={onTemplateForDescendantsButtonHandler}
-                type="button"
-              >
-                Template for descendants
-              </button>
-            </li>
-          </ul>
-        </div>
-      )}
-    </div>
-  );
-});
-PageCreateButton.displayName = 'PageCreateButton';

+ 46 - 0
apps/app/src/components/Sidebar/PageCreateButton/CreateButton.module.scss

@@ -0,0 +1,46 @@
+@use '~/styles/variables' as var;
+
+@use '../button-styles';
+
+.btn-create :global {
+  @extend %btn-basis;
+
+  // centering
+  .icon {
+    top: 50%;
+    left: 50%;
+    transform: translateX(-50%) translateY(-50%);
+  }
+}
+
+// pointer-events
+.btn-create :global {
+  pointer-events: none;
+
+  svg .background {
+    pointer-events: fill;
+  }
+}
+
+// == Colors
+.btn-create {
+  background-color: transparent !important;
+}
+
+.btn-create :global {
+  svg {
+    fill: var(--bs-btn-bg);
+  }
+}
+
+.btn-create:hover :global {
+  svg {
+    fill: var(--bs-btn-hover-bg);
+  }
+}
+
+.btn-create:active :global {
+  svg {
+    fill: var(--bs-btn-active-bg);
+  }
+}

+ 24 - 0
apps/app/src/components/Sidebar/PageCreateButton/CreateButton.tsx

@@ -0,0 +1,24 @@
+import type { ButtonHTMLAttributes, DetailedHTMLProps } from 'react';
+
+import { Hexagon } from './Hexagon';
+
+import styles from './CreateButton.module.scss';
+
+const moduleClass = styles['btn-create'];
+
+
+type Props = DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
+
+export const CreateButton = (props: Props): JSX.Element => {
+  return (
+    <button
+      type="button"
+      {...props}
+      className={`${moduleClass} btn btn-primary ${props.className ?? ''}`}
+      data-testid="grw-sidebar-nav-page-create-button"
+    >
+      <Hexagon />
+      <span className="icon material-symbols-outlined position-absolute">edit</span>
+    </button>
+  );
+};

+ 69 - 0
apps/app/src/components/Sidebar/PageCreateButton/DropendMenu.tsx

@@ -0,0 +1,69 @@
+import React from 'react';
+
+import { useTranslation } from 'react-i18next';
+
+type DropendMenuProps = {
+  todaysPath: string,
+  onClickCreateNewPageButtonHandler: () => Promise<void>
+  onClickCreateTodaysButtonHandler: () => Promise<void>
+  onClickTemplateForChildrenButtonHandler: () => Promise<void>
+  onClickTemplateForDescendantsButtonHandler: () => Promise<void>
+}
+
+export const DropendMenu = React.memo((props: DropendMenuProps): JSX.Element => {
+  const {
+    todaysPath,
+    onClickCreateNewPageButtonHandler,
+    onClickCreateTodaysButtonHandler,
+    onClickTemplateForChildrenButtonHandler,
+    onClickTemplateForDescendantsButtonHandler,
+  } = props;
+
+  const { t } = useTranslation('commons');
+
+  return (
+    <ul className="dropdown-menu">
+      <li>
+        <button
+          className="dropdown-item"
+          onClick={onClickCreateNewPageButtonHandler}
+          type="button"
+        >
+          {t('create_page_dropdown.new_page')}
+        </button>
+      </li>
+      <li><hr className="dropdown-divider" /></li>
+      <li><span className="text-muted px-3">{t('create_page_dropdown.todays.desc')}</span></li>
+      <li>
+        <button
+          className="dropdown-item"
+          onClick={onClickCreateTodaysButtonHandler}
+          type="button"
+        >
+          {todaysPath}
+        </button>
+      </li>
+      <li><hr className="dropdown-divider" /></li>
+      <li><span className="text-muted text-nowrap px-3">{t('create_page_dropdown.template.desc')}</span></li>
+      <li>
+        <button
+          className="dropdown-item"
+          onClick={onClickTemplateForChildrenButtonHandler}
+          type="button"
+        >
+          {t('create_page_dropdown.template.children')}
+        </button>
+      </li>
+      <li>
+        <button
+          className="dropdown-item"
+          onClick={onClickTemplateForDescendantsButtonHandler}
+          type="button"
+        >
+          {t('create_page_dropdown.template.decendants')}
+        </button>
+      </li>
+    </ul>
+  );
+});
+DropendMenu.displayName = 'DropendMenu';

+ 60 - 0
apps/app/src/components/Sidebar/PageCreateButton/DropendToggle.module.scss

@@ -0,0 +1,60 @@
+@use '~/styles/variables' as var;
+
+@use '../button-styles';
+
+.btn-toggle :global {
+  @extend %btn-basis;
+
+  left: 12px;
+  padding: 0;
+
+  .icon {
+    top: 50%;
+    right: 0px;
+    font-size: 22px;
+    transform: translateY(-50%);
+  }
+}
+
+// no caret
+.btn-toggle {
+  &:global {
+    // no caret
+    &::after {
+      display: none !important;
+    }
+  }
+}
+
+// hitarea
+.btn-toggle :global {
+  .hitarea {
+    top: 0;
+    right: -10px;
+    bottom: 0;
+    left: 0;
+  }
+}
+
+// == Colors
+.btn-toggle {
+  background-color: transparent !important;
+}
+
+.btn-toggle :global {
+  svg {
+    fill: var(--grw-primary-400);
+  }
+}
+
+.btn-toggle:hover :global {
+  svg {
+    fill: var(--grw-primary-400);
+  }
+}
+
+.btn-toggle:active :global {
+  svg {
+    fill: var(--grw-primary-600);
+  }
+}

+ 24 - 0
apps/app/src/components/Sidebar/PageCreateButton/DropendToggle.tsx

@@ -0,0 +1,24 @@
+import type { ButtonHTMLAttributes, DetailedHTMLProps } from 'react';
+
+import { Hexagon } from './Hexagon';
+
+import styles from './DropendToggle.module.scss';
+
+const moduleClass = styles['btn-toggle'];
+
+
+type Props = DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
+
+export const DropendToggle = (props: Props): JSX.Element => {
+  return (
+    <button
+      type="button"
+      {...props}
+      className={`${moduleClass} btn btn-primary ${props.className ?? ''}`}
+    >
+      <Hexagon />
+      <div className="hitarea position-absolute" />
+      <span className="icon material-symbols-outlined position-absolute">chevron_right</span>
+    </button>
+  );
+};

+ 18 - 0
apps/app/src/components/Sidebar/PageCreateButton/Hexagon.tsx

@@ -0,0 +1,18 @@
+import React from 'react';
+
+type Props = {
+  className?: string,
+}
+
+export const Hexagon = React.memo((props: Props): JSX.Element => (
+  <svg
+    xmlns="http://www.w3.org/2000/svg"
+    viewBox="0 0 27.691 23.999"
+    height="36px"
+    className={props.className}
+  >
+    <g className="background" transform="translate(0 0)">
+      <path d="M20.768,0l6.923,12L20.768,24H6.923L0,12,6.923,0Z" transform="translate(0)"></path>
+    </g>
+  </svg>
+));

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

@@ -0,0 +1,2 @@
+.grw-page-create-button :global {
+}

+ 205 - 0
apps/app/src/components/Sidebar/PageCreateButton/PageCreateButton.tsx

@@ -0,0 +1,205 @@
+import React, { useCallback, useState } from 'react';
+
+import { pagePathUtils } from '@growi/core/dist/utils';
+import { format } from 'date-fns';
+import { useRouter } from 'next/router';
+
+import { createPage, exist } from '~/client/services/page-operation';
+import { toastError } from '~/client/util/toastr';
+import { useCurrentUser } from '~/stores/context';
+import { useSWRxCurrentPage } from '~/stores/page';
+import loggerFactory from '~/utils/logger';
+
+import { DropendMenu } from './DropendMenu';
+import { CreateButton } from './CreateButton';
+import { DropendToggle } from './DropendToggle';
+
+const logger = loggerFactory('growi:cli:PageCreateButton');
+
+export const PageCreateButton = React.memo((): JSX.Element => {
+  const router = useRouter();
+  const { data: currentPage, isLoading } = useSWRxCurrentPage();
+  const { data: currentUser } = useCurrentUser();
+
+  const [isHovered, setIsHovered] = useState(false);
+  const [isCreating, setIsCreating] = useState(false);
+
+  const now = format(new Date(), 'yyyy/MM/dd');
+  const userHomepagePath = pagePathUtils.userHomepagePath(currentUser);
+  const todaysPath = `${userHomepagePath}/memo/${now}`;
+
+  const onMouseEnterHandler = () => {
+    setIsHovered(true);
+  };
+
+  const onMouseLeaveHandler = () => {
+    setIsHovered(false);
+  };
+
+  const onClickCreateNewPageButtonHandler = 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 onClickCreateTodaysButtonHandler = useCallback(async() => {
+    if (currentUser == null) {
+      return;
+    }
+
+    try {
+      setIsCreating(true);
+
+      // TODO: get grant, grantUserGroupId data from parent page
+      // https://redmine.weseek.co.jp/issues/133892
+      const params = {
+        isSlackEnabled: false,
+        slackChannels: '',
+        grant: 1,
+        pageTags: [],
+      };
+
+      const res = await exist(JSON.stringify([todaysPath]));
+      if (!res.pages[todaysPath]) {
+        await createPage(todaysPath, '', params);
+      }
+
+      router.push(`${todaysPath}#edit`);
+    }
+    catch (err) {
+      logger.warn(err);
+      toastError(err);
+    }
+    finally {
+      setIsCreating(false);
+    }
+  }, [currentUser, router, todaysPath]);
+
+  const onClickTemplateForChildrenButtonHandler = useCallback(async() => {
+    if (isLoading) return;
+
+    try {
+      setIsCreating(true);
+
+      const path = currentPage == null || currentPage.path === '/'
+        ? '/_template'
+        : `${currentPage.path}/_template`;
+
+      const params = {
+        isSlackEnabled: false,
+        slackChannels: '',
+        grant: currentPage?.grant || 1,
+        pageTags: [],
+        grantUserGroupId: currentPage?.grantedGroup?._id,
+      };
+
+      const res = await exist(JSON.stringify([path]));
+      if (!res.pages[path]) {
+        await createPage(path, '', params);
+      }
+
+      router.push(`${path}#edit`);
+    }
+    catch (err) {
+      logger.warn(err);
+      toastError(err);
+    }
+    finally {
+      setIsCreating(false);
+    }
+  }, [currentPage, isLoading, router]);
+
+  const onClickTemplateForDescendantsButtonHandler = useCallback(async() => {
+    if (isLoading) return;
+
+    try {
+      setIsCreating(true);
+
+      const path = currentPage == null || currentPage.path === '/'
+        ? '/__template'
+        : `${currentPage.path}/__template`;
+
+      const params = {
+        isSlackEnabled: false,
+        slackChannels: '',
+        grant: currentPage?.grant || 1,
+        pageTags: [],
+        grantUserGroupId: currentPage?.grantedGroup?._id,
+      };
+
+      const res = await exist(JSON.stringify([path]));
+      if (!res.pages[path]) {
+        await createPage(path, '', params);
+      }
+
+      router.push(`${path}#edit`);
+    }
+    catch (err) {
+      logger.warn(err);
+      toastError(err);
+    }
+    finally {
+      setIsCreating(false);
+    }
+  }, [currentPage, isLoading, router]);
+
+  // TODO: update button design
+  // https://redmine.weseek.co.jp/issues/132683
+  return (
+    <div
+      className="d-flex flex-row"
+      onMouseEnter={onMouseEnterHandler}
+      onMouseLeave={onMouseLeaveHandler}
+    >
+      <div className="btn-group flex-grow-1">
+        <CreateButton
+          className="z-2"
+          onClick={onClickCreateNewPageButtonHandler}
+          disabled={isCreating}
+        />
+      </div>
+      { isHovered && (
+        <div className="btn-group dropend position-absolute">
+          <DropendToggle
+            className="dropdown-toggle dropdown-toggle-split"
+            data-bs-toggle="dropdown"
+            aria-expanded="false"
+          />
+          <DropendMenu
+            todaysPath={todaysPath}
+            onClickCreateNewPageButtonHandler={onClickCreateNewPageButtonHandler}
+            onClickCreateTodaysButtonHandler={onClickCreateTodaysButtonHandler}
+            onClickTemplateForChildrenButtonHandler={onClickTemplateForChildrenButtonHandler}
+            onClickTemplateForDescendantsButtonHandler={onClickTemplateForDescendantsButtonHandler}
+          />
+        </div>
+      )}
+    </div>
+  );
+});

+ 1 - 0
apps/app/src/components/Sidebar/PageCreateButton/index.ts

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

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

@@ -5,7 +5,7 @@
 @use '../button-styles';
 @use '../button-styles';
 
 
 .btn-toggle-collapse :global {
 .btn-toggle-collapse :global {
-  @extend %btn-primary-basis;
+  @extend %btn-basis;
 
 
   $height: var.$grw-sidebar-nav-width; // declare $height with the same value as the sidebar nav width
   $height: var.$grw-sidebar-nav-width; // declare $height with the same value as the sidebar nav width
   height: $height;
   height: $height;

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

@@ -1,15 +1,12 @@
 @use '@growi/core/scss/bootstrap/init' as bs;
 @use '@growi/core/scss/bootstrap/init' as bs;
 
 
-@use '~/styles/variables' as var;
 @use '../button-styles';
 @use '../button-styles';
 
 
-@use './variables' as sidebarNavVar;
+@use '../variables' as sidebarVar;
 
 
 .grw-primary-items :global {
 .grw-primary-items :global {
   .btn {
   .btn {
-    @extend %btn-primary-basis;
-
-    height: sidebarNavVar.$grw-sidebar-primary-button-height;
+    @extend %btn-basis;
 
 
     i {
     i {
       opacity: 0.7;
       opacity: 0.7;
@@ -24,7 +21,7 @@
 
 
 // Add indicator
 // Add indicator
 .grw-primary-items :global {
 .grw-primary-items :global {
-  $btn-height: sidebarNavVar.$grw-sidebar-primary-button-height;
+  $btn-height: sidebarVar.$grw-sidebar-button-height;
   $btn-active-indicator-height: 34px;
   $btn-active-indicator-height: 34px;
 
 
   .btn {
   .btn {

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

@@ -74,7 +74,7 @@ const PrimaryItem: FC<PrimaryItemProps> = (props: PrimaryItemProps) => {
     <button
     <button
       type="button"
       type="button"
       data-testid={`grw-sidebar-nav-primary-${labelForTestId}`}
       data-testid={`grw-sidebar-nav-primary-${labelForTestId}`}
-      className={`d-block btn btn-primary ${indicatorClass}`}
+      className={`btn btn-primary ${indicatorClass}`}
       onClick={itemClickedHandler}
       onClick={itemClickedHandler}
       onMouseEnter={mouseEnteredHandler}
       onMouseEnter={mouseEnteredHandler}
     >
     >

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

@@ -2,15 +2,12 @@
 
 
 @use '../button-styles';
 @use '../button-styles';
 
 
-@use './variables' as sidebarNavVar;
 
 
 .grw-secondary-items :global {
 .grw-secondary-items :global {
   .btn {
   .btn {
-    @extend %btn-primary-basis;
+    @extend %btn-basis;
 
 
-    height: sidebarNavVar.$grw-sidebar-primary-button-height;
-
-    i {
+    span {
       opacity: 0.6;
       opacity: 0.6;
 
 
       &:hover,
       &:hover,

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

@@ -29,11 +29,11 @@ const SecondaryItem: FC<SecondaryItemProps> = (props: SecondaryItemProps) => {
   return (
   return (
     <Link
     <Link
       href={href}
       href={href}
-      className="d-block btn btn-primary"
+      className="d-block btn btn-primary d-flex align-items-center justify-content-center"
       target={`${isBlank ? '_blank' : ''}`}
       target={`${isBlank ? '_blank' : ''}`}
       prefetch={false}
       prefetch={false}
     >
     >
-      <i className="material-symbols-outlined">{iconName}</i>
+      <span className="material-symbols-outlined">{iconName}</span>
     </Link>
     </Link>
   );
   );
 };
 };

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

@@ -1,9 +1,9 @@
 @use '@growi/core/scss/bootstrap/init' as bs;
 @use '@growi/core/scss/bootstrap/init' as bs;
 
 
-@use './variables' as sidebarNavVar;
+@use '../variables' as sidebarVar;
 
 
 .grw-skeleton-item :global {
 .grw-skeleton-item :global {
-  height: sidebarNavVar.$grw-sidebar-primary-button-height;
+  height: sidebarVar.$grw-sidebar-button-height;
   padding: .75rem;
   padding: .75rem;
 
 
   .grw-skeleton {
   .grw-skeleton {

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

@@ -1 +0,0 @@
-$grw-sidebar-primary-button-height: 50px;

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

@@ -1,8 +1,15 @@
 @use '~/styles/variables' as var;
 @use '~/styles/variables' as var;
 
 
-%btn-primary-basis {
-  padding-top: .75rem;
-  padding-bottom: .75rem;
+@use './variables' as sidebarVar;
+
+
+%btn-basis {
+  --bs-btn-padding-x: 0;
+  --bs-btn-padding-y: 0;
+
+  width: var.$grw-sidebar-nav-width;
+  height: sidebarVar.$grw-sidebar-button-height;
+
   line-height: 1em;
   line-height: 1em;
   border: 0;
   border: 0;
   border-radius: 0;
   border-radius: 0;

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

@@ -0,0 +1 @@
+$grw-sidebar-button-height: 50px;

+ 2 - 4
apps/app/src/server/routes/apiv3/pages.js

@@ -240,11 +240,9 @@ module.exports = (crowi) => {
   }
   }
 
 
   async function generateUniquePath(basePath, index = 1) {
   async function generateUniquePath(basePath, index = 1) {
-    const Page = mongoose.model('Page');
     const path = basePath + index;
     const path = basePath + index;
-    const response = await Page.findByPath(path);
-    const isPathExists = response != null;
-    if (isPathExists) {
+    const existingPageId = await Page.exists({ path, isEmpty: false });
+    if (existingPageId != null) {
       return generateUniquePath(basePath, index + 1);
       return generateUniquePath(basePath, index + 1);
     }
     }
     return path;
     return path;

+ 2 - 1
apps/app/src/styles/_editor.scss

@@ -1,4 +1,5 @@
 @use '@growi/core/scss/bootstrap/init' as bs;
 @use '@growi/core/scss/bootstrap/init' as bs;
+
 @use './variables' as var;
 @use './variables' as var;
 
 
 @import './organisms/wiki-custom-sidebar';
 @import './organisms/wiki-custom-sidebar';
@@ -27,7 +28,7 @@
    *****************/
    *****************/
   .dynamic-layout-root {
   .dynamic-layout-root {
     width: calc(100vw - var.$grw-sidebar-nav-width);
     width: calc(100vw - var.$grw-sidebar-nav-width);
-    height: 100vh;
+    @extend .flex-expand-vh-100;
   }
   }
 
 
 
 

+ 1 - 11
apps/app/src/styles/_layout.scss

@@ -1,19 +1,9 @@
 @use '@growi/core/scss/bootstrap/init' as bs;
 @use '@growi/core/scss/bootstrap/init' as bs;
-@use '@growi/core/scss/flex-expand';
 
 
 @use './variables' as var;
 @use './variables' as var;
 
 
-.flex-expand-horiz {
-  @extend %flex-expand-horiz;
-}
-
-.flex-expand-vert {
-  @extend %flex-expand-vert;
-}
-
 .dynamic-layout-root {
 .dynamic-layout-root {
-  @extend %flex-expand-vert;
-  overflow-y: unset;
+  @extend .flex-expand-vert;
 }
 }
 
 
 .dynamic-layout-root.growi-layout-fluid .grw-container-convertible {
 .dynamic-layout-root.growi-layout-fluid .grw-container-convertible {

+ 60 - 0
apps/app/turbo.json

@@ -0,0 +1,60 @@
+{
+  "$schema": "https://turbo.build/schema.json",
+  "extends": ["//"],
+  "pipeline": {
+
+    "styles-prebuilt": {
+      "outputs": ["src/styles/prebuilt/**"],
+      "inputs": [
+        "src/styles/**/*.scss",
+        "../../packages/core/scss/**"
+      ],
+      "outputMode": "new-only"
+    },
+    "build": {
+      "dependsOn": ["^build", "styles-prebuilt"],
+      "outputs": [".next/**", "!.next/cache/**", "dist/**"],
+      "outputMode": "new-only"
+    },
+
+    "dev:migrate": {
+      "dependsOn": ["@growi/core#dev"],
+      "outputs": ["tmp/cache/migration-status.out"],
+      "inputs": ["src/migrations/*.js"],
+      "outputMode": "new-only"
+    },
+    "dev:styles-prebuilt": {
+      "outputs": ["src/styles/prebuilt/**"],
+      "inputs": [
+        "src/styles/**/*.scss",
+        "!src/styles/prebuilt/**",
+        "../../packages/core/scss/**"
+      ],
+      "outputMode": "new-only"
+    },
+    "dev": {
+      "dependsOn": ["^dev", "dev:migrate", "dev:styles-prebuilt"],
+      "cache": false,
+      "persistent": true
+    },
+    "dev:ci": {
+      "dependsOn": ["^dev", "dev:migrate", "dev:styles-prebuilt"],
+      "cache": false
+    },
+
+    "lint": {
+      "dependsOn": ["^dev", "dev:styles-prebuilt"]
+    },
+
+    "test": {
+      "dependsOn": ["^dev"],
+      "outputMode": "new-only"
+    },
+
+    "version": {
+      "cache": false,
+      "dependsOn": ["^version", "//#version"]
+    }
+
+  }
+}

+ 17 - 4
packages/core/scss/_flex-expand.scss

@@ -1,9 +1,22 @@
-@use './placeholders/flex-expand';
-
 .flex-expand-horiz {
 .flex-expand-horiz {
-  @extend %flex-expand-horiz;
+  display: flex;
+  flex-direction: row;
+  flex-grow: 1;
+  height: 100%;
 }
 }
 
 
 .flex-expand-vert {
 .flex-expand-vert {
-  @extend %flex-expand-vert;
+  display: flex;
+  flex: 1;
+  flex-direction: column;
+  height: 100%;
+}
+
+.flex-expand-vh-100 {
+  height: 100vh;
+
+  .flex-expand-horiz,
+  .flex-expand-vert {
+    overflow-y: auto;
+  }
 }
 }

+ 1 - 0
packages/core/scss/bootstrap/_variables.scss

@@ -113,6 +113,7 @@ $font-family-base: $font-family-sans-serif;
 // $modal-content-border-radius: $border-radius-lg;
 // $modal-content-border-radius: $border-radius-lg;
 // $modal-header-padding-y: 0.75rem;
 // $modal-header-padding-y: 0.75rem;
 // $modal-header-padding-x: 1rem;
 // $modal-header-padding-x: 1rem;
+$modal-footer-border-width: 0;
 
 
 //== Alerts
 //== Alerts
 // $alert-bg-level: -2;
 // $alert-bg-level: -2;

+ 0 - 17
packages/core/scss/placeholders/_flex-expand.scss

@@ -1,17 +0,0 @@
-// ref: https://discuss.codemirror.net/t/how-to-fit-the-codemirror-6-widget-into-a-flex-div/4207/4
-%flex-expand-horiz {
-  display: flex;
-  flex-direction: row;
-  flex-grow: 1;
-  height: 100%;
-  overflow-y: auto;
-}
-
-// ref: https://discuss.codemirror.net/t/how-to-fit-the-codemirror-6-widget-into-a-flex-div/4207/4
-%flex-expand-vert {
-  display: flex;
-  flex: 1;
-  flex-direction: column;
-  height: 100%;
-  overflow-y: auto;
-}

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

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

+ 1 - 0
packages/editor/turbo.json

@@ -1,4 +1,5 @@
 {
 {
+  "$schema": "https://turbo.build/schema.json",
   "extends": ["//"],
   "extends": ["//"],
   "pipeline": {
   "pipeline": {
     "build": {
     "build": {

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

@@ -12,6 +12,8 @@
   --bs-btn-active-color: #{$color-active};
   --bs-btn-active-color: #{$color-active};
   --bs-btn-active-bg: transparent;
   --bs-btn-active-bg: transparent;
 
 
+  --bs-btn-border-width: 0;
+
   &:hover {
   &:hover {
     --bs-btn-active-bg: rgba(#{$color-active-rgb}, 0.2);
     --bs-btn-active-bg: rgba(#{$color-active-rgb}, 0.2);
   }
   }

+ 1 - 47
turbo.json

@@ -39,19 +39,6 @@
       "outputs": ["dist/**"],
       "outputs": ["dist/**"],
       "outputMode": "new-only"
       "outputMode": "new-only"
     },
     },
-    "@growi/app#styles-prebuilt": {
-      "outputs": ["src/styles/prebuilt/**"],
-      "inputs": [
-        "src/styles/**/*.scss",
-        "../../packages/core/scss/**/*.scss"
-      ],
-      "outputMode": "new-only"
-    },
-    "@growi/app#build": {
-      "dependsOn": ["^build", "@growi/app#styles-prebuilt"],
-      "outputs": [".next/**", "!.next/cache/**", "dist/**"],
-      "outputMode": "new-only"
-    },
     "@growi/slackbot-proxy#build": {
     "@growi/slackbot-proxy#build": {
       "dependsOn": ["@growi/slack#build"],
       "dependsOn": ["@growi/slack#build"],
       "outputs": ["dist/**"],
       "outputs": ["dist/**"],
@@ -89,29 +76,6 @@
       "outputs": ["dist/**"],
       "outputs": ["dist/**"],
       "outputMode": "new-only"
       "outputMode": "new-only"
     },
     },
-    "@growi/app#dev:migrate": {
-      "dependsOn": ["@growi/core#dev"],
-      "outputs": ["tmp/cache/migration-status.out"],
-      "inputs": ["src/migrations/*.js"],
-      "outputMode": "new-only"
-    },
-    "@growi/app#dev:styles-prebuilt": {
-      "outputs": ["src/styles/prebuilt/**"],
-      "inputs": [
-        "src/styles/**/*.scss",
-        "!src/styles/prebuilt/**"
-      ],
-      "outputMode": "new-only"
-    },
-    "@growi/app#dev": {
-      "dependsOn": ["^dev", "@growi/app#dev:migrate", "@growi/app#dev:styles-prebuilt"],
-      "cache": false,
-      "persistent": true
-    },
-    "@growi/app#dev:ci": {
-      "dependsOn": ["^dev", "@growi/app#dev:migrate", "@growi/app#dev:styles-prebuilt"],
-      "cache": false
-    },
     "@growi/slackbot-proxy#dev": {
     "@growi/slackbot-proxy#dev": {
       "dependsOn": ["@growi/slack#dev"],
       "dependsOn": ["@growi/slack#dev"],
       "cache": false,
       "cache": false,
@@ -157,19 +121,12 @@
     "@growi/ui#lint": {
     "@growi/ui#lint": {
       "dependsOn": ["@growi/core#dev"]
       "dependsOn": ["@growi/core#dev"]
     },
     },
-    "@growi/app#lint": {
-      "dependsOn": ["^dev", "@growi/app#dev:styles-prebuilt"]
-    },
     "@growi/slackbot-proxy#lint": {
     "@growi/slackbot-proxy#lint": {
       "dependsOn": ["@growi/slack#dev"]
       "dependsOn": ["@growi/slack#dev"]
     },
     },
     "lint": {
     "lint": {
     },
     },
 
 
-    "@growi/app#test": {
-      "dependsOn": ["^dev"],
-      "outputMode": "new-only"
-    },
     "@growi/slackbot-proxy#test": {
     "@growi/slackbot-proxy#test": {
       "dependsOn": ["@growi/slack#dev"],
       "dependsOn": ["@growi/slack#dev"],
       "outputMode": "new-only"
       "outputMode": "new-only"
@@ -189,10 +146,7 @@
     "test": {
     "test": {
       "outputMode": "new-only"
       "outputMode": "new-only"
     },
     },
-    "@growi/app#version": {
-      "cache": false,
-      "dependsOn": ["^version", "//#version"]
-    },
+
     "version": {
     "version": {
       "cache": false
       "cache": false
     },
     },