fix(web): cycle rich text and Markdown from one toolbar button

The two view icons were the same choice. One button now switches between them, and the tooltip names the mode the click will open.
This commit is contained in:
tianma-if committed 2026-09-30 19:34:20 +08:00
1 parent b9535e071d
commit d2ddb07a0f
5 files changed
+36 -47

No files matched your search

+29 -44
View File
@@ -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;
}) => (
<Tooltip>
@@ -88,7 +90,16 @@ const EditorToolbarButton = ({
{children}
</button>
</TooltipTrigger>
<TooltipContent side="bottom">{title}</TooltipContent>
<TooltipContent side="bottom" className={shortcutLabel ? "flex items-center gap-2" : undefined}>
{shortcutLabel ? (
<>
<span>{title}</span>
<kbd className="rounded border border-current/25 bg-current/10 px-1.5 py-0.5 font-mono text-xs leading-none">
{shortcutLabel}
</kbd>
</>
) : title}
</TooltipContent>
</Tooltip>
);
@@ -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 ? (
<>
<div className="flex shrink-0 items-center" role="group" aria-label={t("editorToolbar.viewMode")}>
{EDITOR_VIEW_MODES.map((mode) => (
<EditorToolbarButton
key={mode.value}
title={mode.value === "markdown" && markdownModeShortcutLabel
? `${t(mode.labelKey)} (${markdownModeShortcutLabel})`
: t(mode.labelKey)}
active={activeEditorView === mode.value}
disabled={viewSwitchDisabled}
onClick={() => onEditorViewChange(mode.value)}
>
<mode.icon className="h-4 w-4" />
</EditorToolbarButton>
))}
</div>
<MemoEditorToolbarDivider className="hidden sm:block" />
</>
) : onMarkdownModeChange ? (
<>
<Tooltip>
<TooltipTrigger asChild>
<button
className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md text-slate-500 transition hover:bg-slate-100 hover:text-slate-800 disabled:pointer-events-none disabled:opacity-40"
type="button"
aria-label={markdownMode ? t("editorToolbar.richText") : t("editorToolbar.markdown")}
aria-pressed={markdownMode}
disabled={readOnly}
onMouseDown={(event) => event.preventDefault()}
onClick={onMarkdownModeChange}
>
{markdownMode ? <FileText className="h-4 w-4" /> : <FileCode2 className="h-4 w-4" />}
</button>
</TooltipTrigger>
<TooltipContent side="bottom" className="flex items-center gap-2">
<span>{markdownMode ? t("editorToolbar.richText") : t("editorToolbar.markdown")}</span>
{markdownModeShortcutLabel && (
<kbd className="rounded border border-current/25 bg-current/10 px-1.5 py-0.5 font-mono text-xs leading-none">
{markdownModeShortcutLabel}
</kbd>
)}
</TooltipContent>
</Tooltip>
<EditorToolbarButton
title={t(nextEditorViewMode.labelKey)}
shortcutLabel={markdownModeShortcutLabel}
disabled={onEditorViewChange ? viewSwitchDisabled : readOnly}
onClick={() => {
if (onEditorViewChange) {
onEditorViewChange(nextEditorViewMode.value);
return;
}
onMarkdownModeChange?.();
}}
>
<nextEditorViewMode.icon className="h-4 w-4" />
</EditorToolbarButton>
<MemoEditorToolbarDivider className="hidden sm:block" />
</>
) : null}
@@ -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)]');
-1
View File
@@ -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",
-1
View File
@@ -2259,7 +2259,6 @@ export const ja = {
deleteTable: "表を削除",
markdown: "Markdown ソースに切り替え",
richText: "リッチテキストに切り替え",
viewMode: "ノートの表示",
appearance: "外観とスタイル",
blockStyle: "段落スタイル",
markdownSource: "Markdown ソース",
-1
View File
@@ -2257,7 +2257,6 @@ export const zhCN = {
deleteTable: "删除表格",
markdown: "切换到 Markdown 源码",
richText: "切换到富文本编辑",
viewMode: "笔记视图",
appearance: "外观与样式",
blockStyle: "段落样式",
markdownSource: "Markdown 源码",