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: [ children: [
{ {
text: 'This is a todo list', text: 'This is a todo list',
subscript: true subscript: false
} }
], ],
indent: 1, indent: 1,

View File

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

View File

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

View File

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

View File

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