diff --git a/apps/web/src/components/EditorToolbar.tsx b/apps/web/src/components/EditorToolbar.tsx
index 260d4bef..65ee5276 100644
--- a/apps/web/src/components/EditorToolbar.tsx
+++ b/apps/web/src/components/EditorToolbar.tsx
@@ -61,12 +61,14 @@ const EditorToolbarButton = ({
children,
disabled = false,
onClick,
+ shortcutLabel,
title,
}: {
active?: boolean;
children: ReactNode;
disabled?: boolean;
onClick: () => void;
+ shortcutLabel?: string | null;
title: string;
}) => (
@@ -88,7 +90,16 @@ const EditorToolbarButton = ({
{children}
- {title}
+
+ {shortcutLabel ? (
+ <>
+ {title}
+
+ {shortcutLabel}
+
+ >
+ ) : title}
+
);
@@ -221,6 +232,8 @@ export const EditorToolbar = ({
: "plaintext";
const activeEditorView = onEditorViewChange ? editorView : markdownMode ? "markdown" : "rich";
const showFormattingTools = activeEditorView === "rich";
+ const nextEditorViewMode = EDITOR_VIEW_MODES[activeEditorView === "rich" ? 1 : 0];
+ const canCycleEditorView = Boolean(onEditorViewChange || onMarkdownModeChange);
useEffect(() => {
const controls = controlsRef.current;
@@ -328,50 +341,22 @@ export const EditorToolbar = ({
!expanded && MEMO_EDITOR_TOOLBAR_COLLAPSED_CLASS_NAME
)}
>
- {onEditorViewChange ? (
+ {canCycleEditorView ? (
<>
-
- {EDITOR_VIEW_MODES.map((mode) => (
- onEditorViewChange(mode.value)}
- >
-
-
- ))}
-
-
- >
- ) : onMarkdownModeChange ? (
- <>
-
-
-
-
-
- {markdownMode ? t("editorToolbar.richText") : t("editorToolbar.markdown")}
- {markdownModeShortcutLabel && (
-
- {markdownModeShortcutLabel}
-
- )}
-
-
+ {
+ if (onEditorViewChange) {
+ onEditorViewChange(nextEditorViewMode.value);
+ return;
+ }
+ onMarkdownModeChange?.();
+ }}
+ >
+
+
>
) : null}
diff --git a/apps/web/src/components/MemoEditorHeaderActions.test.mjs b/apps/web/src/components/MemoEditorHeaderActions.test.mjs
index b4f0a261..bfe434ab 100644
--- a/apps/web/src/components/MemoEditorHeaderActions.test.mjs
+++ b/apps/web/src/components/MemoEditorHeaderActions.test.mjs
@@ -76,6 +76,13 @@ describe("shared memo editor header actions", () => {
expect(densitySource).not.toContain("min-[1600px]:flex");
});
+ test("cycles rich text and markdown from one toolbar button", () => {
+ expect(editorToolbarSource).toContain("const nextEditorViewMode = EDITOR_VIEW_MODES[activeEditorView === \"rich\" ? 1 : 0]");
+ expect(editorToolbarSource).toContain("onEditorViewChange(nextEditorViewMode.value)");
+ expect(editorToolbarSource).not.toContain("EDITOR_VIEW_MODES.map");
+ expect(editorToolbarSource).not.toContain('t("editorToolbar.viewMode")');
+ });
+
test("aligns the note title, notebook row, and article on one reading gutter", () => {
expect(editorSource).toContain("MEMO_EDITOR_READING_GUTTER_CLASS_NAME");
expect(densitySource).toContain('px-[var(--editor-reading-gutter)]');
diff --git a/packages/shared/src/i18n/en-US.ts b/packages/shared/src/i18n/en-US.ts
index f9e63a3f..7b8b4451 100644
--- a/packages/shared/src/i18n/en-US.ts
+++ b/packages/shared/src/i18n/en-US.ts
@@ -2259,7 +2259,6 @@ export const enUS = {
deleteTable: "Delete table",
markdown: "Switch to Markdown source",
richText: "Switch to rich text",
- viewMode: "Note view",
appearance: "Appearance and style",
blockStyle: "Paragraph style",
markdownSource: "Markdown source",
diff --git a/packages/shared/src/i18n/ja.ts b/packages/shared/src/i18n/ja.ts
index c0b1242b..33a7d166 100644
--- a/packages/shared/src/i18n/ja.ts
+++ b/packages/shared/src/i18n/ja.ts
@@ -2259,7 +2259,6 @@ export const ja = {
deleteTable: "表を削除",
markdown: "Markdown ソースに切り替え",
richText: "リッチテキストに切り替え",
- viewMode: "ノートの表示",
appearance: "外観とスタイル",
blockStyle: "段落スタイル",
markdownSource: "Markdown ソース",
diff --git a/packages/shared/src/i18n/zh-CN.ts b/packages/shared/src/i18n/zh-CN.ts
index d46ea586..2b0854d7 100644
--- a/packages/shared/src/i18n/zh-CN.ts
+++ b/packages/shared/src/i18n/zh-CN.ts
@@ -2257,7 +2257,6 @@ export const zhCN = {
deleteTable: "删除表格",
markdown: "切换到 Markdown 源码",
richText: "切换到富文本编辑",
- viewMode: "笔记视图",
appearance: "外观与样式",
blockStyle: "段落样式",
markdownSource: "Markdown 源码",