diff --git a/src/components/SlashMenu.tsx b/src/components/SlashMenu.tsx
index afea61b..8358dd8 100644
--- a/src/components/SlashMenu.tsx
+++ b/src/components/SlashMenu.tsx
@@ -9,9 +9,7 @@ import {
AlignLeft, AlignCenter, AlignRight,
Trash2, AppWindow
} from "lucide-solid";
-import { cn, convertImageToWebpFile } from "@/lib/utils";
-import { activeTaskId, uploadTaskAttachment, activeNoteId, uploadNoteAttachment } from "@/store";
-import { toast } from "solid-sonner";
+import { cn } from "@/lib/utils";
export interface CommandItem {
title: string;
@@ -122,54 +120,7 @@ export const getSuggestionItems = ({ query }: { query: string }): CommandItem[]
description: "Upload an image.",
icon: ImageIcon,
command: ({ editor, range }: { editor: any, range: any }) => {
- editor.chain().focus().deleteRange(range).run();
-
- const taskId = activeTaskId();
- const noteId = activeNoteId();
-
- if (!taskId && !noteId) {
- toast.error("Cannot upload image outside of a task or note.");
- return;
- }
-
- const input = document.createElement("input");
- input.type = "file";
- input.accept = "image/*";
- input.onchange = async () => {
- if (input.files?.length) {
- const originalFile = input.files[0];
- toast.promise(
- (async () => {
- const webpFile = await convertImageToWebpFile(originalFile);
- if (taskId) {
- return uploadTaskAttachment(taskId, webpFile);
- } else {
- return uploadNoteAttachment(noteId!, webpFile);
- }
- })(),
- {
- loading: "Processing & uploading image...",
- success: (url) => {
- // Insert image, then run enter to add a newline (paragraph) below it.
- // Without this, the image stays selected and the next keypress deletes it.
- editor.chain()
- .focus()
- .setImage({ src: url })
- .run();
-
- // Manually force a new paragraph at the end so the user can keep typing
- editor.chain()
- .focus('end')
- .insertContent('
')
- .run();
- return "Image uploaded.";
- },
- error: "Failed to upload image."
- }
- );
- }
- };
- input.click();
+ editor.chain().focus().deleteRange(range).uploadImage().run();
},
},
{
diff --git a/src/components/TaskDetail.tsx b/src/components/TaskDetail.tsx
index 26a2d2d..53d299d 100644
--- a/src/components/TaskDetail.tsx
+++ b/src/components/TaskDetail.tsx
@@ -3,7 +3,7 @@ import { Sheet, SheetContent } from "@/components/ui/sheet";
import { type Task, removeTask, restoreTask, updateTask, saveTaskAsTemplate, shareTask, revokeShare, splitTask, loadTaskContent, currentTaskContext, cleanupTaskAttachments, getFavoriteTag } from "@/store";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
-import { ArrowUpCircle, Clock, Calendar, Type, Trash2, X, Copy, MoreHorizontal, Gauge, Share2, UserMinus, GitBranch, Tag, Settings, Star } from "lucide-solid";
+import { ArrowUpCircle, Clock, Calendar, Type, Trash2, X, Copy, MoreHorizontal, Gauge, Share2, UserMinus, GitBranch, Tag, Settings, Star, Image as ImageIcon } from "lucide-solid";
import { StatusCircle } from "./StatusCircle";
import { calculateDateFromUrgency, calculateUrgencyFromDate } from "@/store";
import { Button } from "./ui/button";
@@ -363,7 +363,7 @@ export const TaskDetail: Component = (props) => {
{/* Favorite */}
- Favorite
+
+
+ {/* Image Upload Shortcut */}
+
+
+
{/* Metadata Row (Dates/Timestamps) */}
diff --git a/src/components/TaskEditor.tsx b/src/components/TaskEditor.tsx
index 2538e79..5ab8812 100644
--- a/src/components/TaskEditor.tsx
+++ b/src/components/TaskEditor.tsx
@@ -27,6 +27,7 @@ import { Dropcursor } from "@tiptap/extension-dropcursor";
import { Mention } from "@tiptap/extension-mention";
import { userSuggestion, noteSuggestion } from "@/lib/mention-suggestion";
import { FileViewer } from "@/lib/extensions/file-viewer";
+import { ImageUpload } from "@/lib/extensions/image-upload";
interface TaskEditorProps {
content?: string;
@@ -161,6 +162,7 @@ export const TaskEditor: Component = (props) => {
width: 2,
}),
FileViewer,
+ ImageUpload,
],
// Use untrack so the editor doesn't re-initialize when props.content changes
content: untrack(() => props.content) || "",
diff --git a/src/lib/extensions/image-upload.ts b/src/lib/extensions/image-upload.ts
new file mode 100644
index 0000000..3715f3a
--- /dev/null
+++ b/src/lib/extensions/image-upload.ts
@@ -0,0 +1,70 @@
+import { Extension } from '@tiptap/core';
+import { activeTaskId, uploadTaskAttachment, activeNoteId, uploadNoteAttachment } from "@/store";
+import { convertImageToWebpFile } from "@/lib/utils";
+import { toast } from "solid-sonner";
+
+declare module '@tiptap/core' {
+ interface Commands {
+ imageUpload: {
+ uploadImage: () => ReturnType;
+ };
+ }
+}
+
+export const ImageUpload = Extension.create({
+ name: 'imageUpload',
+
+ addCommands() {
+ return {
+ uploadImage: () => ({ editor }) => {
+ const taskId = activeTaskId();
+ const noteId = activeNoteId();
+
+ if (!taskId && !noteId) {
+ toast.error("Cannot upload image outside of a task or note.");
+ return false;
+ }
+
+ const input = document.createElement("input");
+ input.type = "file";
+ input.accept = "image/*";
+ input.onchange = async () => {
+ if (input.files?.length) {
+ const originalFile = input.files[0];
+ toast.promise(
+ (async () => {
+ const webpFile = await convertImageToWebpFile(originalFile);
+ if (taskId) {
+ return uploadTaskAttachment(taskId, webpFile);
+ } else {
+ return uploadNoteAttachment(noteId!, webpFile);
+ }
+ })(),
+ {
+ loading: "Processing & uploading image...",
+ success: (url) => {
+ // Insert image
+ editor.chain()
+ .focus()
+ .setImage({ src: url })
+ .run();
+
+ // Manually force a new paragraph at the end so the user can keep typing
+ editor.chain()
+ .focus('end')
+ .insertContent('')
+ .run();
+
+ return "Image uploaded.";
+ },
+ error: "Failed to upload image."
+ }
+ );
+ }
+ };
+ input.click();
+ return true;
+ },
+ };
+ },
+});
diff --git a/src/views/NotepadView.tsx b/src/views/NotepadView.tsx
index 879ade7..68a1e5b 100644
--- a/src/views/NotepadView.tsx
+++ b/src/views/NotepadView.tsx
@@ -2,7 +2,7 @@ import { type Component, createSignal, createMemo, createEffect, onCleanup, For,
import { store, renameTagDefinition, updateNote, removeNote, restoreNote, setActiveNoteId, cleanupNoteAttachments, getFavoriteTag } from "@/store";
import { pb } from "@/lib/pocketbase";
import { type Note } from "@/store";
-import { Trash2, Lock, Unlock, Search, Link, X, ChevronRight, ChevronDown, FileText, Plus, Star, MoreHorizontal } from "lucide-solid";
+import { Trash2, Lock, Unlock, Search, Link, X, ChevronRight, ChevronDown, FileText, Plus, Star, MoreHorizontal, Type, Image as ImageIcon } from "lucide-solid";
import { Button } from "@/components/ui/button";
import { Popover, PopoverTrigger, PopoverContent } from "@/components/ui/popover";
import { DragDropProvider, DragDropSensors, SortableProvider, createSortable, closestCenter } from "@thisbeyond/solid-dnd";
@@ -22,6 +22,7 @@ export const NotepadView: Component<{
const [linkSearchQuery, setLinkSearchQuery] = createSignal("");
const [isLinkedTasksOpen, setIsLinkedTasksOpen] = createSignal(false);
const [isDragging, setIsDragging] = createSignal(false);
+ const [editorInstance, setEditorInstance] = createSignal(null);
const currentUserId = pb.authStore.model?.id;
@@ -432,9 +433,38 @@ export const NotepadView: Component<{
/>
)}
+
+ {/* Editor Commands Row */}
+
+
+
+
- {/* Mobile Tabs Dropdown Moved to Footer */}