diff options
| author | Devin Griffin <[email protected]> | 2026-02-07 13:33:00 -0600 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-02-07 13:33:00 -0600 |
| commit | 6bdd3528ac1b1824bd560678366ac82bb777db9c (patch) | |
| tree | 531fb5a371700fc76390e6055787282c3ace2ae9 /packages/app/src/components | |
| parent | 4efbfcd08735bece0a3e479f23296871780a01b4 (diff) | |
| download | opencode-6bdd3528ac1b1824bd560678366ac82bb777db9c.tar.gz opencode-6bdd3528ac1b1824bd560678366ac82bb777db9c.zip | |
feat(app): drag-n-drop to @mention file (#12569)
Diffstat (limited to 'packages/app/src/components')
| -rw-r--r-- | packages/app/src/components/prompt-input.tsx | 17 | ||||
| -rw-r--r-- | packages/app/src/components/prompt-input/attachments.ts | 21 | ||||
| -rw-r--r-- | packages/app/src/components/prompt-input/drag-overlay.tsx | 6 |
3 files changed, 32 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> |
