Files
outline/app/editor/menus/code.tsx
T
Claude 864079e30b feat: add comment button to mermaid diagram toolbar
Allow users to comment on mermaid diagrams via the selection toolbar,
matching the existing comment-on-image pattern.

https://claude.ai/code/session_01E7BvJCgSpXJZdQEqj9mHgs
2026-05-23 16:30:25 +00:00

126 lines
3.1 KiB
TypeScript

import {
CommentIcon,
CopyIcon,
EditIcon,
ExpandedIcon,
TextWrapIcon,
} from "outline-icons";
import type { Node as ProseMirrorNode } from "prosemirror-model";
import { NodeSelection } from "prosemirror-state";
import type { EditorState } from "prosemirror-state";
import {
pluginKey as mermaidPluginKey,
type MermaidState,
} from "@shared/editor/extensions/Mermaid";
import {
getFrequentCodeLanguages,
codeLanguages,
getLabelForLanguage,
} from "@shared/editor/lib/code";
import { isMermaid } from "@shared/editor/lib/isCode";
import type { TFunction } from "i18next";
import type { MenuItem } from "@shared/editor/types";
import { metaDisplay } from "@shared/utils/keyboard";
export default function codeMenuItems(
state: EditorState,
readOnly: boolean | undefined,
t: TFunction
): MenuItem[] {
const node =
state.selection instanceof NodeSelection
? state.selection.node
: state.selection.$from.node();
const frequentLanguages = getFrequentCodeLanguages();
const frequentLangMenuItems = frequentLanguages.map((value) => {
const label = codeLanguages[value]?.label;
return langToMenuItem({ node, value, label });
});
const remainingLangMenuItems = Object.entries(codeLanguages)
.filter(
([value]) =>
!frequentLanguages.includes(value as keyof typeof codeLanguages)
)
.map(([value, item]) => langToMenuItem({ node, value, label: item.label }));
const getLanguageMenuItems = () =>
frequentLangMenuItems.length
? [
...frequentLangMenuItems,
{ name: "separator" },
...remainingLangMenuItems,
]
: remainingLangMenuItems;
const isEditingMermaid = !!(mermaidPluginKey.getState(state) as MermaidState)
?.editingId;
return [
{
name: "copyToClipboard",
icon: <CopyIcon />,
label: readOnly
? getLabelForLanguage(node.attrs.language ?? "none")
: undefined,
tooltip: t("Copy"),
},
{
name: "separator",
},
{
name: "edit_mermaid",
icon: <EditIcon />,
tooltip: t("Edit diagram"),
shortcut: `${metaDisplay} Enter`,
visible: isMermaid(node) && !isEditingMermaid && !readOnly,
},
{
name: "commentOnMermaid",
icon: <CommentIcon />,
tooltip: t("Comment"),
shortcut: `${metaDisplay}+⌥+M`,
visible: isMermaid(node) && !isEditingMermaid && !readOnly,
},
{
name: "separator",
},
{
name: "toggleCodeBlockWrap",
icon: <TextWrapIcon />,
tooltip: t("Wrap text"),
active: () => node.attrs.wrap,
visible: !readOnly && (!isMermaid(node) || isEditingMermaid),
},
{
name: "separator",
},
{
name: "code_block",
label: getLabelForLanguage(node.attrs.language ?? "none"),
icon: <ExpandedIcon />,
children: getLanguageMenuItems(),
visible: !readOnly,
},
];
}
const langToMenuItem = ({
node,
value,
label,
}: {
node: ProseMirrorNode;
value: string;
label: string;
}): MenuItem => ({
name: "code_block",
label,
active: () => node.attrs.language === value,
attrs: {
language: value,
},
});