From fcaf1df716458eb021b1a2a8e00f033101d3f244 Mon Sep 17 00:00:00 2001 From: liuxuezhuo Date: Wed, 30 Sep 2026 10:27:23 +0800 Subject: [PATCH] feat(composer): move selection chips below the textarea The top context row now carries only the image preview. Editor, browser and canvas selection chips render in a new row between the textarea and the toolbar, so highlighting text no longer inserts a strip above the typing area. The overflow collapse and the has-content show/hide logic follow the chips to the new row; image changes refresh the top row directly. Co-authored-by: QoderAI (Qwen 3.8 Max) --- src/features/chat/tabs/tab-dom.ts | 2 ++ src/features/chat/tabs/tab.ts | 18 ++++++++++-------- src/features/chat/tabs/types.ts | 5 ++++- src/style/components/input.css | 26 +++++++++++++++++++------- 4 files changed, 35 insertions(+), 16 deletions(-) diff --git a/src/features/chat/tabs/tab-dom.ts b/src/features/chat/tabs/tab-dom.ts index 6d70b73..f7dd6ee 100644 --- a/src/features/chat/tabs/tab-dom.ts +++ b/src/features/chat/tabs/tab-dom.ts @@ -21,6 +21,7 @@ export function buildTabDOM(contentEl: HTMLElement): TabDOMElements { dir: 'auto', }, }); + const selectionRowEl = inputWrapper.createDiv({ cls: 'qoderian-selection-row' }); return { contentEl, @@ -35,6 +36,7 @@ export function buildTabDOM(contentEl: HTMLElement): TabDOMElements { inputEl, navRowEl, contextRowEl, + selectionRowEl, selectionIndicatorEl: null, browserIndicatorEl: null, canvasIndicatorEl: null, diff --git a/src/features/chat/tabs/tab.ts b/src/features/chat/tabs/tab.ts index d814c30..bb389a7 100644 --- a/src/features/chat/tabs/tab.ts +++ b/src/features/chat/tabs/tab.ts @@ -19,6 +19,7 @@ import { openReferenceChip } from '../../../shared/obsidian/compat'; import { BrowserSelectionController } from '../controllers/browser-selection-controller'; import { CanvasSelectionController } from '../controllers/canvas-selection-controller'; import { ContextRowOverflowController } from '../controllers/context-row-overflow'; +import { updateContextRowHasContent } from '../controllers/context-row-visibility'; import { ConversationController } from '../controllers/conversation-controller'; import { InputController } from '../controllers/input-controller'; import { NavigationController } from '../controllers/navigation-controller'; @@ -305,6 +306,7 @@ function initializeContextManagers(tab: TabData, plugin: QoderianPlugin): void { tab.controllers.selectionController?.updateContextRowVisibility(); tab.controllers.browserSelectionController?.updateContextRowVisibility(); tab.controllers.canvasSelectionController?.updateContextRowVisibility(); + updateContextRowHasContent(dom.contextRowEl); autoResizeTextarea(dom.inputEl); tab.renderer?.scrollToBottomIfNeeded(); updateComposerSendAvailability(tab); @@ -590,27 +592,27 @@ export function initializeTabUI( // Initialize context managers (file/image) initializeContextManagers(tab, plugin); - // Selection chips - add to contextRowEl (pill style: icon + label + remove) + // Selection chips - add to selectionRowEl below the textarea (pill style: icon + label + remove) dom.selectionIndicatorEl = createSelectionChip( - dom.contextRowEl, + dom.selectionRowEl, 'qoderian-selection-indicator', 'text-select' ); dom.browserIndicatorEl = createSelectionChip( - dom.contextRowEl, + dom.selectionRowEl, 'qoderian-browser-selection-indicator', 'globe' ); dom.canvasIndicatorEl = createSelectionChip( - dom.contextRowEl, + dom.selectionRowEl, 'qoderian-canvas-indicator', 'network' ); // Collapse chips into "+N more" when the sidebar is too narrow. - tab.controllers.contextRowOverflow = new ContextRowOverflowController(dom.contextRowEl); + tab.controllers.contextRowOverflow = new ContextRowOverflowController(dom.selectionRowEl); const catalogInfo = options.getQoderCatalogConfig?.() ?? null; initializeSlashCommands( @@ -836,7 +838,7 @@ export function initializeTabControllers( plugin.app, dom.selectionIndicatorEl!, dom.inputEl, - dom.contextRowEl, + dom.selectionRowEl, () => autoResizeTextarea(dom.inputEl), [dom.contentEl, dom.inputComposerEl, ...getSharedSelectionFocusScopeEls(component)], ); @@ -845,7 +847,7 @@ export function initializeTabControllers( plugin.app, dom.browserIndicatorEl!, dom.inputEl, - dom.contextRowEl, + dom.selectionRowEl, () => autoResizeTextarea(dom.inputEl) ); @@ -853,7 +855,7 @@ export function initializeTabControllers( plugin.app, dom.canvasIndicatorEl!, dom.inputEl, - dom.contextRowEl, + dom.selectionRowEl, () => autoResizeTextarea(dom.inputEl) ); diff --git a/src/features/chat/tabs/types.ts b/src/features/chat/tabs/types.ts index f48ee35..65061a4 100644 --- a/src/features/chat/tabs/types.ts +++ b/src/features/chat/tabs/types.ts @@ -158,9 +158,12 @@ export interface TabDOMElements { /** Nav row for tab badges and header icons (above input wrapper). */ navRowEl: HTMLElement; - /** Context row for file chips and selection indicator (inside input wrapper). */ + /** Context row for the image preview (inside input wrapper, above textarea). */ contextRowEl: HTMLElement; + /** Row for selection chips (inside input wrapper, below textarea). */ + selectionRowEl: HTMLElement; + selectionIndicatorEl: HTMLElement | null; browserIndicatorEl: HTMLElement | null; canvasIndicatorEl: HTMLElement | null; diff --git a/src/style/components/input.css b/src/style/components/input.css index 923f414..bff82a7 100644 --- a/src/style/components/input.css +++ b/src/style/components/input.css @@ -85,33 +85,45 @@ body.qoderian-composer-resizing * { user-select: none; } -/* Context row (context chips: files, images, selections) - inside input wrapper at top */ +/* Context row (image preview) above the textarea and selection row (selection + chips) below it - both inside the input wrapper */ /* Collapsed by default; expanded via .has-content class; textarea fills remaining space */ -.qoderian-context-row { +.qoderian-context-row, +.qoderian-selection-row { display: none; position: relative; align-items: flex-start; justify-content: flex-start; flex-shrink: 0; - padding: 6px 10px 0 10px; gap: 8px; } -/* Show context row when it has visible content */ -.qoderian-context-row.has-content { +.qoderian-context-row { + padding: 6px 10px 0 10px; +} + +.qoderian-selection-row { + padding: 0 10px 6px 10px; +} + +/* Show a row when it has visible content */ +.qoderian-context-row.has-content, +.qoderian-selection-row.has-content { display: flex; } /* Overflow collapse: chips that do not fit a narrow row are parked behind a "+N more" pill. Hidden items stay measurable (absolute + invisible). */ -.qoderian-context-row > .qoderian-context-overflow-hidden { +.qoderian-context-row > .qoderian-context-overflow-hidden, +.qoderian-selection-row > .qoderian-context-overflow-hidden { position: absolute; visibility: hidden; pointer-events: none; } /* Expanded state (pill clicked): wrap so every chip stays reachable. */ -.qoderian-context-row.qoderian-context-row--expanded { +.qoderian-context-row.qoderian-context-row--expanded, +.qoderian-selection-row.qoderian-context-row--expanded { flex-wrap: wrap; }