diff --git a/apps/client/src/features/editor/components/slash-menu/menu-items.ts b/apps/client/src/features/editor/components/slash-menu/menu-items.ts index 8e6cdb1..267fc1e 100644 --- a/apps/client/src/features/editor/components/slash-menu/menu-items.ts +++ b/apps/client/src/features/editor/components/slash-menu/menu-items.ts @@ -9,6 +9,7 @@ import { IconInfoCircle, IconList, IconListNumbers, + IconListTree, IconMath, IconMathFunction, IconMovie, @@ -96,6 +97,20 @@ const CommandGroups: SlashMenuGroupedItemsType = { .run(); }, }, + { + title: "Table of Contents", + description: "Create a table of contents.", + searchTerms: ["table", "contents", "list"], + icon: IconListTree, + command: ({ editor, range }: CommandProps) => { + editor + .chain() + .focus() + .deleteRange(range) + .toggleTableOfContents() + .run(); + }, + }, { title: "Bullet list", description: "Create a simple bullet list.", diff --git a/apps/client/src/features/editor/components/table-of-contents/table-of-contents-menu.tsx b/apps/client/src/features/editor/components/table-of-contents/table-of-contents-menu.tsx new file mode 100644 index 0000000..dcd4dd0 --- /dev/null +++ b/apps/client/src/features/editor/components/table-of-contents/table-of-contents-menu.tsx @@ -0,0 +1,124 @@ +import { BubbleMenu as BaseBubbleMenu, findParentNode, posToDOMRect } from "@tiptap/react"; +import React, { useCallback } from "react"; +import { sticky } from "tippy.js"; +import { Node as PMNode } from "prosemirror-model"; +import { EditorMenuProps, ShouldShowProps } from "@/features/editor/components/table/types/types.ts"; +import { + ActionIcon, + DividerVariant, + Group, + SegmentedControl, + Select, + Tooltip, + Text, + Checkbox, + Card, + Fieldset, +} from "@mantine/core"; +import { IconLayoutAlignCenter, IconLayoutAlignLeft, IconLayoutAlignRight } from "@tabler/icons-react"; +import { NodeWidthResize } from "@/features/editor/components/common/node-width-resize.tsx"; + +export function TableOfContentsMenu({ editor }: EditorMenuProps) { + const shouldShow = useCallback( + ({ state }: ShouldShowProps) => { + if (!state) { + return false; + } + + return editor.isActive("tableOfContents"); + }, + [editor] + ); + + const getReferenceClientRect = useCallback(() => { + const { selection } = editor.state; + const predicate = (node: PMNode) => node.type.name === "tableOfContents"; + const parent = findParentNode(predicate)(selection); + + if (parent) { + const dom = editor.view.nodeDOM(parent?.pos) as HTMLElement; + return dom.getBoundingClientRect(); + } + + return posToDOMRect(editor.view, selection.from, selection.to); + }, [editor]); + + const setDividerType = useCallback( + (type: DividerVariant) => { + editor.chain().focus(undefined, { scrollIntoView: false }).setDividerType(type).run(); + }, + [editor] + ); + + const setTableType = useCallback( + (type: "Contents" | "Child Pages") => { + editor.chain().focus(undefined, { scrollIntoView: false }).setTableType(type).run(); + }, + [editor] + ); + + const setPageIcons = useCallback( + (icons: boolean) => { + editor.chain().focus(undefined, { scrollIntoView: false }).setPageIcons(icons).run(); + }, + [editor] + ); + + return ( + +
+ + +