diff --git a/apps/web/src/app/test/report-playground/ReportPlayground.tsx b/apps/web/src/app/test/report-playground/ReportPlayground.tsx
index 8ebd2bb2b..795f5a7db 100644
--- a/apps/web/src/app/test/report-playground/ReportPlayground.tsx
+++ b/apps/web/src/app/test/report-playground/ReportPlayground.tsx
@@ -201,6 +201,16 @@ const value: ReportElements = [
id: '39ZlrKsOyn',
lineHeight: 3
},
+ {
+ children: [
+ {
+ text: 'sdfasdf'
+ }
+ ],
+ icon: '😄',
+ type: 'callout',
+ id: 'KQ0_YKgdqy'
+ },
{
type: 'p',
id: 'k5Id6hcBYM',
diff --git a/apps/web/src/components/ui/icons/NucleoIconOutlined/grid-layout-cols-3.tsx b/apps/web/src/components/ui/icons/NucleoIconOutlined/grid-layout-cols-3.tsx
index 2b461854a..fb462c43e 100644
--- a/apps/web/src/components/ui/icons/NucleoIconOutlined/grid-layout-cols-3.tsx
+++ b/apps/web/src/components/ui/icons/NucleoIconOutlined/grid-layout-cols-3.tsx
@@ -15,9 +15,9 @@ function gridLayoutCols3(props: iconProps) {
transform="rotate(90 9 9)"
fill="none"
stroke="#212121"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="1.5">
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ strokeWidth="1.5">
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ strokeWidth="1.5">
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ strokeWidth="1.5">
);
diff --git a/apps/web/src/components/ui/report/config/addMenuItems.tsx b/apps/web/src/components/ui/report/config/addMenuItems.tsx
new file mode 100644
index 000000000..0543405be
--- /dev/null
+++ b/apps/web/src/components/ui/report/config/addMenuItems.tsx
@@ -0,0 +1,235 @@
+'use client';
+
+import * as React from 'react';
+import type { PlateEditor } from 'platejs/react';
+import { KEYS } from 'platejs';
+import { AIChatPlugin } from '@platejs/ai/react';
+import { Minus } from '@/components/ui/icons';
+import { NodeTypeIcons } from './icons';
+import { NodeTypeLabels, MenuGroupLabels } from './labels';
+import { insertBlock, insertInlineElement } from '../elements/transforms';
+
+// Shared types for menu items
+export interface MenuItem {
+ icon: React.ReactNode;
+ value: string;
+ onSelect: (editor: PlateEditor, value: string) => void;
+ className?: string;
+ focusEditor?: boolean;
+ keywords?: readonly string[];
+ label?: string;
+ description?: string;
+}
+
+export interface MenuGroup {
+ group: string;
+ items: MenuItem[];
+}
+
+// Shared menu groups used by both SlashNode and InsertToolbarButton
+export const menuGroups: MenuGroup[] = [
+ // {
+ // group: 'AI',
+ // items: [
+ // {
+ // focusEditor: false,
+ // icon: ,
+ // label: NodeTypeLabels.aiChat.label,
+ // keywords: NodeTypeLabels.aiChat.keywords,
+ // value: 'AI',
+ // onSelect: (editor) => {
+ // editor.getApi(AIChatPlugin).aiChat.show();
+ // }
+ // }
+ // ]
+ // },
+ {
+ group: MenuGroupLabels.basicBlocks,
+ items: [
+ {
+ icon: ,
+ keywords: NodeTypeLabels.paragraph.keywords,
+ label: NodeTypeLabels.paragraph.label,
+ value: KEYS.p
+ },
+ {
+ icon: ,
+ keywords: NodeTypeLabels.h1.keywords,
+ label: NodeTypeLabels.h1.label,
+ value: 'h1'
+ },
+ {
+ icon: ,
+ keywords: NodeTypeLabels.h2.keywords,
+ label: NodeTypeLabels.h2.label,
+ value: 'h2'
+ },
+ {
+ icon: ,
+ keywords: NodeTypeLabels.h3.keywords,
+ label: NodeTypeLabels.h3.label,
+ value: 'h3'
+ },
+ {
+ icon: ,
+ keywords: NodeTypeLabels.table.keywords,
+ label: NodeTypeLabels.table.label,
+ value: KEYS.table
+ },
+ {
+ icon: ,
+ keywords: NodeTypeLabels.codeBlock.keywords,
+ label: NodeTypeLabels.codeBlock.label,
+ value: KEYS.codeBlock
+ },
+ {
+ icon: ,
+ keywords: NodeTypeLabels.blockquote.keywords,
+ label: NodeTypeLabels.blockquote.label,
+ value: KEYS.blockquote
+ },
+ {
+ icon: ,
+ label: NodeTypeLabels.divider.label,
+ value: KEYS.hr
+ },
+ {
+ description: 'Insert a highlighted block.',
+ icon: ,
+ keywords: NodeTypeLabels.callout.keywords,
+ label: NodeTypeLabels.callout.label,
+ value: KEYS.callout
+ }
+ ].map((item) => ({
+ ...item,
+ onSelect: (editor, value) => {
+ insertBlock(editor, value);
+ }
+ }))
+ },
+ {
+ group: MenuGroupLabels.lists,
+ items: [
+ {
+ icon: ,
+ keywords: NodeTypeLabels.bulletedList.keywords,
+ label: NodeTypeLabels.bulletedList.label,
+ value: KEYS.ul
+ },
+ {
+ icon: ,
+ keywords: NodeTypeLabels.numberedList.keywords,
+ label: NodeTypeLabels.numberedList.label,
+ value: KEYS.ol
+ },
+ {
+ icon: ,
+ keywords: NodeTypeLabels.todoList.keywords,
+ label: NodeTypeLabels.todoList.label,
+ value: KEYS.listTodo
+ },
+ {
+ icon: ,
+ keywords: NodeTypeLabels.toggleList.keywords,
+ label: NodeTypeLabels.toggleList.label,
+ value: KEYS.toggle
+ }
+ ].map((item) => ({
+ ...item,
+ onSelect: (editor, value) => {
+ insertBlock(editor, value);
+ }
+ }))
+ },
+ {
+ group: MenuGroupLabels.media,
+ items: [
+ {
+ icon: ,
+ keywords: NodeTypeLabels.image?.keywords,
+ label: NodeTypeLabels.image.label,
+ value: KEYS.img
+ },
+ {
+ icon: ,
+ keywords: NodeTypeLabels.embed?.keywords,
+ label: NodeTypeLabels.embed.label,
+ value: KEYS.mediaEmbed
+ }
+ ].map((item) => ({
+ ...item,
+ onSelect: (editor, value) => {
+ insertBlock(editor, value);
+ }
+ }))
+ },
+ {
+ group: MenuGroupLabels.advancedBlocks,
+ items: [
+ {
+ icon: ,
+ keywords: NodeTypeLabels.tableOfContents.keywords,
+ label: NodeTypeLabels.tableOfContents.label,
+ value: KEYS.toc
+ },
+ {
+ icon: ,
+ keywords: NodeTypeLabels.columnsThree.keywords,
+ label: NodeTypeLabels.columnsThree.label,
+ value: 'action_three_columns'
+ },
+ {
+ focusEditor: false,
+ icon: ,
+ keywords: NodeTypeLabels.equation.keywords,
+ label: NodeTypeLabels.equation.label,
+ value: KEYS.equation
+ }
+ ].map((item) => ({
+ ...item,
+ onSelect: (editor, value) => {
+ insertBlock(editor, value);
+ }
+ }))
+ },
+ {
+ group: MenuGroupLabels.inline,
+ items: [
+ {
+ icon: ,
+ keywords: NodeTypeLabels.link?.keywords,
+ label: NodeTypeLabels.link.label,
+ value: KEYS.link
+ },
+ {
+ focusEditor: true,
+ icon: ,
+ keywords: NodeTypeLabels.date.keywords,
+ label: NodeTypeLabels.date.label,
+ value: KEYS.date
+ },
+ {
+ focusEditor: false,
+ icon: ,
+ keywords: NodeTypeLabels.inlineEquation.keywords,
+ label: NodeTypeLabels.inlineEquation.label,
+ value: KEYS.inlineEquation
+ }
+ ].map((item) => ({
+ ...item,
+ onSelect: (editor, value) => {
+ insertInlineElement(editor, value);
+ }
+ }))
+ }
+];
+
+// Helper function to get groups for slash command and insert button
+export function getSlashGroups(): MenuGroup[] {
+ return menuGroups;
+}
+
+// Helper function to get groups for insert button (same as slash groups)
+export function getInsertGroups(): MenuGroup[] {
+ return menuGroups;
+}
diff --git a/apps/web/src/components/ui/report/elements/CalloutNode.tsx b/apps/web/src/components/ui/report/elements/CalloutNode.tsx
index 8c0eb1cdf..00c3beabc 100644
--- a/apps/web/src/components/ui/report/elements/CalloutNode.tsx
+++ b/apps/web/src/components/ui/report/elements/CalloutNode.tsx
@@ -57,7 +57,7 @@ export function CalloutElement({
}>
-
{children}
+ {children}
);
diff --git a/apps/web/src/components/ui/report/elements/InsertToolbarButton.tsx b/apps/web/src/components/ui/report/elements/InsertToolbarButton.tsx
index b895e9da8..dd83ca971 100644
--- a/apps/web/src/components/ui/report/elements/InsertToolbarButton.tsx
+++ b/apps/web/src/components/ui/report/elements/InsertToolbarButton.tsx
@@ -4,11 +4,10 @@ import * as React from 'react';
import type { DropdownMenuProps } from '@radix-ui/react-dropdown-menu';
-import { Minus, Plus } from '@/components/ui/icons';
-import { NodeTypeIcons } from '../config/icons';
-import { createLabel, NodeTypeLabels, MenuGroupLabels } from '../config/labels';
-import { KEYS } from 'platejs';
-import { type PlateEditor, useEditorRef } from 'platejs/react';
+import { Plus } from '@/components/ui/icons';
+import { createLabel } from '../config/labels';
+import { useEditorRef } from 'platejs/react';
+import { getInsertGroups } from '../config/addMenuItems';
import {
DropdownMenu,
@@ -16,183 +15,10 @@ import {
DropdownMenuItem,
DropdownMenuTrigger
} from '@/components/ui/dropdown-menu';
-import { insertBlock, insertInlineElement } from './transforms';
import { ToolbarButton, ToolbarMenuGroup } from '@/components/ui/toolbar/Toolbar';
-type Group = {
- group: string;
- items: Item[];
-};
-
-interface Item {
- icon: React.ReactNode;
- value: string;
- onSelect: (editor: PlateEditor, value: string) => void;
- focusEditor?: boolean;
- label?: string;
-}
-
-const groups: Group[] = [
- {
- group: MenuGroupLabels.basicBlocks,
- items: [
- {
- icon: ,
- label: NodeTypeLabels.paragraph.label,
- value: KEYS.p
- },
- {
- icon: ,
- label: NodeTypeLabels.h1.label,
- value: 'h1'
- },
- {
- icon: ,
- label: NodeTypeLabels.h2.label,
- value: 'h2'
- },
- {
- icon: ,
- label: NodeTypeLabels.h3.label,
- value: 'h3'
- },
- {
- icon: ,
- label: NodeTypeLabels.table.label,
- value: KEYS.table
- },
- {
- icon: ,
- label: NodeTypeLabels.codeBlock.label,
- value: KEYS.codeBlock
- },
- {
- icon: ,
- label: NodeTypeLabels.blockquote.label,
- value: KEYS.blockquote
- },
- {
- icon: ,
- label: NodeTypeLabels.divider.label,
- value: KEYS.hr
- },
- {
- icon: ,
- label: NodeTypeLabels.callout.label,
- value: KEYS.callout
- }
- ].map((item) => ({
- ...item,
- onSelect: (editor, value) => {
- insertBlock(editor, value);
- }
- }))
- },
- {
- group: MenuGroupLabels.lists,
- items: [
- {
- icon: ,
- label: NodeTypeLabels.bulletedList.label,
- value: KEYS.ul
- },
- {
- icon: ,
- label: NodeTypeLabels.numberedList.label,
- value: KEYS.ol
- },
- {
- icon: ,
- label: NodeTypeLabels.todoList.label,
- value: KEYS.listTodo
- },
- {
- icon: ,
- label: NodeTypeLabels.toggleList.label,
- value: KEYS.toggle
- }
- ].map((item) => ({
- ...item,
- onSelect: (editor, value) => {
- insertBlock(editor, value);
- }
- }))
- },
- {
- group: MenuGroupLabels.media,
- items: [
- {
- icon: ,
- label: NodeTypeLabels.image.label,
- value: KEYS.img
- },
- {
- icon: ,
- label: NodeTypeLabels.embed.label,
- value: KEYS.mediaEmbed
- }
- ].map((item) => ({
- ...item,
- onSelect: (editor, value) => {
- insertBlock(editor, value);
- }
- }))
- },
- {
- group: MenuGroupLabels.advancedBlocks,
- items: [
- {
- icon: ,
- label: NodeTypeLabels.tableOfContents.label,
- value: KEYS.toc
- },
- {
- icon: ,
- label: NodeTypeLabels.columnsThree.label,
- value: 'action_three_columns'
- },
- {
- focusEditor: false,
- icon: ,
- label: NodeTypeLabels.equation.label,
- value: KEYS.equation
- }
- ].map((item) => ({
- ...item,
- onSelect: (editor, value) => {
- insertBlock(editor, value);
- }
- }))
- },
- {
- group: MenuGroupLabels.inline,
- items: [
- {
- icon: ,
- label: NodeTypeLabels.link.label,
- value: KEYS.link
- },
- {
- focusEditor: true,
- icon: ,
- label: NodeTypeLabels.date.label,
- value: KEYS.date
- },
- {
- focusEditor: false,
- icon: ,
- label: NodeTypeLabels.inlineEquation.label,
- value: KEYS.inlineEquation
- }
- ].map((item) => ({
- ...item,
- onSelect: (editor, value) => {
- insertInlineElement(editor, value);
- }
- }))
- }
-];
+const groups = getInsertGroups();
export function InsertToolbarButton(props: DropdownMenuProps) {
const editor = useEditorRef();
diff --git a/apps/web/src/components/ui/report/elements/SlashNode.tsx b/apps/web/src/components/ui/report/elements/SlashNode.tsx
index d9f8960e7..6d3a96f01 100644
--- a/apps/web/src/components/ui/report/elements/SlashNode.tsx
+++ b/apps/web/src/components/ui/report/elements/SlashNode.tsx
@@ -2,15 +2,11 @@
import * as React from 'react';
-import type { PlateEditor, PlateElementProps } from 'platejs/react';
+import type { PlateElementProps } from 'platejs/react';
-import { AIChatPlugin } from '@platejs/ai/react';
-import { type TComboboxInputElement, KEYS } from 'platejs';
+import { type TComboboxInputElement } from 'platejs';
import { PlateElement } from 'platejs/react';
-import { NodeTypeIcons } from '../config/icons';
-import { NodeTypeLabels, MenuGroupLabels } from '../config/labels';
-
-import { insertBlock, insertInlineElement } from './transforms';
+import { getSlashGroups } from '../config/addMenuItems';
import {
InlineCombobox,
@@ -22,175 +18,7 @@ import {
InlineComboboxItem
} from './InlineCombobox';
-type Group = {
- group: string;
- items: Item[];
-};
-
-interface Item {
- icon: React.ReactNode;
- value: string;
- onSelect: (editor: PlateEditor, value: string) => void;
- className?: string;
- focusEditor?: boolean;
- keywords?: readonly string[];
- label?: string;
- description?: string;
-}
-
-const groups: Group[] = [
- // {
- // group: 'AI',
- // items: [
- // {
- // focusEditor: false,
- // icon: ,
- // label: NodeTypeLabels.aiChat.label,
- // keywords: NodeTypeLabels.aiChat.keywords,
- // value: 'AI',
- // onSelect: (editor) => {
- // editor.getApi(AIChatPlugin).aiChat.show();
- // }
- // }
- // ]
- // },
- {
- group: MenuGroupLabels.basicBlocks,
- items: [
- {
- icon: ,
- keywords: NodeTypeLabels.paragraph.keywords,
- label: NodeTypeLabels.paragraph.label,
- value: KEYS.p
- },
- {
- icon: ,
- keywords: NodeTypeLabels.h1.keywords,
- label: NodeTypeLabels.h1.label,
- value: KEYS.h1
- },
- {
- icon: ,
- keywords: NodeTypeLabels.h2.keywords,
- label: NodeTypeLabels.h2.label,
- value: KEYS.h2
- },
- {
- icon: ,
- keywords: NodeTypeLabels.h3.keywords,
- label: NodeTypeLabels.h3.label,
- value: KEYS.h3
- },
- {
- icon: ,
- keywords: NodeTypeLabels.bulletedList.keywords,
- label: NodeTypeLabels.bulletedList.label,
- value: KEYS.ul
- },
- {
- icon: ,
- keywords: NodeTypeLabels.numberedList.keywords,
- label: NodeTypeLabels.numberedList.label,
- value: KEYS.ol
- },
- {
- icon: ,
- keywords: NodeTypeLabels.todoList.keywords,
- label: NodeTypeLabels.todoList.label,
- value: KEYS.listTodo
- },
- {
- icon: ,
- keywords: NodeTypeLabels.toggleList.keywords,
- label: NodeTypeLabels.toggleList.label,
- value: KEYS.toggle
- },
- {
- icon: ,
- keywords: NodeTypeLabels.codeBlock.keywords,
- label: NodeTypeLabels.codeBlock.label,
- value: KEYS.codeBlock
- },
- {
- icon: ,
- keywords: NodeTypeLabels.table.keywords,
- label: NodeTypeLabels.table.label,
- value: KEYS.table
- },
- {
- icon: ,
- keywords: NodeTypeLabels.blockquote.keywords,
- label: NodeTypeLabels.blockquote.label,
- value: KEYS.blockquote
- },
- {
- description: 'Insert a highlighted block.',
- icon: ,
- keywords: NodeTypeLabels.callout.keywords,
- label: NodeTypeLabels.callout.label,
- value: KEYS.callout
- }
- ].map((item) => ({
- ...item,
- onSelect: (editor, value) => {
- insertBlock(editor, value);
- }
- }))
- },
- {
- group: MenuGroupLabels.advancedBlocks,
- items: [
- {
- icon: ,
- keywords: NodeTypeLabels.tableOfContents.keywords,
- label: NodeTypeLabels.tableOfContents.label,
- value: KEYS.toc
- },
- {
- icon: ,
- keywords: NodeTypeLabels.columnsThree.keywords,
- label: NodeTypeLabels.columnsThree.label,
- value: 'action_three_columns'
- },
- {
- focusEditor: false,
- icon: ,
- keywords: NodeTypeLabels.equation.keywords,
- label: NodeTypeLabels.equation.label,
- value: KEYS.equation
- }
- ].map((item) => ({
- ...item,
- onSelect: (editor, value) => {
- insertBlock(editor, value);
- }
- }))
- },
- {
- group: MenuGroupLabels.inline,
- items: [
- {
- focusEditor: true,
- icon: ,
- keywords: NodeTypeLabels.date.keywords,
- label: NodeTypeLabels.date.label,
- value: KEYS.date
- },
- {
- focusEditor: false,
- icon: ,
- keywords: NodeTypeLabels.inlineEquation.keywords,
- label: NodeTypeLabels.inlineEquation.label,
- value: KEYS.inlineEquation
- }
- ].map((item) => ({
- ...item,
- onSelect: (editor, value) => {
- insertInlineElement(editor, value);
- }
- }))
- }
-];
+const groups = getSlashGroups();
export function SlashInputElement(props: PlateElementProps) {
const { editor, element } = props;