summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDevin Griffin <[email protected]>2026-02-07 13:33:00 -0600
committerGitHub <[email protected]>2026-02-07 13:33:00 -0600
commit6bdd3528ac1b1824bd560678366ac82bb777db9c (patch)
tree531fb5a371700fc76390e6055787282c3ace2ae9
parent4efbfcd08735bece0a3e479f23296871780a01b4 (diff)
downloadopencode-6bdd3528ac1b1824bd560678366ac82bb777db9c.tar.gz
opencode-6bdd3528ac1b1824bd560678366ac82bb777db9c.zip
feat(app): drag-n-drop to @mention file (#12569)
-rw-r--r--packages/app/src/components/prompt-input.tsx17
-rw-r--r--packages/app/src/components/prompt-input/attachments.ts21
-rw-r--r--packages/app/src/components/prompt-input/drag-overlay.tsx6
-rw-r--r--packages/app/src/i18n/ar.ts1
-rw-r--r--packages/app/src/i18n/br.ts1
-rw-r--r--packages/app/src/i18n/bs.ts1
-rw-r--r--packages/app/src/i18n/da.ts1
-rw-r--r--packages/app/src/i18n/de.ts1
-rw-r--r--packages/app/src/i18n/en.ts1
-rw-r--r--packages/app/src/i18n/es.ts1
-rw-r--r--packages/app/src/i18n/fr.ts1
-rw-r--r--packages/app/src/i18n/ja.ts1
-rw-r--r--packages/app/src/i18n/ko.ts1
-rw-r--r--packages/app/src/i18n/no.ts1
-rw-r--r--packages/app/src/i18n/pl.ts1
-rw-r--r--packages/app/src/i18n/ru.ts1
-rw-r--r--packages/app/src/i18n/th.ts1
-rw-r--r--packages/app/src/i18n/zh.ts1
-rw-r--r--packages/app/src/i18n/zht.ts1
19 files changed, 48 insertions, 12 deletions
diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx
index 2bccddc29..3ac64fad2 100644
--- a/packages/app/src/components/prompt-input.tsx
+++ b/packages/app/src/components/prompt-input.tsx
@@ -205,7 +205,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
historyIndex: number
savedPrompt: Prompt | null
placeholder: number
- dragging: boolean
+ draggingType: "image" | "@mention" | null
mode: "normal" | "shell"
applyingHistory: boolean
}>({
@@ -213,7 +213,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
historyIndex: -1,
savedPrompt: null,
placeholder: Math.floor(Math.random() * EXAMPLES.length),
- dragging: false,
+ draggingType: null,
mode: "normal",
applyingHistory: false,
})
@@ -760,7 +760,11 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
editor: () => editorRef,
isFocused,
isDialogActive: () => !!dialog.active,
- setDragging: (value) => setStore("dragging", value),
+ setDraggingType: (type) => setStore("draggingType", type),
+ focusEditor: () => {
+ editorRef.focus()
+ setCursorPosition(editorRef, promptLength(prompt.current()))
+ },
addPart,
})
@@ -946,11 +950,14 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
"group/prompt-input": true,
"bg-surface-raised-stronger-non-alpha shadow-xs-border relative": true,
"rounded-[14px] overflow-clip focus-within:shadow-xs-border": true,
- "border-icon-info-active border-dashed": store.dragging,
+ "border-icon-info-active border-dashed": store.draggingType !== null,
[props.class ?? ""]: !!props.class,
}}
>
- <PromptDragOverlay dragging={store.dragging} label={language.t("prompt.dropzone.label")} />
+ <PromptDragOverlay
+ type={store.draggingType}
+ label={language.t(store.draggingType === "@mention" ? "prompt.dropzone.file.label" : "prompt.dropzone.label")}
+ />
<PromptContextItems
items={prompt.context.items()}
active={(item) => {
diff --git a/packages/app/src/components/prompt-input/attachments.ts b/packages/app/src/components/prompt-input/attachments.ts
index 4ea2cfb90..48eda3742 100644
--- a/packages/app/src/components/prompt-input/attachments.ts
+++ b/packages/app/src/components/prompt-input/attachments.ts
@@ -11,7 +11,8 @@ type PromptAttachmentsInput = {
editor: () => HTMLDivElement | undefined
isFocused: () => boolean
isDialogActive: () => boolean
- setDragging: (value: boolean) => void
+ setDraggingType: (type: "image" | "@mention" | null) => void
+ focusEditor: () => void
addPart: (part: ContentPart) => void
}
@@ -84,15 +85,18 @@ export function createPromptAttachments(input: PromptAttachmentsInput) {
event.preventDefault()
const hasFiles = event.dataTransfer?.types.includes("Files")
+ const hasText = event.dataTransfer?.types.includes("text/plain")
if (hasFiles) {
- input.setDragging(true)
+ input.setDraggingType("image")
+ } else if (hasText) {
+ input.setDraggingType("@mention")
}
}
const handleGlobalDragLeave = (event: DragEvent) => {
if (input.isDialogActive()) return
if (!event.relatedTarget) {
- input.setDragging(false)
+ input.setDraggingType(null)
}
}
@@ -100,7 +104,16 @@ export function createPromptAttachments(input: PromptAttachmentsInput) {
if (input.isDialogActive()) return
event.preventDefault()
- input.setDragging(false)
+ input.setDraggingType(null)
+
+ const plainText = event.dataTransfer?.getData("text/plain")
+ const filePrefix = "file:"
+ if (plainText?.startsWith(filePrefix)) {
+ const filePath = plainText.slice(filePrefix.length)
+ input.focusEditor()
+ input.addPart({ type: "file", path: filePath, content: "@" + filePath, start: 0, end: 0 })
+ return
+ }
const dropped = event.dataTransfer?.files
if (!dropped) return
diff --git a/packages/app/src/components/prompt-input/drag-overlay.tsx b/packages/app/src/components/prompt-input/drag-overlay.tsx
index f5a4d399e..e05b47d7c 100644
--- a/packages/app/src/components/prompt-input/drag-overlay.tsx
+++ b/packages/app/src/components/prompt-input/drag-overlay.tsx
@@ -2,16 +2,16 @@ import { Component, Show } from "solid-js"
import { Icon } from "@opencode-ai/ui/icon"
type PromptDragOverlayProps = {
- dragging: boolean
+ type: "image" | "@mention" | null
label: string
}
export const PromptDragOverlay: Component<PromptDragOverlayProps> = (props) => {
return (
- <Show when={props.dragging}>
+ <Show when={props.type !== null}>
<div class="absolute inset-0 z-10 flex items-center justify-center bg-surface-raised-stronger-non-alpha/90 pointer-events-none">
<div class="flex flex-col items-center gap-2 text-text-weak">
- <Icon name="photo" class="size-8" />
+ <Icon name={props.type === "@mention" ? "link" : "photo"} class="size-8" />
<span class="text-14-regular">{props.label}</span>
</div>
</div>
diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts
index 77a3edb06..22ee3e998 100644
--- a/packages/app/src/i18n/ar.ts
+++ b/packages/app/src/i18n/ar.ts
@@ -211,6 +211,7 @@ export const dict = {
"prompt.popover.emptyResults": "لا توجد نتائج مطابقة",
"prompt.popover.emptyCommands": "لا توجد أوامر مطابقة",
"prompt.dropzone.label": "أفلت الصور أو ملفات PDF هنا",
+ "prompt.dropzone.file.label": "أفلت لإشارة @ للملف",
"prompt.slash.badge.custom": "مخصص",
"prompt.slash.badge.skill": "مهارة",
"prompt.slash.badge.mcp": "mcp",
diff --git a/packages/app/src/i18n/br.ts b/packages/app/src/i18n/br.ts
index a743a3d89..b1544e0a6 100644
--- a/packages/app/src/i18n/br.ts
+++ b/packages/app/src/i18n/br.ts
@@ -211,6 +211,7 @@ export const dict = {
"prompt.popover.emptyResults": "Nenhum resultado correspondente",
"prompt.popover.emptyCommands": "Nenhum comando correspondente",
"prompt.dropzone.label": "Solte imagens ou PDFs aqui",
+ "prompt.dropzone.file.label": "Solte para @mencionar arquivo",
"prompt.slash.badge.custom": "personalizado",
"prompt.slash.badge.skill": "skill",
"prompt.slash.badge.mcp": "mcp",
diff --git a/packages/app/src/i18n/bs.ts b/packages/app/src/i18n/bs.ts
index ce37989c2..ddfd31048 100644
--- a/packages/app/src/i18n/bs.ts
+++ b/packages/app/src/i18n/bs.ts
@@ -219,6 +219,7 @@ export const dict = {
"prompt.popover.emptyResults": "Nema rezultata",
"prompt.popover.emptyCommands": "Nema komandi",
"prompt.dropzone.label": "Spusti slike ili PDF-ove ovdje",
+ "prompt.dropzone.file.label": "Spusti za @spominjanje datoteke",
"prompt.slash.badge.custom": "prilagođeno",
"prompt.slash.badge.skill": "skill",
"prompt.slash.badge.mcp": "mcp",
diff --git a/packages/app/src/i18n/da.ts b/packages/app/src/i18n/da.ts
index 88704607b..d8860b16b 100644
--- a/packages/app/src/i18n/da.ts
+++ b/packages/app/src/i18n/da.ts
@@ -211,6 +211,7 @@ export const dict = {
"prompt.popover.emptyResults": "Ingen matchende resultater",
"prompt.popover.emptyCommands": "Ingen matchende kommandoer",
"prompt.dropzone.label": "Slip billeder eller PDF'er her",
+ "prompt.dropzone.file.label": "Slip for at @nævne fil",
"prompt.slash.badge.custom": "brugerdefineret",
"prompt.slash.badge.skill": "skill",
"prompt.slash.badge.mcp": "mcp",
diff --git a/packages/app/src/i18n/de.ts b/packages/app/src/i18n/de.ts
index a4d12d445..f1ff6e8c0 100644
--- a/packages/app/src/i18n/de.ts
+++ b/packages/app/src/i18n/de.ts
@@ -253,6 +253,7 @@ export const dict = {
"prompt.popover.emptyResults": "Keine passenden Ergebnisse",
"prompt.popover.emptyCommands": "Keine passenden Befehle",
"prompt.dropzone.label": "Bilder oder PDFs hier ablegen",
+ "prompt.dropzone.file.label": "Ablegen zum @Erwähnen der Datei",
"prompt.slash.badge.custom": "benutzerdefiniert",
"prompt.slash.badge.skill": "skill",
"prompt.slash.badge.mcp": "mcp",
diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts
index 4d7d571af..9e6dd35de 100644
--- a/packages/app/src/i18n/en.ts
+++ b/packages/app/src/i18n/en.ts
@@ -256,6 +256,7 @@ export const dict = {
"prompt.popover.emptyResults": "No matching results",
"prompt.popover.emptyCommands": "No matching commands",
"prompt.dropzone.label": "Drop images or PDFs here",
+ "prompt.dropzone.file.label": "Drop to @mention file",
"prompt.slash.badge.custom": "custom",
"prompt.slash.badge.skill": "skill",
"prompt.slash.badge.mcp": "mcp",
diff --git a/packages/app/src/i18n/es.ts b/packages/app/src/i18n/es.ts
index 5d48ba494..0d095c1ea 100644
--- a/packages/app/src/i18n/es.ts
+++ b/packages/app/src/i18n/es.ts
@@ -211,6 +211,7 @@ export const dict = {
"prompt.popover.emptyResults": "Sin resultados coincidentes",
"prompt.popover.emptyCommands": "Sin comandos coincidentes",
"prompt.dropzone.label": "Suelta imágenes o PDFs aquí",
+ "prompt.dropzone.file.label": "Suelta para @mencionar archivo",
"prompt.slash.badge.custom": "personalizado",
"prompt.slash.badge.skill": "skill",
"prompt.slash.badge.mcp": "mcp",
diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts
index a76e57ff1..06f18f063 100644
--- a/packages/app/src/i18n/fr.ts
+++ b/packages/app/src/i18n/fr.ts
@@ -211,6 +211,7 @@ export const dict = {
"prompt.popover.emptyResults": "Aucun résultat correspondant",
"prompt.popover.emptyCommands": "Aucune commande correspondante",
"prompt.dropzone.label": "Déposez des images ou des PDF ici",
+ "prompt.dropzone.file.label": "Déposez pour @mentionner le fichier",
"prompt.slash.badge.custom": "personnalisé",
"prompt.slash.badge.skill": "skill",
"prompt.slash.badge.mcp": "mcp",
diff --git a/packages/app/src/i18n/ja.ts b/packages/app/src/i18n/ja.ts
index e41dea9dc..c4bd09b71 100644
--- a/packages/app/src/i18n/ja.ts
+++ b/packages/app/src/i18n/ja.ts
@@ -210,6 +210,7 @@ export const dict = {
"prompt.popover.emptyResults": "一致する結果がありません",
"prompt.popover.emptyCommands": "一致するコマンドがありません",
"prompt.dropzone.label": "画像またはPDFをここにドロップ",
+ "prompt.dropzone.file.label": "ドロップして@メンションファイルを追加",
"prompt.slash.badge.custom": "カスタム",
"prompt.slash.badge.skill": "スキル",
"prompt.slash.badge.mcp": "mcp",
diff --git a/packages/app/src/i18n/ko.ts b/packages/app/src/i18n/ko.ts
index a4f42a583..5b1ac21e2 100644
--- a/packages/app/src/i18n/ko.ts
+++ b/packages/app/src/i18n/ko.ts
@@ -214,6 +214,7 @@ export const dict = {
"prompt.popover.emptyResults": "일치하는 결과 없음",
"prompt.popover.emptyCommands": "일치하는 명령어 없음",
"prompt.dropzone.label": "이미지나 PDF를 여기에 드롭하세요",
+ "prompt.dropzone.file.label": "드롭하여 파일 @멘션 추가",
"prompt.slash.badge.custom": "사용자 지정",
"prompt.slash.badge.skill": "스킬",
"prompt.slash.badge.mcp": "mcp",
diff --git a/packages/app/src/i18n/no.ts b/packages/app/src/i18n/no.ts
index 3de7837f8..d167d7d9c 100644
--- a/packages/app/src/i18n/no.ts
+++ b/packages/app/src/i18n/no.ts
@@ -214,6 +214,7 @@ export const dict = {
"prompt.popover.emptyResults": "Ingen matchende resultater",
"prompt.popover.emptyCommands": "Ingen matchende kommandoer",
"prompt.dropzone.label": "Slipp bilder eller PDF-er her",
+ "prompt.dropzone.file.label": "Slipp for å @nevne fil",
"prompt.slash.badge.custom": "egendefinert",
"prompt.slash.badge.skill": "skill",
"prompt.slash.badge.mcp": "mcp",
diff --git a/packages/app/src/i18n/pl.ts b/packages/app/src/i18n/pl.ts
index 44bc4677b..90a1aa230 100644
--- a/packages/app/src/i18n/pl.ts
+++ b/packages/app/src/i18n/pl.ts
@@ -211,6 +211,7 @@ export const dict = {
"prompt.popover.emptyResults": "Brak pasujących wyników",
"prompt.popover.emptyCommands": "Brak pasujących poleceń",
"prompt.dropzone.label": "Upuść obrazy lub pliki PDF tutaj",
+ "prompt.dropzone.file.label": "Upuść, aby @wspomnieć plik",
"prompt.slash.badge.custom": "własne",
"prompt.slash.badge.skill": "skill",
"prompt.slash.badge.mcp": "mcp",
diff --git a/packages/app/src/i18n/ru.ts b/packages/app/src/i18n/ru.ts
index 28785c0e9..22f1af639 100644
--- a/packages/app/src/i18n/ru.ts
+++ b/packages/app/src/i18n/ru.ts
@@ -211,6 +211,7 @@ export const dict = {
"prompt.popover.emptyResults": "Нет совпадений",
"prompt.popover.emptyCommands": "Нет совпадающих команд",
"prompt.dropzone.label": "Перетащите изображения или PDF сюда",
+ "prompt.dropzone.file.label": "Отпустите для @упоминания файла",
"prompt.slash.badge.custom": "своё",
"prompt.slash.badge.skill": "навык",
"prompt.slash.badge.mcp": "mcp",
diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts
index 9858f39d7..f7f1ad261 100644
--- a/packages/app/src/i18n/th.ts
+++ b/packages/app/src/i18n/th.ts
@@ -216,6 +216,7 @@ export const dict = {
"prompt.popover.emptyResults": "ไม่พบผลลัพธ์ที่ตรงกัน",
"prompt.popover.emptyCommands": "ไม่พบคำสั่งที่ตรงกัน",
"prompt.dropzone.label": "วางรูปภาพหรือ PDF ที่นี่",
+ "prompt.dropzone.file.label": "วางเพื่อ @กล่าวถึงไฟล์",
"prompt.slash.badge.custom": "กำหนดเอง",
"prompt.slash.badge.skill": "skill",
"prompt.slash.badge.mcp": "mcp",
diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts
index a8fda6f3a..e86f9aa5d 100644
--- a/packages/app/src/i18n/zh.ts
+++ b/packages/app/src/i18n/zh.ts
@@ -252,6 +252,7 @@ export const dict = {
"prompt.popover.emptyResults": "没有匹配的结果",
"prompt.popover.emptyCommands": "没有匹配的命令",
"prompt.dropzone.label": "将图片或 PDF 拖到这里",
+ "prompt.dropzone.file.label": "拖放以 @提及文件",
"prompt.slash.badge.custom": "自定义",
"prompt.slash.badge.skill": "技能",
"prompt.slash.badge.mcp": "mcp",
diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts
index 319f5c51d..458000fa4 100644
--- a/packages/app/src/i18n/zht.ts
+++ b/packages/app/src/i18n/zht.ts
@@ -249,6 +249,7 @@ export const dict = {
"prompt.popover.emptyResults": "沒有符合的結果",
"prompt.popover.emptyCommands": "沒有符合的命令",
"prompt.dropzone.label": "將圖片或 PDF 拖到這裡",
+ "prompt.dropzone.file.label": "拖放以 @提及檔案",
"prompt.slash.badge.custom": "自訂",
"prompt.slash.badge.skill": "技能",
"prompt.slash.badge.mcp": "mcp",