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

Merge branch 'dev/7.0.x' into feat/139307-140753-replacement-simple-line-icon-all

satof3 2 лет назад
Родитель
Сommit
366e0bbcd8
38 измененных файлов с 391 добавлено и 362 удалено
  1. 1 0
      apps/app/public/static/locales/en_US/translation.json
  2. 1 0
      apps/app/public/static/locales/ja_JP/translation.json
  3. 1 0
      apps/app/public/static/locales/zh_CN/translation.json
  4. 1 1
      apps/app/src/client/services/page-operation.ts
  5. 1 1
      apps/app/src/client/services/side-effects/drawio-modal-launcher-for-view.ts
  6. 3 1
      apps/app/src/client/services/side-effects/handsontable-modal-launcher-for-view.ts
  7. 2 2
      apps/app/src/client/util/bookmark-utils.ts
  8. 1 1
      apps/app/src/components/ContentLinkButtons.tsx
  9. 2 2
      apps/app/src/components/Page/PageView.tsx
  10. 1 1
      apps/app/src/components/PageAccessoriesModal/PageAttachment.tsx
  11. 4 0
      apps/app/src/components/PageContentFooter.tsx
  12. 5 5
      apps/app/src/components/PageControls/PageControls.tsx
  13. 2 31
      apps/app/src/components/PageEditor/EditorNavbarBottom.tsx
  14. 211 170
      apps/app/src/components/PageEditor/OptionsSelector.tsx
  15. 2 14
      apps/app/src/components/PageEditor/PageEditor.tsx
  16. 1 1
      apps/app/src/components/PageEditor/page-path-rename-utils.ts
  17. 1 1
      apps/app/src/components/PagePresentationModal.tsx
  18. 8 5
      apps/app/src/components/PageSideContents/PageSideContents.tsx
  19. 1 1
      apps/app/src/components/PageStatusAlert.tsx
  20. 1 1
      apps/app/src/components/PageTimeline.tsx
  21. 8 17
      apps/app/src/components/SearchPage.tsx
  22. 5 5
      apps/app/src/components/SearchPage/OperateAllControl.tsx
  23. 9 0
      apps/app/src/components/SearchPage/SearchControl.module.scss
  24. 4 3
      apps/app/src/components/SearchPage/SearchOptionModal.tsx
  25. 15 17
      apps/app/src/components/SearchPage/SearchResultContent.tsx
  26. 3 0
      apps/app/src/components/SearchPage/SortControl.module.scss
  27. 32 35
      apps/app/src/components/SearchPage/SortControl.tsx
  28. 1 1
      apps/app/src/components/ShareLinkPageView.tsx
  29. 1 1
      apps/app/src/components/Sidebar/Custom/CustomSidebarSubstance.tsx
  30. 4 10
      apps/app/src/interfaces/editor-settings.ts
  31. 1 1
      apps/app/src/pages/[[...path]].page.tsx
  32. 1 0
      apps/app/src/server/models/page.ts
  33. 22 1
      apps/app/src/stores/page.tsx
  34. 2 2
      packages/core/src/interfaces/page.ts
  35. 5 5
      packages/editor/src/components/CodeMirrorEditor/CodeMirrorEditor.tsx
  36. 3 2
      packages/editor/src/components/playground/Playground.tsx
  37. 6 4
      packages/editor/src/components/playground/PlaygroundController.tsx
  38. 19 20
      packages/editor/src/services/editor-theme/index.ts

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

@@ -538,6 +538,7 @@
     "search_again" : "Search again",
     "search_again" : "Search again",
     "number_of_list_to_display" : "Display",
     "number_of_list_to_display" : "Display",
     "page_number_unit" : "pages",
     "page_number_unit" : "pages",
+    "hit_number_unit" : "hit",
     "sort_axis": {
     "sort_axis": {
       "relationScore": "Sort by relevance",
       "relationScore": "Sort by relevance",
       "createdAt": "Creation date",
       "createdAt": "Creation date",

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

@@ -571,6 +571,7 @@
     "search_again" : "再検索",
     "search_again" : "再検索",
     "number_of_list_to_display" : "表示件数",
     "number_of_list_to_display" : "表示件数",
     "page_number_unit" : "件",
     "page_number_unit" : "件",
+    "hit_number_unit" : "件",
     "sort_axis": {
     "sort_axis": {
       "relationScore": "関連度順",
       "relationScore": "関連度順",
       "createdAt": "作成日時",
       "createdAt": "作成日時",

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

@@ -541,6 +541,7 @@
     "search_again" : "再次搜索",
     "search_again" : "再次搜索",
     "number_of_list_to_display" : "显示器的数量",
     "number_of_list_to_display" : "显示器的数量",
     "page_number_unit" : "例",
     "page_number_unit" : "例",
+    "hit_number_unit" : "例",
     "sort_axis": {
     "sort_axis": {
       "relationScore": "按相关性排序",
       "relationScore": "按相关性排序",
       "createdAt": "按创建日期排序",
       "createdAt": "按创建日期排序",

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

@@ -124,7 +124,7 @@ export const useUpdateStateAfterSave = (pageId: string|undefined|null, opts?: Up
     await mutateCurrentPageId(pageId);
     await mutateCurrentPageId(pageId);
     const updatedPage = await mutateCurrentPage();
     const updatedPage = await mutateCurrentPage();
 
 
-    if (updatedPage == null) { return }
+    if (updatedPage == null || updatedPage.revision == null) { return }
 
 
     // supress to mutate only when updated from built-in editor
     // supress to mutate only when updated from built-in editor
     // and see: https://github.com/weseek/growi/pull/7118
     // and see: https://github.com/weseek/growi/pull/7118

+ 1 - 1
apps/app/src/client/services/side-effects/drawio-modal-launcher-for-view.ts

@@ -34,7 +34,7 @@ export const useDrawioModalLauncherForView = (opts?: {
   const { open: openDrawioModal } = useDrawioModal();
   const { open: openDrawioModal } = useDrawioModal();
 
 
   const saveByDrawioModal = useCallback(async(drawioMxFile: string, bol: number, eol: number) => {
   const saveByDrawioModal = useCallback(async(drawioMxFile: string, bol: number, eol: number) => {
-    if (currentPage == null || shareLinkId != null) {
+    if (currentPage == null || currentPage.revision == null || shareLinkId != null) {
       return;
       return;
     }
     }
 
 

+ 3 - 1
apps/app/src/client/services/side-effects/handsontable-modal-launcher-for-view.ts

@@ -33,7 +33,7 @@ export const useHandsontableModalLauncherForView = (opts?: {
   const { open: openHandsontableModal } = useHandsontableModal();
   const { open: openHandsontableModal } = useHandsontableModal();
 
 
   const saveByHandsontableModal = useCallback(async(table: MarkdownTable, bol: number, eol: number) => {
   const saveByHandsontableModal = useCallback(async(table: MarkdownTable, bol: number, eol: number) => {
-    if (currentPage == null || shareLinkId != null) {
+    if (currentPage == null || currentPage.revision == null || shareLinkId != null) {
       return;
       return;
     }
     }
 
 
@@ -64,6 +64,8 @@ export const useHandsontableModalLauncherForView = (opts?: {
     }
     }
 
 
     const handler = (bol: number, eol: number) => {
     const handler = (bol: number, eol: number) => {
+      if (currentPage.revision == null) return;
+
       const markdown = currentPage.revision.body;
       const markdown = currentPage.revision.body;
       const currentMarkdownTable = getMarkdownTableFromLine(markdown, bol, eol);
       const currentMarkdownTable = getMarkdownTableFromLine(markdown, bol, eol);
       openHandsontableModal(currentMarkdownTable, false, table => saveByHandsontableModal(table, bol, eol));
       openHandsontableModal(currentMarkdownTable, false, table => saveByHandsontableModal(table, bol, eol));

+ 2 - 2
apps/app/src/client/util/bookmark-utils.ts

@@ -1,6 +1,6 @@
 import type { IRevision, Ref } from '@growi/core';
 import type { IRevision, Ref } from '@growi/core';
 
 
-import { BookmarkFolderItems } from '~/interfaces/bookmark-info';
+import type { BookmarkFolderItems } from '~/interfaces/bookmark-info';
 
 
 import { apiv3Delete, apiv3Post, apiv3Put } from './apiv3-client';
 import { apiv3Delete, apiv3Post, apiv3Put } from './apiv3-client';
 
 
@@ -31,7 +31,7 @@ export const deleteBookmarkFolder = async(bookmarkFolderId: string): Promise<voi
 };
 };
 
 
 // Rename page from bookmark item control
 // Rename page from bookmark item control
-export const renamePage = async(pageId: string, revisionId: Ref<IRevision>, newPagePath: string): Promise<void> => {
+export const renamePage = async(pageId: string, revisionId: Ref<IRevision> | undefined, newPagePath: string): Promise<void> => {
   await apiv3Put('/pages/rename', { pageId, revisionId, newPagePath });
   await apiv3Put('/pages/rename', { pageId, revisionId, newPagePath });
 };
 };
 
 

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

@@ -40,7 +40,7 @@ RecentlyCreatedLinkButton.displayName = 'RecentlyCreatedLinkButton';
 
 
 
 
 export type ContentLinkButtonsProps = {
 export type ContentLinkButtonsProps = {
-  author?: IUserHasId,
+  author: IUserHasId | null,
 }
 }
 
 
 export const ContentLinkButtons = (props: ContentLinkButtonsProps): JSX.Element => {
 export const ContentLinkButtons = (props: ContentLinkButtonsProps): JSX.Element => {

+ 2 - 2
apps/app/src/components/Page/PageView.tsx

@@ -68,7 +68,7 @@ export const PageView = (props: Props): JSX.Element => {
   const { data: viewOptions } = useViewOptions();
   const { data: viewOptions } = useViewOptions();
 
 
   const page = pageBySWR ?? initialPage;
   const page = pageBySWR ?? initialPage;
-  const isNotFound = isNotFoundMeta || page?.revision == null;
+  const isNotFound = isNotFoundMeta || page == null;
   const isUsersHomepagePath = isUsersHomepage(pagePath);
   const isUsersHomepagePath = isUsersHomepage(pagePath);
 
 
   const shouldExpandContent = useShouldExpandContent(page);
   const shouldExpandContent = useShouldExpandContent(page);
@@ -124,7 +124,7 @@ export const PageView = (props: Props): JSX.Element => {
     : null;
     : null;
 
 
   const Contents = () => {
   const Contents = () => {
-    if (isNotFound) {
+    if (isNotFound || page?.revision == null) {
       return <NotFoundPage path={pagePath} />;
       return <NotFoundPage path={pagePath} />;
     }
     }
 
 

+ 1 - 1
apps/app/src/components/PageAccessoriesModal/PageAttachment.tsx

@@ -33,7 +33,7 @@ const PageAttachment = (): JSX.Element => {
   const { data: dataAttachments, remove } = useSWRxAttachments(pageId, pageNumber);
   const { data: dataAttachments, remove } = useSWRxAttachments(pageId, pageNumber);
   const { open: openDeleteAttachmentModal } = useDeleteAttachmentModal();
   const { open: openDeleteAttachmentModal } = useDeleteAttachmentModal();
   const { data: currentPage } = useSWRxCurrentPage();
   const { data: currentPage } = useSWRxCurrentPage();
-  const markdown = currentPage?.revision.body;
+  const markdown = currentPage?.revision?.body;
 
 
   // Custom hooks
   // Custom hooks
   const inUseAttachmentsMap: { [id: string]: boolean } | undefined = useMemo(() => {
   const inUseAttachmentsMap: { [id: string]: boolean } | undefined = useMemo(() => {

+ 4 - 0
apps/app/src/components/PageContentFooter.tsx

@@ -21,6 +21,10 @@ export const PageContentFooter = (props: PageContentFooterProps): JSX.Element =>
     creator, lastUpdateUser, createdAt, updatedAt,
     creator, lastUpdateUser, createdAt, updatedAt,
   } = page;
   } = page;
 
 
+  if (page.isEmpty) {
+    return <></>;
+  }
+
   return (
   return (
     <div className={`${styles['page-content-footer']} page-content-footer py-4 d-edit-none d-print-none}`}>
     <div className={`${styles['page-content-footer']} page-content-footer py-4 d-edit-none d-print-none}`}>
       <div className="container-lg grw-container-convertible">
       <div className="container-lg grw-container-convertible">

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

@@ -107,7 +107,7 @@ type CommonProps = {
 type PageControlsSubstanceProps = CommonProps & {
 type PageControlsSubstanceProps = CommonProps & {
   pageId: string,
   pageId: string,
   shareLinkId?: string | null,
   shareLinkId?: string | null,
-  revisionId: string | null,
+  revisionId?: string | null,
   path?: string | null,
   path?: string | null,
   pageInfo: IPageInfoForOperation,
   pageInfo: IPageInfoForOperation,
   expandContentWidth?: boolean,
   expandContentWidth?: boolean,
@@ -178,7 +178,7 @@ const PageControlsSubstance = (props: PageControlsSubstanceProps): JSX.Element =
     const page: IPageToRenameWithMeta = {
     const page: IPageToRenameWithMeta = {
       data: {
       data: {
         _id: pageId,
         _id: pageId,
-        revision: revisionId,
+        revision: revisionId ?? null,
         path,
         path,
       },
       },
       meta: pageInfo,
       meta: pageInfo,
@@ -195,7 +195,7 @@ const PageControlsSubstance = (props: PageControlsSubstanceProps): JSX.Element =
     const pageToDelete: IPageToDeleteWithMeta = {
     const pageToDelete: IPageToDeleteWithMeta = {
       data: {
       data: {
         _id: pageId,
         _id: pageId,
-        revision: revisionId,
+        revision: revisionId ?? null,
         path,
         path,
       },
       },
       meta: pageInfo,
       meta: pageInfo,
@@ -311,7 +311,7 @@ const PageControlsSubstance = (props: PageControlsSubstanceProps): JSX.Element =
 type PageControlsProps = CommonProps & {
 type PageControlsProps = CommonProps & {
   pageId: string,
   pageId: string,
   shareLinkId?: string | null,
   shareLinkId?: string | null,
-  revisionId?: string,
+  revisionId?: string | null,
   path?: string | null,
   path?: string | null,
   expandContentWidth?: boolean,
   expandContentWidth?: boolean,
 };
 };
@@ -346,7 +346,7 @@ export const PageControls = memo((props: PageControlsProps): JSX.Element => {
       {...props}
       {...props}
       pageInfo={pageInfo}
       pageInfo={pageInfo}
       pageId={pageId}
       pageId={pageId}
-      revisionId={revisionId ?? null}
+      revisionId={revisionId}
       path={path}
       path={path}
       onClickEditTagsButton={onClickEditTagsButton}
       onClickEditTagsButton={onClickEditTagsButton}
       onClickDuplicateMenuItem={onClickDuplicateMenuItem}
       onClickDuplicateMenuItem={onClickDuplicateMenuItem}

+ 2 - 31
apps/app/src/components/PageEditor/EditorNavbarBottom.tsx

@@ -26,7 +26,6 @@ const OptionsSelector = dynamic(() => import('~/components/PageEditor/OptionsSel
 
 
 const EditorNavbarBottom = (): JSX.Element => {
 const EditorNavbarBottom = (): JSX.Element => {
 
 
-  const [isExpanded, setExpanded] = useState(false);
   const [isSlackExpanded, setSlackExpanded] = useState(false);
   const [isSlackExpanded, setSlackExpanded] = useState(false);
 
 
   const { data: editorMode } = useEditorMode();
   const { data: editorMode } = useEditorMode();
@@ -57,23 +56,8 @@ const EditorNavbarBottom = (): JSX.Element => {
     setSlackChannelsStr(slackChannels);
     setSlackChannelsStr(slackChannels);
   }, []);
   }, []);
 
 
-
-  const renderExpandButton = () => (
-    <div className="d-md-none ms-2">
-      <button
-        type="button"
-        className="btn btn-outline-secondary btn-expand border-0 material-symbols-outlined"
-        onClick={() => setExpanded(!isExpanded)}
-      >
-        {isExpanded ? 'expand_more' : 'expand_less'}
-      </button>
-    </div>
-  );
-
-  const isCollapsedOptionsSelectorEnabled = !isDeviceLargerThanLg;
-
   return (
   return (
-    <div className={`${isCollapsedOptionsSelectorEnabled ? 'fixed-bottom' : ''} `} data-testid="grw-editor-navbar-bottom">
+    <div data-testid="grw-editor-navbar-bottom">
       {/* Collapsed SlackNotification */}
       {/* Collapsed SlackNotification */}
       {isSlackConfigured && (
       {isSlackConfigured && (
         <Collapse isOpen={isSlackExpanded && !isDeviceLargerThanLg}>
         <Collapse isOpen={isSlackExpanded && !isDeviceLargerThanLg}>
@@ -95,7 +79,7 @@ const EditorNavbarBottom = (): JSX.Element => {
       }
       }
       <div className={`flex-expand-horiz align-items-center px-2 px-md-3 ${moduleClass}`}>
       <div className={`flex-expand-horiz align-items-center px-2 px-md-3 ${moduleClass}`}>
         <form>
         <form>
-          { isDeviceLargerThanMd && <OptionsSelector /> }
+          <OptionsSelector collapsed={!isDeviceLargerThanMd} />
         </form>
         </form>
         <form className="row row-cols-lg-auto g-3 align-items-center ms-auto">
         <form className="row row-cols-lg-auto g-3 align-items-center ms-auto">
           {/* Responsive Design for the SlackNotification */}
           {/* Responsive Design for the SlackNotification */}
@@ -125,21 +109,8 @@ const EditorNavbarBottom = (): JSX.Element => {
             </div>
             </div>
           ))}
           ))}
           <SavePageControls slackChannels={slackChannelsStr} />
           <SavePageControls slackChannels={slackChannelsStr} />
-          { isCollapsedOptionsSelectorEnabled && renderExpandButton() }
         </form>
         </form>
       </div>
       </div>
-      {/* Collapsed OptionsSelector */}
-      { isCollapsedOptionsSelectorEnabled && (
-        <Collapse isOpen={isExpanded}>
-          <div className="px-2"> {/* set padding for border-top */}
-            <div className={`navbar navbar-expand border-top px-0 ${moduleClass}`}>
-              <form className="ms-auto">
-                <OptionsSelector />
-              </form>
-            </div>
-          </div>
-        </Collapse>
-      ) }
     </div>
     </div>
   );
   );
 };
 };

+ 211 - 170
apps/app/src/components/PageEditor/OptionsSelector.tsx

@@ -2,67 +2,114 @@ import React, {
   memo, useCallback, useMemo, useState,
   memo, useCallback, useMemo, useState,
 } from 'react';
 } from 'react';
 
 
+import type {
+  EditorTheme, KeyMapMode,
+} from '@growi/editor';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
+import Image from 'next/image';
 import {
 import {
-  Dropdown, DropdownToggle, DropdownMenu, DropdownItem,
+  Dropdown, DropdownToggle, DropdownMenu, Input, FormGroup,
 } from 'reactstrap';
 } from 'reactstrap';
 
 
 import { useIsIndentSizeForced } from '~/stores/context';
 import { useIsIndentSizeForced } from '~/stores/context';
 import { useEditorSettings, useCurrentIndentSize } from '~/stores/editor';
 import { useEditorSettings, useCurrentIndentSize } from '~/stores/editor';
 
 
-import { DEFAULT_THEME, type KeyMapMode } from '../../interfaces/editor-settings';
+import {
+  DEFAULT_THEME, DEFAULT_KEYMAP,
+} from '../../interfaces/editor-settings';
 
 
 
 
-const AVAILABLE_THEMES = [
-  'DefaultLight', 'Eclipse', 'Basic', 'Ayu', 'Rosé Pine', 'DefaultDark', 'Material', 'Nord', 'Cobalt', 'Kimbie',
-];
+type RadioListItemProps = {
+  onClick: () => void,
+  icon?: React.ReactNode,
+  text: string,
+  checked?: boolean
+}
 
 
-const TYPICAL_INDENT_SIZE = [2, 4];
+const RadioListItem = (props: RadioListItemProps): JSX.Element => {
+  const {
+    onClick, icon, text, checked,
+  } = props;
+  return (
+    <li className="list-group-item border-0 d-flex align-items-center">
+      <input
+        onClick={onClick}
+        className="form-check-input me-3"
+        type="radio"
+        name="listGroupRadio"
+        id={`editor_config_radio_item_${text}`}
+        checked={checked}
+      />
+      {icon}
+      <label className="form-check-label stretched-link fs-6" htmlFor={`editor_config_radio_item_${text}`}>{text}</label>
+    </li>
+  );
+};
+
+
+type SelectorProps = {
+  header: string,
+  onClickBefore: () => void,
+  items: JSX.Element,
+}
 
 
+const Selector = (props: SelectorProps): JSX.Element => {
+
+  const { header, onClickBefore, items } = props;
+  return (
+    <div className="d-flex flex-column w-100">
+      <button type="button" className="btn border-0 d-flex align-items-center text-muted ms-2" onClick={onClickBefore}>
+        <span className="material-symbols-outlined fs-5 py-0 me-1">navigate_before</span>
+        <label>{header}</label>
+      </button>
+      <hr className="my-1" />
+      <ul className="list-group d-flex ms-2">
+        { items }
+      </ul>
+    </div>
+  );
+
+};
+
+
+type EditorThemeToLabel = {
+  [key in EditorTheme]: string;
+}
 
 
-const ThemeSelector = (): JSX.Element => {
+const EDITORTHEME_LABEL_MAP: EditorThemeToLabel = {
+  defaultlight: 'DefaultLight',
+  eclipse: 'Eclipse',
+  basic: 'Basic',
+  ayu: 'Ayu',
+  rosepine: 'Rosé Pine',
+  defaultdark: 'DefaultDark',
+  material: 'Material',
+  nord: 'Nord',
+  cobalt: 'Cobalt',
+  kimbie: 'Kimbie',
+};
 
 
-  const [isThemeMenuOpened, setIsThemeMenuOpened] = useState(false);
+const ThemeSelector = memo(({ onClickBefore }: {onClickBefore: () => void}): JSX.Element => {
 
 
   const { data: editorSettings, update } = useEditorSettings();
   const { data: editorSettings, update } = useEditorSettings();
+  const selectedTheme = editorSettings?.theme ?? DEFAULT_THEME;
 
 
-  const menuItems = useMemo(() => (
+  const listItems = useMemo(() => (
     <>
     <>
-      { AVAILABLE_THEMES.map((theme) => {
+      { (Object.keys(EDITORTHEME_LABEL_MAP) as EditorTheme[]).map((theme) => {
+        const themeLabel = EDITORTHEME_LABEL_MAP[theme];
         return (
         return (
-          <DropdownItem className="menuitem-label" onClick={() => update({ theme })}>
-            {theme}
-          </DropdownItem>
+          <RadioListItem onClick={() => update({ theme })} text={themeLabel} checked={theme === selectedTheme} />
         );
         );
       }) }
       }) }
     </>
     </>
-  ), [update]);
-
-  const selectedTheme = editorSettings?.theme ?? DEFAULT_THEME;
+  ), [update, selectedTheme]);
 
 
   return (
   return (
-    <div className="input-group flex-nowrap">
-      <div>
-        <span className="input-group-text" id="igt-theme">Theme</span>
-      </div>
-
-      <Dropdown
-        direction="up"
-        isOpen={isThemeMenuOpened}
-        toggle={() => setIsThemeMenuOpened(!isThemeMenuOpened)}
-      >
-        <DropdownToggle color="outline-secondary" caret>
-          {selectedTheme}
-        </DropdownToggle>
-
-        <DropdownMenu container="body">
-          {menuItems}
-        </DropdownMenu>
-
-      </Dropdown>
-    </div>
+    <Selector header="Theme" onClickBefore={onClickBefore} items={listItems} />
   );
   );
-};
+});
+ThemeSelector.displayName = 'ThemeSelector';
 
 
 
 
 type KeyMapModeToLabel = {
 type KeyMapModeToLabel = {
@@ -76,105 +123,74 @@ const KEYMAP_LABEL_MAP: KeyMapModeToLabel = {
   vscode: 'Visual Studio Code',
   vscode: 'Visual Studio Code',
 };
 };
 
 
-const KeymapSelector = memo((): JSX.Element => {
-
-  const [isKeyMenuOpened, setIsKeyMenuOpened] = useState(false);
+const KeymapSelector = memo(({ onClickBefore }: {onClickBefore: () => void}): JSX.Element => {
 
 
   const { data: editorSettings, update } = useEditorSettings();
   const { data: editorSettings, update } = useEditorSettings();
+  const selectedKeymapMode = editorSettings?.keymapMode ?? DEFAULT_KEYMAP;
 
 
-  const menuItems = useMemo(() => (
+  const listItems = useMemo(() => (
     <>
     <>
       { (Object.keys(KEYMAP_LABEL_MAP) as KeyMapMode[]).map((keymapMode) => {
       { (Object.keys(KEYMAP_LABEL_MAP) as KeyMapMode[]).map((keymapMode) => {
         const keymapLabel = KEYMAP_LABEL_MAP[keymapMode];
         const keymapLabel = KEYMAP_LABEL_MAP[keymapMode];
         const icon = (keymapMode !== 'default')
         const icon = (keymapMode !== 'default')
-          ? <img src={`/images/icons/${keymapMode}.png`} width="16px" className="me-2"></img>
+          ? <Image src={`/images/icons/${keymapMode}.png`} width={16} height={16} className="me-2" alt={keymapMode} />
           : null;
           : null;
         return (
         return (
-          <DropdownItem className="menuitem-label" onClick={() => update({ keymapMode })}>
-            {icon}{keymapLabel}
-          </DropdownItem>
+          <RadioListItem onClick={() => update({ keymapMode })} icon={icon} text={keymapLabel} checked={keymapMode === selectedKeymapMode} />
         );
         );
       }) }
       }) }
     </>
     </>
-  ), [update]);
+  ), [update, selectedKeymapMode]);
 
 
-  const selectedKeymapMode = editorSettings?.keymapMode ?? 'default';
 
 
   return (
   return (
-    <div className="input-group flex-nowrap">
-      <span className="input-group-text" id="igt-keymap">Keymap</span>
-      <Dropdown
-        direction="up"
-        isOpen={isKeyMenuOpened}
-        toggle={() => setIsKeyMenuOpened(!isKeyMenuOpened)}
-      >
-        <DropdownToggle color="outline-secondary" caret>
-          {selectedKeymapMode}
-        </DropdownToggle>
-
-        <DropdownMenu container="body">
-          {menuItems}
-        </DropdownMenu>
-
-      </Dropdown>
-    </div>
+    <Selector header="Keymap" onClickBefore={onClickBefore} items={listItems} />
   );
   );
-
 });
 });
-
 KeymapSelector.displayName = 'KeymapSelector';
 KeymapSelector.displayName = 'KeymapSelector';
 
 
-type IndentSizeSelectorProps = {
-  isIndentSizeForced: boolean,
-  selectedIndentSize: number,
-  onChange: (indentSize: number) => void,
-}
 
 
-const IndentSizeSelector = memo(({ isIndentSizeForced, selectedIndentSize, onChange }: IndentSizeSelectorProps): JSX.Element => {
+const TYPICAL_INDENT_SIZE = [2, 4];
+
+const IndentSizeSelector = memo(({ onClickBefore }: {onClickBefore: () => void}): JSX.Element => {
 
 
-  const [isIndentMenuOpened, setIsIndentMenuOpened] = useState(false);
+  const { data: currentIndentSize, mutate: mutateCurrentIndentSize } = useCurrentIndentSize();
 
 
-  const menuItems = useMemo(() => (
+  const listItems = useMemo(() => (
     <>
     <>
       { TYPICAL_INDENT_SIZE.map((indent) => {
       { TYPICAL_INDENT_SIZE.map((indent) => {
         return (
         return (
-          <DropdownItem className="menuitem-label" onClick={() => onChange(indent)}>
-            {indent}
-          </DropdownItem>
+          <RadioListItem onClick={() => mutateCurrentIndentSize(indent)} text={indent.toString()} checked={indent === currentIndentSize} />
         );
         );
       }) }
       }) }
     </>
     </>
-  ), [onChange]);
+  ), [currentIndentSize, mutateCurrentIndentSize]);
 
 
   return (
   return (
-    <div className="input-group flex-nowrap">
-      <span className="input-group-text" id="igt-indent">Indent</span>
-      <Dropdown
-        direction="up"
-        isOpen={isIndentMenuOpened}
-        toggle={() => setIsIndentMenuOpened(!isIndentMenuOpened)}
-        disabled={isIndentSizeForced}
-      >
-        <DropdownToggle color="outline-secondary" caret>
-          {selectedIndentSize}
-        </DropdownToggle>
-
-        <DropdownMenu container="body">
-          {menuItems}
-        </DropdownMenu>
-
-      </Dropdown>
-    </div>
+    <Selector header="Indent" onClickBefore={onClickBefore} items={listItems} />
   );
   );
 });
 });
-
 IndentSizeSelector.displayName = 'IndentSizeSelector';
 IndentSizeSelector.displayName = 'IndentSizeSelector';
 
 
 
 
-const ConfigurationDropdown = memo((): JSX.Element => {
-  const { t } = useTranslation();
+type SwitchItemProps = {
+  onClick: () => void,
+  checked: boolean,
+  text: string,
+};
+const SwitchItem = memo((props: SwitchItemProps): JSX.Element => {
+  const { onClick, checked, text } = props;
+  return (
+    <FormGroup switch>
+      <Input type="switch" checked={checked} onClick={onClick} />
+      <label>{text}</label>
+    </FormGroup>
 
 
-  const [isCddMenuOpened, setCddMenuOpened] = useState(false);
+  );
+});
+
+const ConfigurationSelector = memo((): JSX.Element => {
+  const { t } = useTranslation();
 
 
   const { data: editorSettings, update } = useEditorSettings();
   const { data: editorSettings, update } = useEditorSettings();
 
 
@@ -185,20 +201,8 @@ const ConfigurationDropdown = memo((): JSX.Element => {
 
 
     const isActive = editorSettings.styleActiveLine;
     const isActive = editorSettings.styleActiveLine;
 
 
-    const iconClasses = ['text-info'];
-    if (isActive) {
-      iconClasses.push('ti ti-check');
-    }
-    const iconClassName = iconClasses.join(' ');
-
     return (
     return (
-      <DropdownItem toggle={false} onClick={() => update({ styleActiveLine: !isActive })}>
-        <div className="d-flex justify-content-between">
-          <span className="icon-container"></span>
-          <span className="menuitem-label">{ t('page_edit.Show active line') }</span>
-          <span className="icon-container"><i className={iconClassName}></i></span>
-        </div>
-      </DropdownItem>
+      <SwitchItem onClick={() => update({ styleActiveLine: !isActive })} checked={isActive} text={t('page_edit.Show active line')} />
     );
     );
   }, [editorSettings, update, t]);
   }, [editorSettings, update, t]);
 
 
@@ -209,81 +213,118 @@ const ConfigurationDropdown = memo((): JSX.Element => {
 
 
     const isActive = editorSettings.autoFormatMarkdownTable;
     const isActive = editorSettings.autoFormatMarkdownTable;
 
 
-    const iconClasses = ['text-info'];
-    if (isActive) {
-      iconClasses.push('ti ti-check');
-    }
-    const iconClassName = iconClasses.join(' ');
-
     return (
     return (
-      <DropdownItem toggle={false} onClick={() => update({ autoFormatMarkdownTable: !isActive })}>
-        <div className="d-flex justify-content-between">
-          <span className="icon-container"></span>
-          <span className="menuitem-label">{ t('page_edit.auto_format_table') }</span>
-          <span className="icon-container"><i className={iconClassName}></i></span>
-        </div>
-      </DropdownItem>
+      <SwitchItem onClick={() => update({ autoFormatMarkdownTable: !isActive })} checked={isActive} text={t('page_edit.auto_format_table')} />
     );
     );
   }, [editorSettings, t, update]);
   }, [editorSettings, t, update]);
 
 
   return (
   return (
-    <div className="my-0">
-      <Dropdown
-        direction="up"
-        className="grw-editor-configuration-dropdown"
-        isOpen={isCddMenuOpened}
-        toggle={() => setCddMenuOpened(!isCddMenuOpened)}
-      >
-
-        <DropdownToggle color="outline-secondary" caret>
-          <span className="material-symbols-outlined">settings</span>
-        </DropdownToggle>
-
-        <DropdownMenu container="body">
-          {renderActiveLineMenuItem()}
-          {renderMarkdownTableAutoFormattingMenuItem()}
-          {/* <DropdownItem divider /> */}
-        </DropdownMenu>
-
-      </Dropdown>
+    <div className="mx-3 mt-1">
+      {renderActiveLineMenuItem()}
+      {renderMarkdownTableAutoFormattingMenuItem()}
     </div>
     </div>
   );
   );
+});
+ConfigurationSelector.displayName = 'ConfigurationSelector';
+
 
 
+type ChangeStateButtonProps = {
+  onClick: () => void,
+  header: string,
+  data: string,
+  disabled?: boolean,
+}
+const ChangeStateButton = memo((props: ChangeStateButtonProps): JSX.Element => {
+  const {
+    onClick, header, data, disabled,
+  } = props;
+  return (
+    <button type="button" className="d-flex align-items-center btn btn-sm border-0 my-1" disabled={disabled} onClick={onClick}>
+      <label className="ms-2 me-auto">{header}</label>
+      <label className="text-muted d-flex align-items-center ms-2 me-1">
+        {data}
+        <span className="material-symbols-outlined fs-5 py-0">navigate_next</span>
+      </label>
+    </button>
+  );
 });
 });
 
 
-ConfigurationDropdown.displayName = 'ConfigurationDropdown';
 
 
+const OptionsStatus = {
+  Home: 'Home',
+  Theme: 'Theme',
+  Keymap: 'Keymap',
+  Indent: 'Indent',
+} as const;
+type OptionStatus = typeof OptionsStatus[keyof typeof OptionsStatus];
+
+export const OptionsSelector = ({ collapsed }: {collapsed?: boolean}): JSX.Element => {
 
 
-export const OptionsSelector = (): JSX.Element => {
+  const [dropdownOpen, setDropdownOpen] = useState(false);
+
+  const [status, setStatus] = useState<OptionStatus>(OptionsStatus.Home);
   const { data: editorSettings } = useEditorSettings();
   const { data: editorSettings } = useEditorSettings();
+  const { data: currentIndentSize } = useCurrentIndentSize();
   const { data: isIndentSizeForced } = useIsIndentSizeForced();
   const { data: isIndentSizeForced } = useIsIndentSizeForced();
-  const { data: currentIndentSize, mutate: mutateCurrentIndentSize } = useCurrentIndentSize();
 
 
-  if (editorSettings == null || isIndentSizeForced == null || currentIndentSize == null) {
+  if (editorSettings == null || currentIndentSize == null || isIndentSizeForced == null) {
     return <></>;
     return <></>;
   }
   }
 
 
   return (
   return (
-    <>
-      <div className="d-flex flex-row zindex-dropdown">
-        <span>
-          <ThemeSelector />
-        </span>
-        <span className="d-none d-sm-block ms-2 ms-sm-4">
-          <KeymapSelector />
-        </span>
-        <span className="ms-2 ms-sm-4">
-          <IndentSizeSelector
-            isIndentSizeForced={isIndentSizeForced}
-            selectedIndentSize={currentIndentSize}
-            onChange={newValue => mutateCurrentIndentSize(newValue)}
-          />
-        </span>
-        <span className="ms-2 ms-sm-4">
-          <ConfigurationDropdown />
-        </span>
-      </div>
-    </>
+    <Dropdown isOpen={dropdownOpen} toggle={() => { setStatus(OptionsStatus.Home); setDropdownOpen(!dropdownOpen) }} direction="up" className="">
+      <DropdownToggle
+        className={`btn btn-outline-neutral-secondary d-flex align-items-center justify-content-center p-1 m-1
+              ${collapsed ? 'border-0' : 'border border-secondary'}
+              ${dropdownOpen ? 'active' : ''}
+              `}
+      >
+        <span className="material-symbols-outlined py-0 fs-5"> settings </span>
+        {
+          collapsed ? <></>
+            : <label className="ms-1 me-1">Editor Config</label>
+        }
+      </DropdownToggle>
+      <DropdownMenu container="body">
+        {
+          status === OptionsStatus.Home && (
+            <div className="d-flex flex-column">
+              <label className="text-muted ms-3">
+                Editor Config
+              </label>
+              <hr className="my-1" />
+              <ChangeStateButton onClick={() => setStatus(OptionsStatus.Theme)} header="Theme" data={EDITORTHEME_LABEL_MAP[editorSettings.theme ?? ''] ?? ''} />
+              <hr className="my-1" />
+              <ChangeStateButton
+                onClick={() => setStatus(OptionsStatus.Keymap)}
+                header="Keymap"
+                data={KEYMAP_LABEL_MAP[editorSettings.keymapMode ?? ''] ?? ''}
+              />
+              <hr className="my-1" />
+              <ChangeStateButton
+                disabled={isIndentSizeForced}
+                onClick={() => setStatus(OptionsStatus.Indent)}
+                header="Indent"
+                data={currentIndentSize.toString() ?? ''}
+              />
+              <hr className="my-1" />
+              <ConfigurationSelector />
+            </div>
+          )
+        }
+        { status === OptionsStatus.Theme && (
+          <ThemeSelector onClickBefore={() => setStatus(OptionsStatus.Home)} />
+        )
+        }
+        { status === OptionsStatus.Keymap && (
+          <KeymapSelector onClickBefore={() => setStatus(OptionsStatus.Home)} />
+        )
+        }
+        { status === OptionsStatus.Indent && (
+          <IndentSizeSelector onClickBefore={() => setStatus(OptionsStatus.Home)} />
+        )
+        }
+      </DropdownMenu>
+    </Dropdown>
   );
   );
-
 };
 };

+ 2 - 14
apps/app/src/components/PageEditor/PageEditor.tsx

@@ -133,13 +133,7 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
   const { resolvedTheme } = useNextThemes();
   const { resolvedTheme } = useNextThemes();
   mutateResolvedTheme({ themeData: resolvedTheme });
   mutateResolvedTheme({ themeData: resolvedTheme });
 
 
-  // TODO: remove workaround
-  // for https://redmine.weseek.co.jp/issues/125923
-  const [createdPageRevisionIdWithAttachment, setCreatedPageRevisionIdWithAttachment] = useState();
-
-  // TODO: remove workaround
-  // for https://redmine.weseek.co.jp/issues/125923
-  const currentRevisionId = currentPage?.revision?._id ?? createdPageRevisionIdWithAttachment;
+  const currentRevisionId = currentPage?.revision?._id;
 
 
   const initialValueRef = useRef('');
   const initialValueRef = useRef('');
   const initialValue = useMemo(() => {
   const initialValue = useMemo(() => {
@@ -194,12 +188,6 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
 
 
   }, [markdownToPreview, mutateIsConflict]);
   }, [markdownToPreview, mutateIsConflict]);
 
 
-  // TODO: remove workaround
-  // for https://redmine.weseek.co.jp/issues/125923
-  useEffect(() => {
-    setCreatedPageRevisionIdWithAttachment(undefined);
-  }, [router]);
-
   useEffect(() => {
   useEffect(() => {
     if (socket == null) { return }
     if (socket == null) { return }
 
 
@@ -359,7 +347,7 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
     mutateIsConflict(false);
     mutateIsConflict(false);
 
 
     // set resolved markdown in editing markdown
     // set resolved markdown in editing markdown
-    const markdown = pageData?.revision.body ?? '';
+    const markdown = pageData?.revision?.body ?? '';
     mutateEditingMarkdown(markdown);
     mutateEditingMarkdown(markdown);
 
 
   }, [mutateCurrentPage, mutateEditingMarkdown, mutateIsConflict, mutateTagsInfo, syncTagsInfoForEditor]);
   }, [mutateCurrentPage, mutateEditingMarkdown, mutateIsConflict, mutateTagsInfo, syncTagsInfoForEditor]);

+ 1 - 1
apps/app/src/components/PageEditor/page-path-rename-utils.ts

@@ -40,7 +40,7 @@ export const usePagePathRenameHandler = (
     try {
     try {
       await apiv3Put('/pages/rename', {
       await apiv3Put('/pages/rename', {
         pageId: currentPage._id,
         pageId: currentPage._id,
-        revisionId: currentPage.revision._id,
+        revisionId: currentPage.revision?._id,
         newPagePath,
         newPagePath,
       });
       });
 
 

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

@@ -60,7 +60,7 @@ const PagePresentationModal = (): JSX.Element => {
     return <></>;
     return <></>;
   }
   }
 
 
-  const markdown = currentPage?.revision.body;
+  const markdown = currentPage?.revision?.body;
 
 
   return (
   return (
     <Modal
     <Modal

+ 8 - 5
apps/app/src/components/PageSideContents/PageSideContents.tsx

@@ -1,6 +1,7 @@
 import React, { Suspense, useCallback } from 'react';
 import React, { Suspense, useCallback } from 'react';
 
 
-import { getIdForRef, type IPageHasId, type IPageInfoForOperation } from '@growi/core';
+import type { IPagePopulatedToShowRevision } from '@growi/core';
+import { getIdForRef, type IPageInfoForOperation } from '@growi/core';
 import { pagePathUtils } from '@growi/core/dist/utils';
 import { pagePathUtils } from '@growi/core/dist/utils';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import dynamic from 'next/dynamic';
@@ -70,7 +71,7 @@ const Tags = (props: TagsProps): JSX.Element => {
 
 
 
 
 export type PageSideContentsProps = {
 export type PageSideContentsProps = {
-  page: IPageHasId,
+  page: IPagePopulatedToShowRevision,
   isSharedUser?: boolean,
   isSharedUser?: boolean,
 }
 }
 
 
@@ -91,9 +92,11 @@ export const PageSideContents = (props: PageSideContentsProps): JSX.Element => {
   return (
   return (
     <>
     <>
       {/* Tags */}
       {/* Tags */}
-      <Suspense fallback={<PageTagsSkeleton />}>
-        <Tags pageId={page._id} revisionId={getIdForRef(page.revision)} />
-      </Suspense>
+      { page.revision != null && (
+        <Suspense fallback={<PageTagsSkeleton />}>
+          <Tags pageId={page._id} revisionId={page.revision._id} />
+        </Suspense>
+      ) }
 
 
       <div className={`${styles['grw-page-accessories-controls']} d-flex flex-column gap-2`}>
       <div className={`${styles['grw-page-accessories-controls']} d-flex flex-column gap-2`}>
         {/* Page list */}
         {/* Page list */}

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

@@ -38,7 +38,7 @@ export const PageStatusAlert = (): JSX.Element => {
 
 
   const refreshPage = useCallback(async() => {
   const refreshPage = useCallback(async() => {
     const updatedPageData = await mutatePageData();
     const updatedPageData = await mutatePageData();
-    mutateEditingMarkdown(updatedPageData?.revision.body);
+    mutateEditingMarkdown(updatedPageData?.revision?.body);
   }, [mutateEditingMarkdown, mutatePageData]);
   }, [mutateEditingMarkdown, mutatePageData]);
 
 
   const onClickResolveConflict = useCallback(() => {
   const onClickResolveConflict = useCallback(() => {

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

@@ -30,7 +30,7 @@ const TimelineCard = ({ page }: TimelineCardProps): JSX.Element => {
         </Link>
         </Link>
       </div>
       </div>
       <div className="card-body">
       <div className="card-body">
-        { rendererOptions != null && (
+        { rendererOptions != null && page.revision != null && (
           <RevisionLoader
           <RevisionLoader
             rendererOptions={rendererOptions}
             rendererOptions={rendererOptions}
             pageId={page._id}
             pageId={page._id}

+ 8 - 17
apps/app/src/components/SearchPage.tsx

@@ -28,8 +28,6 @@ const INITIAL_PAGIONG_SIZE = 20;
 
 
 type SearchResultListHeadProps = {
 type SearchResultListHeadProps = {
   searchResult: IFormattedSearchResult,
   searchResult: IFormattedSearchResult,
-  searchingKeyword: string,
-  offset: number,
   pagingSize: number,
   pagingSize: number,
   onPagingSizeChanged: (size: number) => void,
   onPagingSizeChanged: (size: number) => void,
 }
 }
@@ -38,13 +36,10 @@ const SearchResultListHead = React.memo((props: SearchResultListHeadProps): JSX.
   const { t } = useTranslation();
   const { t } = useTranslation();
 
 
   const {
   const {
-    searchResult, searchingKeyword, offset, pagingSize,
-    onPagingSizeChanged,
+    searchResult, // pagingSize, onPagingSizeChanged,
   } = props;
   } = props;
 
 
-  const { took, total, hitsCount } = searchResult.meta;
-  const leftNum = offset + 1;
-  const rightNum = offset + hitsCount;
+  const { took, total } = searchResult.meta;
 
 
   if (total === 0) {
   if (total === 0) {
     return (
     return (
@@ -57,15 +52,14 @@ const SearchResultListHead = React.memo((props: SearchResultListHeadProps): JSX.
   return (
   return (
     <div className="d-flex align-items-center justify-content-between">
     <div className="d-flex align-items-center justify-content-between">
       <div className="text-nowrap">
       <div className="text-nowrap">
-        {t('search_result.result_meta')}
-        <span className="search-result-keyword ms-2">{`${searchingKeyword}`}</span>
-        <span className="ms-3">{`${leftNum}-${rightNum}`} / {total}</span>
+        <span className="ms-3 fw-bold">{total} {t('search_result.hit_number_unit', 'hit')}</span>
         { took != null && (
         { took != null && (
           // blackout 70px rectangle in VRT
           // blackout 70px rectangle in VRT
           <span data-vrt-blackout className="ms-3 text-muted d-inline-block" style={{ minWidth: '70px' }}>({took}ms)</span>
           <span data-vrt-blackout className="ms-3 text-muted d-inline-block" style={{ minWidth: '70px' }}>({took}ms)</span>
         ) }
         ) }
       </div>
       </div>
-      <div className="input-group flex-nowrap search-result-select-group ms-auto d-md-flex d-none">
+      {/* TODO: infinite scroll for search result */}
+      {/* <div className="input-group flex-nowrap search-result-select-group ms-auto d-md-flex d-none">
         <div>
         <div>
           <label className="form-label input-group-text text-muted" htmlFor="inputGroupSelect01">{t('search_result.number_of_list_to_display')}</label>
           <label className="form-label input-group-text text-muted" htmlFor="inputGroupSelect01">{t('search_result.number_of_list_to_display')}</label>
         </div>
         </div>
@@ -79,7 +73,7 @@ const SearchResultListHead = React.memo((props: SearchResultListHeadProps): JSX.
             return <option key={limit} value={limit}>{limit} {t('search_result.page_number_unit')}</option>;
             return <option key={limit} value={limit}>{limit} {t('search_result.page_number_unit')}</option>;
           })}
           })}
         </select>
         </select>
-      </div>
+      </div> */}
     </div>
     </div>
   );
   );
 });
 });
@@ -185,11 +179,10 @@ export const SearchPage = (): JSX.Element => {
           >
           >
             <button
             <button
               type="button"
               type="button"
-              className="btn btn-outline-danger text-nowrap border-0 px-2"
+              className="btn border-0 text-danger"
               disabled={isDisabled}
               disabled={isDisabled}
               onClick={deleteAllButtonClickedHandler}
               onClick={deleteAllButtonClickedHandler}
             >
             >
-              <span className="material-symbols-outlined">delete</span>
               {t('search_result.delete_all_selected_page')}
               {t('search_result.delete_all_selected_page')}
             </button>
             </button>
           </OperateAllControl>
           </OperateAllControl>
@@ -221,13 +214,11 @@ export const SearchPage = (): JSX.Element => {
     return (
     return (
       <SearchResultListHead
       <SearchResultListHead
         searchResult={data}
         searchResult={data}
-        searchingKeyword={keyword ?? ''}
-        offset={offset}
         pagingSize={limit}
         pagingSize={limit}
         onPagingSizeChanged={pagingSizeChangedHandler}
         onPagingSizeChanged={pagingSizeChangedHandler}
       />
       />
     );
     );
-  }, [data, keyword, limit, offset, pagingSizeChangedHandler]);
+  }, [data, limit, pagingSizeChangedHandler]);
 
 
   const searchPager = useMemo(() => {
   const searchPager = useMemo(() => {
     // when pager is not needed
     // when pager is not needed

+ 5 - 5
apps/app/src/components/SearchPage/OperateAllControl.tsx

@@ -1,11 +1,10 @@
-import React, {
-  ChangeEvent, forwardRef, ForwardRefRenderFunction, useImperativeHandle, useRef,
-} from 'react';
+import type { ChangeEvent, ForwardRefRenderFunction } from 'react';
+import React, { forwardRef, useImperativeHandle, useRef } from 'react';
 
 
 import { Input } from 'reactstrap';
 import { Input } from 'reactstrap';
 
 
-import { ISelectableAndIndeterminatable } from '~/client/interfaces/selectable-all';
-import { IndeterminateInputElement } from '~/interfaces/indeterminate-input-elm';
+import type { ISelectableAndIndeterminatable } from '~/client/interfaces/selectable-all';
+import type { IndeterminateInputElement } from '~/interfaces/indeterminate-input-elm';
 
 
 type Props = {
 type Props = {
   isCheckboxDisabled?: boolean,
   isCheckboxDisabled?: boolean,
@@ -58,6 +57,7 @@ const OperateAllControlSubstance: ForwardRefRenderFunction<ISelectableAndIndeter
         type="checkbox"
         type="checkbox"
         id="cb-check-all"
         id="cb-check-all"
         data-testid="cb-select-all"
         data-testid="cb-select-all"
+        className="ms-2"
         innerRef={selectAllCheckboxElm}
         innerRef={selectAllCheckboxElm}
         disabled={isCheckboxDisabled}
         disabled={isCheckboxDisabled}
         onChange={checkboxChangedHandler}
         onChange={checkboxChangedHandler}

+ 9 - 0
apps/app/src/components/SearchPage/SearchControl.module.scss

@@ -0,0 +1,9 @@
+
+@use '@growi/core/scss/bootstrap/init' as bs;
+
+@use '@growi/ui/scss/atoms/btn-muted';
+
+// == Colors
+.btn-delete {
+  @include btn-muted.colorize(bs.$red);
+}

+ 4 - 3
apps/app/src/components/SearchPage/SearchOptionModal.tsx

@@ -1,4 +1,5 @@
-import React, { FC } from 'react';
+import type { FC } from 'react';
+import React from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import {
 import {
@@ -51,7 +52,7 @@ const SearchOptionModal: FC<Props> = (props: Props) => {
       </ModalHeader>
       </ModalHeader>
       <ModalBody>
       <ModalBody>
         <div className="d-flex p-2">
         <div className="d-flex p-2">
-          <div className="border border-gray me-3">
+          <div className="me-3">
             <label className="form-label px-3 py-2 mb-0 d-flex align-items-center">
             <label className="form-label px-3 py-2 mb-0 d-flex align-items-center">
               <input
               <input
                 className="me-2"
                 className="me-2"
@@ -62,7 +63,7 @@ const SearchOptionModal: FC<Props> = (props: Props) => {
               {t('Include Subordinated Target Page', { target: '/user' })}
               {t('Include Subordinated Target Page', { target: '/user' })}
             </label>
             </label>
           </div>
           </div>
-          <div className="border border-gray">
+          <div className="">
             <label className="form-label px-3 py-2 mb-0 d-flex align-items-center">
             <label className="form-label px-3 py-2 mb-0 d-flex align-items-center">
               <input
               <input
                 className="me-2"
                 className="me-2"

+ 15 - 17
apps/app/src/components/SearchPage/SearchResultContent.tsx

@@ -119,7 +119,7 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
 
 
   const { t } = useTranslation();
   const { t } = useTranslation();
 
 
-  const page = pageWithMeta?.data;
+  const page = pageWithMeta.data;
   const { open: openDuplicateModal } = usePageDuplicateModal();
   const { open: openDuplicateModal } = usePageDuplicateModal();
   const { open: openRenameModal } = usePageRenameModal();
   const { open: openRenameModal } = usePageRenameModal();
   const { open: openDeleteModal } = usePageDeleteModal();
   const { open: openDeleteModal } = usePageDeleteModal();
@@ -183,7 +183,10 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
       return <></>;
       return <></>;
     }
     }
 
 
-    const revisionId = getIdForRef(page.revision);
+    const revisionId = page.revision != null ? getIdForRef(page.revision) : null;
+    const additionalMenuItemRenderer = revisionId != null
+      ? props => <AdditionalMenuItems {...props} pageId={page._id} revisionId={revisionId} />
+      : undefined;
 
 
     return (
     return (
       <div className="d-flex flex-column align-items-end justify-content-center px-2 py-1">
       <div className="d-flex flex-column align-items-end justify-content-center px-2 py-1">
@@ -194,7 +197,7 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
           expandContentWidth={shouldExpandContent}
           expandContentWidth={shouldExpandContent}
           showPageControlDropdown={showPageControlDropdown}
           showPageControlDropdown={showPageControlDropdown}
           forceHideMenuItems={forceHideMenuItems}
           forceHideMenuItems={forceHideMenuItems}
-          additionalMenuItemRenderer={props => <AdditionalMenuItems {...props} pageId={page._id} revisionId={revisionId} />}
+          additionalMenuItemRenderer={additionalMenuItemRenderer}
           onClickDuplicateMenuItem={duplicateItemClickedHandler}
           onClickDuplicateMenuItem={duplicateItemClickedHandler}
           onClickRenameMenuItem={renameItemClickedHandler}
           onClickRenameMenuItem={renameItemClickedHandler}
           onClickDeleteMenuItem={deleteItemClickedHandler}
           onClickDeleteMenuItem={deleteItemClickedHandler}
@@ -205,8 +208,6 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
   }, [page, shouldExpandContent, showPageControlDropdown, forceHideMenuItems,
   }, [page, shouldExpandContent, showPageControlDropdown, forceHideMenuItems,
       duplicateItemClickedHandler, renameItemClickedHandler, deleteItemClickedHandler, switchContentWidthHandler]);
       duplicateItemClickedHandler, renameItemClickedHandler, deleteItemClickedHandler, switchContentWidthHandler]);
 
 
-  const isRenderable = page != null && rendererOptions != null;
-
   const fluidLayoutClass = shouldExpandContent ? _fluidLayoutClass : '';
   const fluidLayoutClass = shouldExpandContent ? _fluidLayoutClass : '';
 
 
   return (
   return (
@@ -217,25 +218,23 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
     >
     >
       <RightComponent />
       <RightComponent />
 
 
-      { isRenderable && (
-        <div className="container-lg grw-container-convertible pt-2 pb-2">
-          <PagePathNav pageId={page._id} pagePath={page.path} formerLinkClassName="small" latterLinkClassName="fs-3" />
-        </div>
-      ) }
+      <div className="container-lg grw-container-convertible pt-2 pb-2">
+        <PagePathNav pageId={page._id} pagePath={page.path} formerLinkClassName="small" latterLinkClassName="fs-3" />
+      </div>
 
 
       <div
       <div
         id="search-result-content-body-container"
         id="search-result-content-body-container"
         ref={scrollElementRef}
         ref={scrollElementRef}
         className="search-result-content-body-container container-lg grw-container-convertible overflow-y-scroll"
         className="search-result-content-body-container container-lg grw-container-convertible overflow-y-scroll"
       >
       >
-        { isRenderable && (
+        { page.revision != null && rendererOptions != null && (
           <RevisionLoader
           <RevisionLoader
             rendererOptions={rendererOptions}
             rendererOptions={rendererOptions}
             pageId={page._id}
             pageId={page._id}
             revisionId={page.revision}
             revisionId={page.revision}
           />
           />
         )}
         )}
-        { isRenderable && (
+        { page.revision != null && (
           <PageComment
           <PageComment
             rendererOptions={rendererOptions}
             rendererOptions={rendererOptions}
             pageId={page._id}
             pageId={page._id}
@@ -245,11 +244,10 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
             isReadOnly
             isReadOnly
           />
           />
         )}
         )}
-        { isRenderable && (
-          <PageContentFooter
-            page={page}
-          />
-        )}
+
+        <PageContentFooter
+          page={page}
+        />
       </div>
       </div>
     </div>
     </div>
   );
   );

+ 3 - 0
apps/app/src/components/SearchPage/SortControl.module.scss

@@ -0,0 +1,3 @@
+.sort-control {
+  min-width: 180px;
+}

+ 32 - 35
apps/app/src/components/SearchPage/SortControl.tsx

@@ -1,7 +1,12 @@
-import React, { FC } from 'react';
+import type { FC } from 'react';
+import React from 'react';
+
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
+
 import { SORT_AXIS, SORT_ORDER } from '../../interfaces/search';
 import { SORT_AXIS, SORT_ORDER } from '../../interfaces/search';
 
 
+import styles from './SortControl.module.scss';
+
 const { DESC, ASC } = SORT_ORDER;
 const { DESC, ASC } = SORT_ORDER;
 
 
 type Props = {
 type Props = {
@@ -22,43 +27,35 @@ const SortControl: FC <Props> = (props: Props) => {
     }
     }
   };
   };
 
 
-  const renderOrderIcon = () => {
-    const iconClassName = ASC === order ? 'fa fa-sort-amount-asc' : 'fa fa-sort-amount-desc';
-    return <i className={iconClassName} aria-hidden="true" />;
-  };
 
 
   return (
   return (
     <>
     <>
-      <div className="input-group flex-nowrap">
-        <div>
-          <div className="input-group-text border text-muted" id="btnGroupAddon">
-            {renderOrderIcon()}
-          </div>
-        </div>
-        <div className="border rounded-end">
-          <button
-            type="button"
-            className="btn dropdown-toggle py-1"
-            data-bs-toggle="dropdown"
-          >
-            <span className="me-2 text-secondary">{t(`search_result.sort_axis.${sort}`)}</span>
-          </button>
-          <div className="dropdown-menu dropdown-menu-right">
-            {Object.values(SORT_AXIS).map((sortAxis) => {
-              const nextOrder = (sort !== sortAxis || order === ASC) ? DESC : ASC;
-              return (
-                <button
-                  key={sortAxis}
-                  className="dropdown-item"
-                  type="button"
-                  onClick={() => { onClickChangeSort(sortAxis, nextOrder) }}
-                >
-                  <span>{t(`search_result.sort_axis.${sortAxis}`)}</span>
-                </button>
-              );
-            })}
-          </div>
-        </div>
+      <div className={`btn-group ${styles['sort-control']}`}>
+        <button
+          className="d-flex align-items-center btn btn-sm btn-outline-neutral-secondary rounded-pill"
+          type="button"
+          data-bs-toggle="dropdown"
+          aria-expanded="false"
+        >
+          <span className="material-symbols-outlined py-0">sort</span>
+          <span className="ms-2 me-auto">{t(`search_result.sort_axis.${sort}`)}</span>
+          <span className="material-symbols-outlined py-0">expand_more</span>
+        </button>
+        <ul className="dropdown-menu">
+          {Object.values(SORT_AXIS).map((sortAxis) => {
+            const nextOrder = (sort !== sortAxis || order === ASC) ? DESC : ASC;
+            return (
+              <button
+                key={sortAxis}
+                className="dropdown-item"
+                type="button"
+                onClick={() => { onClickChangeSort(sortAxis, nextOrder) }}
+              >
+                <span>{t(`search_result.sort_axis.${sortAxis}`)}</span>
+              </button>
+            );
+          })}
+        </ul>
       </div>
       </div>
     </>
     </>
   );
   );

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

@@ -67,7 +67,7 @@ export const ShareLinkPageView = (props: Props): JSX.Element => {
 
 
 
 
   const Contents = () => {
   const Contents = () => {
-    if (isNotFound) {
+    if (isNotFound || page.revision == null) {
       return <></>;
       return <></>;
     }
     }
 
 

+ 1 - 1
apps/app/src/components/Sidebar/Custom/CustomSidebarSubstance.tsx

@@ -19,7 +19,7 @@ export const CustomSidebarSubstance = (): JSX.Element => {
 
 
   if (rendererOptions == null) return <></>;
   if (rendererOptions == null) return <></>;
 
 
-  const markdown = page?.revision.body;
+  const markdown = page?.revision?.body;
 
 
   return (
   return (
     <div className={`py-3 grw-custom-sidebar-content ${styles['grw-custom-sidebar-content']}`}>
     <div className={`py-3 grw-custom-sidebar-content ${styles['grw-custom-sidebar-content']}`}>

+ 4 - 10
apps/app/src/interfaces/editor-settings.ts

@@ -1,16 +1,10 @@
-export const DEFAULT_THEME = 'DefaultLight';
+import { type EditorTheme, type KeyMapMode } from '@growi/editor';
 
 
-const KeyMapMode = {
-  default: 'default',
-  vim: 'vim',
-  emacs: 'emacs',
-  vscode: 'vscode',
-} as const;
-
-export type KeyMapMode = typeof KeyMapMode[keyof typeof KeyMapMode];
+export const DEFAULT_KEYMAP = 'default';
+export const DEFAULT_THEME = 'defaultlight';
 
 
 export interface IEditorSettings {
 export interface IEditorSettings {
-  theme: undefined | string,
+  theme: undefined | EditorTheme,
   keymapMode: undefined | KeyMapMode,
   keymapMode: undefined | KeyMapMode,
   styleActiveLine: boolean,
   styleActiveLine: boolean,
   autoFormatMarkdownTable: boolean,
   autoFormatMarkdownTable: boolean,

+ 1 - 1
apps/app/src/pages/[[...path]].page.tsx

@@ -260,7 +260,7 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
     if (currentPageId != null && !props.isNotFound) {
     if (currentPageId != null && !props.isNotFound) {
       const mutatePageData = async() => {
       const mutatePageData = async() => {
         const pageData = await mutateCurrentPage();
         const pageData = await mutateCurrentPage();
-        mutateEditingMarkdown(pageData?.revision.body);
+        mutateEditingMarkdown(pageData?.revision?.body);
       };
       };
 
 
       // If skipSSR is true, use the API to retrieve page data.
       // If skipSSR is true, use the API to retrieve page data.

+ 1 - 0
apps/app/src/server/models/page.ts

@@ -1045,6 +1045,7 @@ schema.methods.calculateAndUpdateLatestRevisionBodyLength = async function(this:
   // eslint-disable-next-line rulesdir/no-populate
   // eslint-disable-next-line rulesdir/no-populate
   const populatedPageDocument = await this.populate<PageDocument>('revision', 'body');
   const populatedPageDocument = await this.populate<PageDocument>('revision', 'body');
 
 
+  assert(populatedPageDocument.revision != null);
   assert(isPopulated(populatedPageDocument.revision));
   assert(isPopulated(populatedPageDocument.revision));
 
 
   this.latestRevisionBodyLength = populatedPageDocument.revision.body.length;
   this.latestRevisionBodyLength = populatedPageDocument.revision.body.length;

+ 22 - 1
apps/app/src/stores/page.tsx

@@ -56,7 +56,28 @@ export const useSWRxCurrentPage = (initialData?: IPagePopulatedToShowRevision|nu
   const key = 'currentPage';
   const key = 'currentPage';
 
 
   const { cache } = useSWRConfig();
   const { cache } = useSWRConfig();
-  const shouldMutate = initialData?._id !== cache.get(key)?.data?._id && initialData !== undefined;
+
+  // Problem 1: https://github.com/weseek/growi/pull/7772/files#diff-4c1708c4f959974166c15435c6b35950ba01bbf35e7e4b8e99efeb125a8000a7
+  // Problem 2: https://redmine.weseek.co.jp/issues/141027
+  const shouldMutate = (() => {
+    if (initialData === undefined) {
+      return false;
+    }
+
+    // reset when null
+    if (initialData == null) {
+      return true;
+    }
+
+    const cachedData = cache.get(key)?.data as IPagePopulatedToShowRevision|null;
+    if (initialData._id !== cachedData?._id) {
+      return true;
+    }
+
+    if (initialData.revision?._id !== cachedData?.revision?._id) {
+      return true;
+    }
+  })();
 
 
   useEffect(() => {
   useEffect(() => {
     if (shouldMutate) {
     if (shouldMutate) {

+ 2 - 2
packages/core/src/interfaces/page.ts

@@ -18,7 +18,7 @@ export type IGrantedGroup = {
 export type IPage = {
 export type IPage = {
   path: string,
   path: string,
   status: string,
   status: string,
-  revision: Ref<IRevision>,
+  revision?: Ref<IRevision>,
   tags: Ref<ITag>[],
   tags: Ref<ITag>[],
   creator: any,
   creator: any,
   createdAt: Date,
   createdAt: Date,
@@ -50,7 +50,7 @@ export type IPagePopulatedToShowRevision = Omit<IPageHasId, 'lastUpdateUser'|'cr
   creator: IUserHasId | null,
   creator: IUserHasId | null,
   deleteUser: IUserHasId,
   deleteUser: IUserHasId,
   grantedGroups: { type: GroupType, item: IUserGroupHasId }[],
   grantedGroups: { type: GroupType, item: IUserGroupHasId }[],
-  revision: IRevisionHasId,
+  revision?: IRevisionHasId,
   author: IUserHasId,
   author: IUserHasId,
 }
 }
 
 

+ 5 - 5
packages/editor/src/components/CodeMirrorEditor/CodeMirrorEditor.tsx

@@ -31,8 +31,8 @@ const CodeMirrorEditorContainer = forwardRef<HTMLDivElement>((props, ref) => {
 export type CodeMirrorEditorProps = {
 export type CodeMirrorEditorProps = {
   acceptedUploadFileType?: AcceptedUploadFileType,
   acceptedUploadFileType?: AcceptedUploadFileType,
   indentSize?: number,
   indentSize?: number,
-  editorTheme?: string,
-  editorKeymap?: string,
+  editorTheme?: EditorTheme,
+  editorKeymap?: KeyMapMode,
   onChange?: (value: string) => void,
   onChange?: (value: string) => void,
   onSave?: () => void,
   onSave?: () => void,
   onUpload?: (files: File[]) => void,
   onUpload?: (files: File[]) => void,
@@ -153,9 +153,9 @@ export const CodeMirrorEditor = (props: Props): JSX.Element => {
   const [themeExtension, setThemeExtension] = useState<Extension | undefined>(undefined);
   const [themeExtension, setThemeExtension] = useState<Extension | undefined>(undefined);
   useEffect(() => {
   useEffect(() => {
     const settingTheme = async(name?: EditorTheme) => {
     const settingTheme = async(name?: EditorTheme) => {
-      setThemeExtension(await getEditorTheme(name ?? 'DefaultLight'));
+      setThemeExtension(await getEditorTheme(name ?? 'defaultlight'));
     };
     };
-    settingTheme(editorTheme as EditorTheme);
+    settingTheme(editorTheme);
   }, [codeMirrorEditor, editorTheme, setThemeExtension]);
   }, [codeMirrorEditor, editorTheme, setThemeExtension]);
 
 
   useEffect(() => {
   useEffect(() => {
@@ -174,7 +174,7 @@ export const CodeMirrorEditor = (props: Props): JSX.Element => {
     const settingKeyMap = async(name?: KeyMapMode) => {
     const settingKeyMap = async(name?: KeyMapMode) => {
       setKeymapExtension(await getKeymap(name ?? 'default'));
       setKeymapExtension(await getKeymap(name ?? 'default'));
     };
     };
-    settingKeyMap(editorKeymap as KeyMapMode);
+    settingKeyMap(editorKeymap);
 
 
   }, [codeMirrorEditor, editorKeymap, setKeymapExtension]);
   }, [codeMirrorEditor, editorKeymap, setKeymapExtension]);
 
 

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

@@ -6,6 +6,7 @@ import { AcceptedUploadFileType } from '@growi/core';
 import { toast } from 'react-toastify';
 import { toast } from 'react-toastify';
 
 
 import { GlobalCodeMirrorEditorKey } from '../../consts';
 import { GlobalCodeMirrorEditorKey } from '../../consts';
+import type { EditorTheme, KeyMapMode } from '../../services';
 import { useCodeMirrorEditorIsolated } from '../../stores';
 import { useCodeMirrorEditorIsolated } from '../../stores';
 import { CodeMirrorEditorMain } from '../CodeMirrorEditorMain';
 import { CodeMirrorEditorMain } from '../CodeMirrorEditorMain';
 
 
@@ -15,8 +16,8 @@ import { Preview } from './Preview';
 export const Playground = (): JSX.Element => {
 export const Playground = (): JSX.Element => {
 
 
   const [markdownToPreview, setMarkdownToPreview] = useState('');
   const [markdownToPreview, setMarkdownToPreview] = useState('');
-  const [editorTheme, setEditorTheme] = useState('');
-  const [editorKeymap, setEditorKeymap] = useState('');
+  const [editorTheme, setEditorTheme] = useState<EditorTheme>('defaultlight');
+  const [editorKeymap, setEditorKeymap] = useState<KeyMapMode>('default');
 
 
   const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(GlobalCodeMirrorEditorKey.MAIN);
   const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(GlobalCodeMirrorEditorKey.MAIN);
 
 

+ 6 - 4
packages/editor/src/components/playground/PlaygroundController.tsx

@@ -3,7 +3,9 @@ import { useCallback } from 'react';
 import { useForm } from 'react-hook-form';
 import { useForm } from 'react-hook-form';
 
 
 import { GlobalCodeMirrorEditorKey } from '../../consts';
 import { GlobalCodeMirrorEditorKey } from '../../consts';
-import { AllEditorTheme, AllKeyMap } from '../../services';
+import {
+  AllEditorTheme, AllKeyMap, EditorTheme, KeyMapMode,
+} from '../../services';
 import { useCodeMirrorEditorIsolated } from '../../stores';
 import { useCodeMirrorEditorIsolated } from '../../stores';
 
 
 export const InitEditorValueRow = (): JSX.Element => {
 export const InitEditorValueRow = (): JSX.Element => {
@@ -71,7 +73,7 @@ export const SetCaretLineRow = (): JSX.Element => {
 
 
 
 
 type SetParamRowProps = {
 type SetParamRowProps = {
-    update: (value: string) => void,
+    update: (value: any) => void,
     items: string[],
     items: string[],
 }
 }
 
 
@@ -106,8 +108,8 @@ const SetParamRow = (
 
 
 
 
 type PlaygroundControllerProps = {
 type PlaygroundControllerProps = {
-  setEditorTheme: (value: string) => void
-  setEditorKeymap: (value: string) => void
+  setEditorTheme: (value: EditorTheme) => void
+  setEditorKeymap: (value: KeyMapMode) => void
 };
 };
 
 
 export const PlaygroundController = (props: PlaygroundControllerProps): JSX.Element => {
 export const PlaygroundController = (props: PlaygroundControllerProps): JSX.Element => {

+ 19 - 20
packages/editor/src/services/editor-theme/index.ts

@@ -2,41 +2,40 @@ import { Extension } from '@codemirror/state';
 
 
 export const getEditorTheme = async(themeName: EditorTheme): Promise<Extension> => {
 export const getEditorTheme = async(themeName: EditorTheme): Promise<Extension> => {
   switch (themeName) {
   switch (themeName) {
-    case 'Eclipse':
+    case 'eclipse':
       return (await import('@uiw/codemirror-theme-eclipse')).eclipse;
       return (await import('@uiw/codemirror-theme-eclipse')).eclipse;
-    case 'Basic':
+    case 'basic':
       return (await import('cm6-theme-basic-light')).basicLight;
       return (await import('cm6-theme-basic-light')).basicLight;
-    case 'Ayu':
+    case 'ayu':
       return (await import('./ayu')).ayu;
       return (await import('./ayu')).ayu;
-    case 'Rosé Pine':
+    case 'rosepine':
       return (await import('./rose-pine')).rosePine;
       return (await import('./rose-pine')).rosePine;
-    case 'DefaultDark':
+    case 'defaultdark':
       return (await import('./original-dark')).originalDark;
       return (await import('./original-dark')).originalDark;
-    case 'Material':
+    case 'material':
       return (await import('cm6-theme-material-dark')).materialDark;
       return (await import('cm6-theme-material-dark')).materialDark;
-    case 'Nord':
+    case 'nord':
       return (await import('cm6-theme-nord')).nord;
       return (await import('cm6-theme-nord')).nord;
-    case 'Cobalt':
+    case 'cobalt':
       return (await import('./cobalt')).cobalt;
       return (await import('./cobalt')).cobalt;
-    case 'Kimbie':
+    case 'kimbie':
       return (await import('@uiw/codemirror-theme-kimbie')).kimbie;
       return (await import('@uiw/codemirror-theme-kimbie')).kimbie;
   }
   }
   return (await import('./original-light')).originalLight;
   return (await import('./original-light')).originalLight;
 };
 };
 
 
 const EditorTheme = {
 const EditorTheme = {
-  DefaultLight: 'DefaultLight',
-  Eclipse: 'Eclipse',
-  Basic: 'Basic',
-  Ayu: 'Ayu',
-  'Rosé Pine': 'Rosé Pine',
-  DefaultDark: 'DefaultDark',
-  Material: 'Material',
-  Nord: 'Nord',
-  Cobalt: 'Cobalt',
-  Kimbie: 'Kimbie',
+  defaultlight: 'defaultlight',
+  eclipse: 'eclipse',
+  basic: 'basic',
+  ayu: 'ayu',
+  rosepine:  'rosepine',
+  defaultdark: 'defaultdark',
+  material: 'material',
+  nord: 'nord',
+  cobalt: 'cobalt',
+  kimbie: 'kimbie',
 } as const;
 } as const;
 
 
-
 export const AllEditorTheme = Object.values(EditorTheme);
 export const AllEditorTheme = Object.values(EditorTheme);
 export type EditorTheme = typeof EditorTheme[keyof typeof EditorTheme]
 export type EditorTheme = typeof EditorTheme[keyof typeof EditorTheme]