-
- setIsEmpty(!md.trim())}
- onSubmit={handleSubmit}
- onUploadFile={handleUpload}
- debounceMs={100}
- />
-
-
-
-
-
-
-
-
+
+
+
+ setIsEmpty(!md.trim())}
+ onSubmit={handleSubmit}
+ onUploadFile={handleUpload}
+ debounceMs={100}
+ />
+
+
+ editorRef.current?.insertFile(result.filename, result.link, isImage)
+ }
+ disabled={uploading}
+ />
+
+
);
diff --git a/apps/web/features/modals/create-issue.tsx b/apps/web/features/modals/create-issue.tsx
index 099a2a43..ddefa1ba 100644
--- a/apps/web/features/modals/create-issue.tsx
+++ b/apps/web/features/modals/create-issue.tsx
@@ -33,6 +33,8 @@ import { useWorkspaceStore, useActorName } from "@/features/workspace";
import { useIssueStore } from "@/features/issues";
import { useIssueDraftStore } from "@/features/issues/stores/draft-store";
import { api } from "@/shared/api";
+import { useFileUpload } from "@/shared/hooks/use-file-upload";
+import { FileUploadButton } from "@/components/common/file-upload-button";
// ---------------------------------------------------------------------------
// Pill trigger — shared rounded-full button style for toolbar
@@ -90,6 +92,10 @@ export function CreateIssueModal({ onClose, data }: { onClose: () => void; data?
// Due date popover
const [dueDateOpen, setDueDateOpen] = useState(false);
+ // File upload
+ const { uploadWithToast } = useFileUpload();
+ const handleUpload = (file: File) => uploadWithToast(file);
+
const assigneeQuery = assigneeFilter.toLowerCase();
const filteredMembers = members.filter((m) => m.name.toLowerCase().includes(assigneeQuery));
const filteredAgents = agents.filter((a) => a.name.toLowerCase().includes(assigneeQuery));
@@ -229,6 +235,7 @@ export function CreateIssueModal({ onClose, data }: { onClose: () => void; data?
defaultValue={draft.description}
placeholder="Add description..."
onUpdate={(md) => setDraft({ description: md })}
+ onUploadFile={handleUpload}
debounceMs={500}
/>
@@ -420,7 +427,11 @@ export function CreateIssueModal({ onClose, data }: { onClose: () => void; data?
+
+ descEditorRef.current?.insertFile(result.filename, result.link, isImage)}
+ />
diff --git a/apps/web/package.json b/apps/web/package.json
index f48cffa1..a3e75923 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -18,6 +18,7 @@
"@dnd-kit/utilities": "^3.2.2",
"@emoji-mart/data": "^1.2.1",
"@floating-ui/dom": "^1.7.6",
+ "@tiptap/extension-code-block-lowlight": "3.20.5",
"@tiptap/extension-image": "^3.20.5",
"@tiptap/extension-link": "^3.20.5",
"@tiptap/extension-mention": "^3.20.5",
@@ -36,6 +37,7 @@
"emoji-mart": "^5.6.0",
"input-otp": "^1.4.2",
"linkify-it": "^5.0.0",
+ "lowlight": "^3.3.0",
"lucide-react": "catalog:",
"next": "^16.1.6",
"next-themes": "^0.4.6",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2565644c..fe3b325d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -75,6 +75,9 @@ importers:
'@floating-ui/dom':
specifier: ^1.7.6
version: 1.7.6
+ '@tiptap/extension-code-block-lowlight':
+ specifier: 3.20.5
+ version: 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/extension-code-block@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)(highlight.js@11.11.1)(lowlight@3.3.0)
'@tiptap/extension-image':
specifier: ^3.20.5
version: 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))
@@ -129,6 +132,9 @@ importers:
linkify-it:
specifier: ^5.0.0
version: 5.0.0
+ lowlight:
+ specifier: ^3.3.0
+ version: 3.3.0
lucide-react:
specifier: 'catalog:'
version: 1.0.1(react@19.2.3)
@@ -1322,6 +1328,15 @@ packages:
peerDependencies:
'@tiptap/extension-list': ^3.20.5
+ '@tiptap/extension-code-block-lowlight@3.20.5':
+ resolution: {integrity: sha512-EINMkflwiUfCkBTAj1meP+nwEEUyXKmJF4yQVHzbt/iIswMtIc/7qvyld92VBgXWJkc+vo/lIPioaZGoSO7TsQ==}
+ peerDependencies:
+ '@tiptap/core': ^3.20.5
+ '@tiptap/extension-code-block': ^3.20.5
+ '@tiptap/pm': ^3.20.5
+ highlight.js: ^11
+ lowlight: ^2 || ^3
+
'@tiptap/extension-code-block@3.20.5':
resolution: {integrity: sha512-0YZnqfqZ1IjzKBM4aezw8j3LZWJFEfs4+mbizHNlnZSYpKzpESYLeaLWGO5SpqF9Z8tmYmSoCaf0fqi5LwgdIA==}
peerDependencies:
@@ -2303,6 +2318,10 @@ packages:
headers-polyfill@4.0.3:
resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==}
+ highlight.js@11.11.1:
+ resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==}
+ engines: {node: '>=12.0.0'}
+
hono@4.12.8:
resolution: {integrity: sha512-VJCEvtrezO1IAR+kqEYnxUOoStaQPGrCmX3j4wDTNOcD1uRPFpGlwQUIW8niPuvHXaTUxeOUl5MMDGrl+tmO9A==}
engines: {node: '>=16.9.0'}
@@ -2619,6 +2638,9 @@ packages:
longest-streak@3.1.0:
resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
+ lowlight@3.3.0:
+ resolution: {integrity: sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==}
+
lru-cache@11.2.7:
resolution: {integrity: sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==}
engines: {node: 20 || >=22}
@@ -4916,6 +4938,14 @@ snapshots:
dependencies:
'@tiptap/extension-list': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)
+ '@tiptap/extension-code-block-lowlight@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/extension-code-block@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)(highlight.js@11.11.1)(lowlight@3.3.0)':
+ dependencies:
+ '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5)
+ '@tiptap/extension-code-block': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)
+ '@tiptap/pm': 3.20.5
+ highlight.js: 11.11.1
+ lowlight: 3.3.0
+
'@tiptap/extension-code-block@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)':
dependencies:
'@tiptap/core': 3.20.5(@tiptap/pm@3.20.5)
@@ -5927,6 +5957,8 @@ snapshots:
headers-polyfill@4.0.3: {}
+ highlight.js@11.11.1: {}
+
hono@4.12.8: {}
html-encoding-sniffer@6.0.0(@noble/hashes@1.8.0):
@@ -6171,6 +6203,12 @@ snapshots:
longest-streak@3.1.0: {}
+ lowlight@3.3.0:
+ dependencies:
+ '@types/hast': 3.0.4
+ devlop: 1.1.0
+ highlight.js: 11.11.1
+
lru-cache@11.2.7: {}
lru-cache@5.1.1: