Merge pull request #660 from buster-so/big-nate-bus-1603-finalize-context-menu-options

block context menu updates
This commit is contained in:
Nate Kelley 2025-08-02 19:10:36 -06:00 committed by GitHub
commit efbe35cd39
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 116 additions and 38 deletions

View File

@ -207,7 +207,7 @@ const value: ReportElements = [
children: [
{
text: 'This is a todo list',
subscript: true
subscript: false
}
],
indent: 1,

View File

@ -203,6 +203,7 @@ export const NodeTypeIcons = {
indent: IndentIncrease,
outdent: IndentDecrease,
download: Download,
turnInto: Pilcrow,
// Tools
equation: Equation,

View File

@ -124,6 +124,21 @@ export const NodeTypeLabels = {
keyboard: '⌘+⇧+Z',
keywords: []
},
delete: {
label: 'Delete',
keyboard: '⌘+⌫',
keywords: []
},
duplicate: {
label: 'Duplicate',
keyboard: undefined,
keywords: []
},
askAI: {
label: 'Ask AI',
keyboard: undefined,
keywords: []
},
link: {
label: 'Link',
keyboard: '⌘+K',
@ -161,13 +176,18 @@ export const NodeTypeLabels = {
keyboard: undefined,
keywords: []
},
insert: {
label: 'Insert',
align: {
label: 'Align',
keyboard: undefined,
keywords: []
},
align: {
label: 'Align',
indentation: {
label: 'Indentation',
keyboard: undefined,
keywords: ['indent', 'outdent']
},
insert: {
label: 'Insert',
keyboard: undefined,
keywords: []
},

View File

@ -19,6 +19,7 @@ import {
} from '@/components/ui/dropdown-menu';
import { ToolbarButton } from '@/components/ui/toolbar/Toolbar';
import { Tooltip } from '../../tooltip';
const items = [
{
@ -69,13 +70,15 @@ export function AlignToolbarButton(props: DropdownMenuProps) {
tf.textAlign.setNodes(value as Alignment);
editor.tf.focus();
}}>
{items.map(({ icon: Icon, value: itemValue }) => (
{items.map(({ icon: Icon, label, value: itemValue }) => (
<Tooltip key={itemValue} title={label} side="left">
<DropdownMenuRadioItem
key={itemValue}
className="data-[state=checked]:bg-accent pl-2 *:first:[span]:hidden"
value={itemValue}>
<Icon />
</DropdownMenuRadioItem>
</Tooltip>
))}
</DropdownMenuRadioGroup>
</DropdownMenuContent>

View File

@ -16,6 +16,7 @@ import {
ContextMenuContent,
ContextMenuGroup,
ContextMenuItem,
ContextMenuShortcut,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
@ -23,10 +24,33 @@ import {
} from '@/components/ui/context-menu';
import { useIsTouchDevice } from '@/hooks/useIsTouchDevice';
import { THEME_RESET_STYLE } from '@/styles/theme-reset';
import { NodeTypeIcons } from '../config/icons';
import { NodeTypeLabels } from '../config/labels';
// Helper function to render menu item content
const MenuItemContent = ({
icon,
labelKey
}: {
icon: React.ComponentType;
labelKey: keyof typeof NodeTypeLabels;
}) => {
const label = NodeTypeLabels[labelKey];
const Icon = icon;
return (
<>
<div className="text-icon-color text-md size-4">
<Icon />
</div>
{label.label}
{label.keyboard && <ContextMenuShortcut>{label.keyboard}</ContextMenuShortcut>}
</>
);
};
type Value = 'askAI' | null;
export function BlockContextMenu({ children }: { children: React.ReactNode }) {
function BlockContextMenuComponent({ children }: { children: React.ReactNode }) {
const { api, editor } = useEditorPlugin(BlockMenuPlugin);
const [value, setValue] = React.useState<Value>(null);
const isTouch = useIsTouchDevice();
@ -88,7 +112,7 @@ export function BlockContextMenu({ children }: { children: React.ReactNode }) {
y: event.clientY
});
}}>
<div className="w-full">{children}</div>
<div className="block-context-menu-trigger">{children}</div>
</ContextMenuTrigger>
<ContextMenuContent
className="w-64"
@ -104,56 +128,84 @@ export function BlockContextMenu({ children }: { children: React.ReactNode }) {
setValue(null);
}}>
<ContextMenuGroup>
<ContextMenuItem
{/* <ContextMenuItem
onClick={() => {
setValue('askAI');
}}>
Ask AI
</ContextMenuItem>
<MenuItemContent icon={NodeTypeIcons.ai} labelKey="askAI" />
</ContextMenuItem> */}
<ContextMenuItem
onClick={() => {
editor.getTransforms(BlockSelectionPlugin).blockSelection.removeNodes();
editor.tf.focus();
}}>
Delete
<MenuItemContent icon={NodeTypeIcons.trash} labelKey="delete" />
</ContextMenuItem>
<ContextMenuItem
onClick={() => {
editor.getTransforms(BlockSelectionPlugin).blockSelection.duplicate();
}}>
Duplicate
{/* <ContextMenuShortcut>⌘ + D</ContextMenuShortcut> */}
<MenuItemContent icon={NodeTypeIcons.copy} labelKey="duplicate" />
</ContextMenuItem>
<ContextMenuSub>
<ContextMenuSubTrigger>Turn into</ContextMenuSubTrigger>
<ContextMenuSubTrigger>
<MenuItemContent icon={NodeTypeIcons.turnInto} labelKey="turnInto" />
</ContextMenuSubTrigger>
<ContextMenuSubContent className="w-48">
<ContextMenuItem onClick={() => handleTurnInto(KEYS.p)}>Paragraph</ContextMenuItem>
<ContextMenuItem onClick={() => handleTurnInto(KEYS.p)}>
<MenuItemContent icon={NodeTypeIcons.paragraph} labelKey="paragraph" />
</ContextMenuItem>
<ContextMenuItem onClick={() => handleTurnInto(KEYS.h1)}>Heading 1</ContextMenuItem>
<ContextMenuItem onClick={() => handleTurnInto(KEYS.h2)}>Heading 2</ContextMenuItem>
<ContextMenuItem onClick={() => handleTurnInto(KEYS.h3)}>Heading 3</ContextMenuItem>
<ContextMenuItem onClick={() => handleTurnInto(KEYS.h1)}>
<MenuItemContent icon={NodeTypeIcons.h1} labelKey="h1" />
</ContextMenuItem>
<ContextMenuItem onClick={() => handleTurnInto(KEYS.h2)}>
<MenuItemContent icon={NodeTypeIcons.h2} labelKey="h2" />
</ContextMenuItem>
<ContextMenuItem onClick={() => handleTurnInto(KEYS.h3)}>
<MenuItemContent icon={NodeTypeIcons.h3} labelKey="h3" />
</ContextMenuItem>
<ContextMenuItem onClick={() => handleTurnInto(KEYS.blockquote)}>
Blockquote
<MenuItemContent icon={NodeTypeIcons.quote} labelKey="blockquote" />
</ContextMenuItem>
</ContextMenuSubContent>
</ContextMenuSub>
</ContextMenuGroup>
<ContextMenuGroup>
<ContextMenuItem
onClick={() => editor.getTransforms(BlockSelectionPlugin).blockSelection.setIndent(1)}>
Indent
</ContextMenuItem>
<ContextMenuItem
onClick={() => editor.getTransforms(BlockSelectionPlugin).blockSelection.setIndent(-1)}>
Outdent
</ContextMenuItem>
<ContextMenuSub>
<ContextMenuSubTrigger>Align</ContextMenuSubTrigger>
<ContextMenuSubTrigger>
<MenuItemContent icon={NodeTypeIcons.indent} labelKey="indentation" />
</ContextMenuSubTrigger>
<ContextMenuSubContent className="w-48">
<ContextMenuItem onClick={() => handleAlign('left')}>Left</ContextMenuItem>
<ContextMenuItem onClick={() => handleAlign('center')}>Center</ContextMenuItem>
<ContextMenuItem onClick={() => handleAlign('right')}>Right</ContextMenuItem>
<ContextMenuItem
onClick={() =>
editor.getTransforms(BlockSelectionPlugin).blockSelection.setIndent(1)
}>
<MenuItemContent icon={NodeTypeIcons.indent} labelKey="indent" />
</ContextMenuItem>
<ContextMenuItem
onClick={() =>
editor.getTransforms(BlockSelectionPlugin).blockSelection.setIndent(-1)
}>
<MenuItemContent icon={NodeTypeIcons.outdent} labelKey="outdent" />
</ContextMenuItem>
</ContextMenuSubContent>
</ContextMenuSub>
<ContextMenuSub>
<ContextMenuSubTrigger>
<MenuItemContent icon={NodeTypeIcons.alignLeft} labelKey="align" />
</ContextMenuSubTrigger>
<ContextMenuSubContent className="w-48">
<ContextMenuItem onClick={() => handleAlign('left')}>
<MenuItemContent icon={NodeTypeIcons.alignLeft} labelKey="alignLeft" />
</ContextMenuItem>
<ContextMenuItem onClick={() => handleAlign('center')}>
<MenuItemContent icon={NodeTypeIcons.alignCenter} labelKey="alignCenter" />
</ContextMenuItem>
<ContextMenuItem onClick={() => handleAlign('right')}>
<MenuItemContent icon={NodeTypeIcons.alignRight} labelKey="alignRight" />
</ContextMenuItem>
</ContextMenuSubContent>
</ContextMenuSub>
</ContextMenuGroup>
@ -161,3 +213,5 @@ export function BlockContextMenu({ children }: { children: React.ReactNode }) {
</ContextMenu>
);
}
export const BlockContextMenu = React.memo(BlockContextMenuComponent);