diff --git a/src/components/SlashMenu.tsx b/src/components/SlashMenu.tsx index 855143b..463e359 100644 --- a/src/components/SlashMenu.tsx +++ b/src/components/SlashMenu.tsx @@ -1,5 +1,5 @@ import { type Component, createSignal, For, createEffect } from "solid-js"; -import { Type, Underline, Code, Quote, ImageIcon, Heading1, Heading2, Heading3, List, ListTodo, Film, FileText, AppWindow, Minus, Grid3X3, Highlighter, AlignLeft, AlignCenter, AlignRight, Trash2, Bold, Italic } from "lucide-solid"; +import { Type, Code, Quote, ImageIcon, Heading1, Heading2, Heading3, List, ListTodo, Film, FileText, AppWindow, Minus, Grid3X3, Trash2 } from "lucide-solid"; import { cn } from "@/lib/utils"; export interface CommandItem { @@ -20,33 +20,6 @@ export const getSuggestionItems = ({ query }: { query: string }): CommandItem[] editor.chain().focus().deleteRange(range).setNode("paragraph").run(); }, }, - { - title: "Bold", - description: "Strong text. (**text**)", - aliases: ["b", "strong"], - icon: Bold, - command: ({ editor, range }: { editor: any, range: any }) => { - editor.chain().focus().deleteRange(range).toggleBold().run(); - }, - }, - { - title: "Italic", - description: "Emphasized text. (*text*)", - aliases: ["i", "emphasis"], - icon: Italic, - command: ({ editor, range }: { editor: any, range: any }) => { - editor.chain().focus().deleteRange(range).toggleItalic().run(); - }, - }, - { - title: "Underline", - description: "Underlined text.", - aliases: ["u"], - icon: Underline, - command: ({ editor, range }: { editor: any, range: any }) => { - editor.chain().focus().deleteRange(range).toggleUnderline().run(); - }, - }, { title: "Heading 1", description: "Big section heading.", @@ -158,87 +131,7 @@ export const getSuggestionItems = ({ query }: { query: string }): CommandItem[] editor.chain().focus().deleteRange(range).insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run(); }, }, - { - title: "Highlight", - description: "Highlight selected text.", - aliases: ["mark"], - icon: Highlighter, - command: ({ editor, range }: { editor: any, range: any }) => { - editor.chain().focus().deleteRange(range).toggleHighlight().run(); - }, - }, - { - title: "Align Left", - description: "Align text to the left.", - icon: AlignLeft, - command: ({ editor, range }: { editor: any, range: any }) => { - editor.chain().focus().deleteRange(range).setTextAlign("left").run(); - }, - }, - { - title: "Align Center", - description: "Align text to the center.", - icon: AlignCenter, - command: ({ editor, range }: { editor: any, range: any }) => { - editor.chain().focus().deleteRange(range).setTextAlign("center").run(); - }, - }, - { - title: "Align Right", - description: "Align text to the right.", - icon: AlignRight, - command: ({ editor, range }: { editor: any, range: any }) => { - editor.chain().focus().deleteRange(range).setTextAlign("right").run(); - }, - }, - { - title: "Add Row Above", - description: "Insert a new row above the cursor.", - icon: Grid3X3, - command: ({ editor, range }: { editor: any, range: any }) => { - editor.chain().focus().deleteRange(range).addRowBefore().run(); - }, - }, - { - title: "Add Row Below", - description: "Insert a new row below the cursor.", - icon: Grid3X3, - command: ({ editor, range }: { editor: any, range: any }) => { - editor.chain().focus().deleteRange(range).addRowAfter().run(); - }, - }, - { - title: "Delete Row", - description: "Delete the current row.", - icon: Trash2, - command: ({ editor, range }: { editor: any, range: any }) => { - editor.chain().focus().deleteRange(range).deleteRow().run(); - }, - }, - { - title: "Add Column Left", - description: "Insert a new column to the left.", - icon: Grid3X3, - command: ({ editor, range }: { editor: any, range: any }) => { - editor.chain().focus().deleteRange(range).addColumnBefore().run(); - }, - }, - { - title: "Add Column Right", - description: "Insert a new column to the right.", - icon: Grid3X3, - command: ({ editor, range }: { editor: any, range: any }) => { - editor.chain().focus().deleteRange(range).addColumnAfter().run(); - }, - }, - { - title: "Delete Column", - description: "Delete the current column.", - icon: Trash2, - command: ({ editor, range }: { editor: any, range: any }) => { - editor.chain().focus().deleteRange(range).deleteColumn().run(); - }, - }, + { title: "Delete Table", description: "Delete the entire table.", diff --git a/src/components/TaskDetail.tsx b/src/components/TaskDetail.tsx index c227647..ac7d8b1 100644 --- a/src/components/TaskDetail.tsx +++ b/src/components/TaskDetail.tsx @@ -1,4 +1,4 @@ -import { type Component, createEffect, createSignal, For, createMemo, onCleanup } from "solid-js"; +import { type Component, createEffect, createSignal, For, createMemo, onCleanup, Show } from "solid-js"; import { Sheet, SheetContent } from "@/components/ui/sheet"; import { type Task, removeTask, restoreTask, updateTask, saveTaskAsTemplate, shareTask, revokeShare, splitTask, loadTaskContent, currentTaskContext, cleanupTaskAttachments, getFavoriteTag, now } from "@/store"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; @@ -18,6 +18,8 @@ import { useTheme } from "./ThemeProvider"; import { getThemeAdjustedColor } from "@/lib/colors"; import { pb } from "@/lib/pocketbase"; import { getRecurrenceProgress } from "@/lib/recurrence"; +import { TextBubbleMenu } from "@/components/TextBubbleMenu"; +import { TableBubbleMenu } from "@/components/TableBubbleMenu"; const TaskEditor = lazy(() => import("./TaskEditor").then(m => ({ default: m.TaskEditor }))); @@ -493,10 +495,19 @@ export const TaskDetail: Component = (props) => {
+ + + {(editor: any) => ( +
+ + +
+ )} +
{/* Content Area */} -
+
}> = (props) => { // Handle full screen images as before return (
- - {(e) => } -
boolean; + command: () => void; + iconClass?: string; +} + +interface FormatGroup { + name: string; + actions: GenericAction[]; +} + +export const TextBubbleMenu: Component<{ editor: Editor }> = (props) => { + // Track state of active styles so we can highlight the buttons + const [activeFormats, setActiveFormats] = createSignal>({}); + + createEffect(() => { + const updateState = () => { + const editor = props.editor; + if (!editor) return; + + setActiveFormats({ + bold: editor.isActive('bold'), + italic: editor.isActive('italic'), + underline: editor.isActive('underline'), + highlight: editor.isActive('highlight'), + alignLeft: editor.isActive({ textAlign: 'left' }), + alignCenter: editor.isActive({ textAlign: 'center' }), + alignRight: editor.isActive({ textAlign: 'right' }), + h1: editor.isActive('heading', { level: 1 }), + h2: editor.isActive('heading', { level: 2 }), + h3: editor.isActive('heading', { level: 3 }), + checklist: editor.isActive('taskList'), + table: editor.isActive('table'), + }); + }; + + props.editor.on("selectionUpdate", updateState); + props.editor.on("transaction", updateState); + + return () => { + props.editor.off("selectionUpdate", updateState); + props.editor.off("transaction", updateState); + }; + }); + + const groups: FormatGroup[] = [ + { + name: "Style", + actions: [ + { label: "Bold", icon: () => , isActive: () => activeFormats().bold, command: () => props.editor.chain().focus().toggleBold().run() }, + { label: "Italic", icon: () => , isActive: () => activeFormats().italic, command: () => props.editor.chain().focus().toggleItalic().run() }, + { label: "Underline", icon: () => , isActive: () => activeFormats().underline, command: () => props.editor.chain().focus().toggleUnderline().run() }, + { label: "Highlight", icon: () => , isActive: () => activeFormats().highlight, command: () => props.editor.chain().focus().toggleHighlight().run() }, + ] + }, + { + name: "Headings", + actions: [ + { label: "Heading 1", icon: () => , isActive: () => activeFormats().h1, command: () => props.editor.chain().focus().toggleHeading({ level: 1 }).run() }, + { label: "Heading 2", icon: () => , isActive: () => activeFormats().h2, command: () => props.editor.chain().focus().toggleHeading({ level: 2 }).run() }, + { label: "Heading 3", icon: () => , isActive: () => activeFormats().h3, command: () => props.editor.chain().focus().toggleHeading({ level: 3 }).run() }, + ] + }, + { + name: "Alignment", + actions: [ + { label: "Align Left", icon: () => , isActive: () => activeFormats().alignLeft, command: () => props.editor.chain().focus().setTextAlign('left').run() }, + { label: "Align Center", icon: () => , isActive: () => activeFormats().alignCenter, command: () => props.editor.chain().focus().setTextAlign('center').run() }, + { label: "Align Right", icon: () => , isActive: () => activeFormats().alignRight, command: () => props.editor.chain().focus().setTextAlign('right').run() }, + ] + }, + { + name: "Blocks", + actions: [ + { label: "Checklist", icon: () => , isActive: () => activeFormats().checklist, command: () => props.editor.chain().focus().toggleTaskList().run() }, + { label: "Table", icon: () => , isActive: () => activeFormats().table, command: () => props.editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run() }, + ] + }, + { + name: "Clear", + actions: [ + { label: "Clear Formatting", icon: () => , command: () => props.editor.chain().focus().clearNodes().unsetAllMarks().run() }, + ] + } + ]; + + return ( +
{ + e.preventDefault(); + e.stopPropagation(); + }} + > +
+
+ + {(group, groupIdx) => ( + <> +
+ + {(action) => ( + + )} + +
+ {groupIdx() < groups.length - 1 && ( +
+ )} + + )} + +
+
+
+ ); +}; diff --git a/src/views/NotepadView.tsx b/src/views/NotepadView.tsx index e3a9c19..e4ac110 100644 --- a/src/views/NotepadView.tsx +++ b/src/views/NotepadView.tsx @@ -12,6 +12,8 @@ import { TaskEditor } from "@/components/TaskEditor"; import { TaskCard } from "@/components/TaskCard"; import { NotesSidebar } from "@/components/NotesSidebar"; import { toast } from "solid-sonner"; +import { TextBubbleMenu } from "@/components/TextBubbleMenu"; +import { TableBubbleMenu } from "@/components/TableBubbleMenu"; export const NotepadView: Component<{ selectedNoteId: string | null; @@ -560,6 +562,14 @@ export const NotepadView: Component<{
+ + {(editor) => ( +
+ + +
+ )} +
{/* Inside Tabs Section Removed: Sub-tabs are now displayed on the outside as hanging bubbles */}