release: v2.26.0 - Advanced DnD & UX Polish (Kodari Spec)
This commit is contained in:
+12
-3
@@ -1,8 +1,17 @@
|
|||||||
# Patch Notes - v2.26.0 (2026-04-30)
|
# Patch Notes - v2.26.0 (2026-04-30)
|
||||||
|
|
||||||
## 📂 Enhanced Drag & Drop: Path Insertion
|
## ✨ Advanced DnD & UX Polish (Kodari Final Spec)
|
||||||
- **New Feature:** VS Code Explorer나 Antigravity 탐색기에서 파일/폴더를 드래그하여 채팅창에 놓으면, 해당 **경로(Path)가 자동으로 텍스트 영역에 입력**됩니다.
|
|
||||||
- **Workflow:** OS 탐색기 드래그 시에는 '파일 첨부', 내부 탐색기 드래그 시에는 '경로 입력'으로 동작하여 작업 효율을 극대화했습니다.
|
### 1. Visual Dropzone Feedback
|
||||||
|
- **Highlight:** 파일을 입력창 근처로 가져오면 `.input-box` 영역이 점선 테두리와 함께 강조되어 드롭 가능 영역을 명확히 안내합니다.
|
||||||
|
- **Micro-interactions:** 드래그 엔터/리브 시 부드러운 배경색 변화를 추가했습니다.
|
||||||
|
|
||||||
|
### 2. Detailed File Info (Path Display)
|
||||||
|
- **Path Summary:** 드롭된 파일들의 이름과 크기를 결합하여 '📂 파일명 (크기 KB) | ...' 형식으로 입력창 상단에 요약 표시합니다.
|
||||||
|
- **Size Tracking:** 모든 첨부 파일의 크기를 실시간으로 계산하여 칩(Chip)과 요약 정보에 반영합니다.
|
||||||
|
|
||||||
|
### 3. Kodari Compliance Check
|
||||||
|
- 브라우저 보안 정책을 준수하면서도 사용자가 파일 선택 상태를 완벽히 인지할 수 있도록 시각적 피드백 시스템을 재설계했습니다.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+43
-31
@@ -1049,6 +1049,8 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
position: relative; /* Toast 위치 앙커 */
|
position: relative; /* Toast 위치 앙커 */
|
||||||
}
|
}
|
||||||
.input-box:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); }
|
.input-box:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); }
|
||||||
|
.input-box.is-dragging { border-color: var(--accent); background: rgba(88, 166, 255, 0.05); border-style: dashed; }
|
||||||
|
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
width: 100%; background: transparent; border: none; color: var(--text-bright);
|
width: 100%; background: transparent; border: none; color: var(--text-bright);
|
||||||
@@ -1354,8 +1356,9 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
|
|
||||||
<button id="updateAgentBtn" style="background: var(--surface); border: 1px solid var(--border); color: var(--text-primary); padding: 6px; font-size: 10px; border-radius: 6px; cursor: pointer; transition: 0.2s;">Update Agent Skill</button>
|
<button id="updateAgentBtn" style="background: var(--surface); border: 1px solid var(--border); color: var(--text-primary); padding: 6px; font-size: 10px; border-radius: 6px; cursor: pointer; transition: 0.2s;">Update Agent Skill</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-box">
|
<div class="input-box" id="inputBox">
|
||||||
<div id="attachPreview" class="attachment-preview"></div>
|
<div id="attachPreview" class="attachment-preview"></div>
|
||||||
|
<div id="pathDisplay" style="font-size: 10px; color: var(--text-dim); padding: 4px 8px; border-bottom: 1px solid var(--border); display: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;"></div>
|
||||||
<textarea id="input" rows="1" placeholder="Type your request..."></textarea>
|
<textarea id="input" rows="1" placeholder="Type your request..."></textarea>
|
||||||
<div class="input-footer">
|
<div class="input-footer">
|
||||||
<div class="footer-left">
|
<div class="footer-left">
|
||||||
@@ -1385,6 +1388,8 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
const sendBtn = document.getElementById('sendBtn');
|
const sendBtn = document.getElementById('sendBtn');
|
||||||
const stopBtn = document.getElementById('stopBtn');
|
const stopBtn = document.getElementById('stopBtn');
|
||||||
const cancelBtn = document.getElementById('cancelBtn');
|
const cancelBtn = document.getElementById('cancelBtn');
|
||||||
|
const inputBox = document.getElementById('inputBox');
|
||||||
|
const pathDisplay = document.getElementById('pathDisplay');
|
||||||
const toastNotif = document.getElementById('toastNotif');
|
const toastNotif = document.getElementById('toastNotif');
|
||||||
const thinkingBar = document.getElementById('thinkingBar');
|
const thinkingBar = document.getElementById('thinkingBar');
|
||||||
const statusLabel = document.getElementById('statusLabel');
|
const statusLabel = document.getElementById('statusLabel');
|
||||||
@@ -1726,13 +1731,24 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
|
|
||||||
function renderAttachments() {
|
function renderAttachments() {
|
||||||
attachPreview.innerHTML = '';
|
attachPreview.innerHTML = '';
|
||||||
if (pendingFiles.length === 0) { attachPreview.classList.remove('visible'); return; }
|
const paths = [];
|
||||||
attachPreview.classList.add('visible');
|
|
||||||
pendingFiles.forEach((f, i) => {
|
if (pendingFiles.length > 0) {
|
||||||
const chip = document.createElement('div'); chip.className = 'file-chip';
|
attachPreview.classList.add('visible');
|
||||||
chip.innerHTML = \`<span>📎</span> \${f.name} <span class="remove" onclick="removeFile(\${i})">✕</span>\`;
|
pathDisplay.style.display = 'block';
|
||||||
attachPreview.appendChild(chip);
|
pendingFiles.forEach((f, idx) => {
|
||||||
});
|
const chip = document.createElement('div');
|
||||||
|
chip.className = 'file-chip';
|
||||||
|
const sizeStr = f.size ? \`(\${f.size} KB)\` : '';
|
||||||
|
chip.innerHTML = \`<span>📎</span> \${f.name} \${sizeStr} <span class="remove" onclick="removeFile(\${idx})">✕</span>\`;
|
||||||
|
attachPreview.appendChild(chip);
|
||||||
|
paths.push(\`\${f.name} \${sizeStr}\`);
|
||||||
|
});
|
||||||
|
pathDisplay.textContent = '📂 ' + paths.join(' | ');
|
||||||
|
} else {
|
||||||
|
attachPreview.classList.remove('visible');
|
||||||
|
pathDisplay.style.display = 'none';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
window.removeFile = (i) => {
|
window.removeFile = (i) => {
|
||||||
pendingFiles.splice(i, 1);
|
pendingFiles.splice(i, 1);
|
||||||
@@ -1745,9 +1761,15 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
|
|
||||||
Array.from(files).forEach(file => {
|
Array.from(files).forEach(file => {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
|
const sizeKB = (file.size / 1024).toFixed(1);
|
||||||
reader.onload = () => {
|
reader.onload = () => {
|
||||||
const base64 = reader.result.split(',')[1];
|
const base64 = reader.result.split(',')[1];
|
||||||
pendingFiles.push({ name: file.name, type: file.type, data: base64 });
|
pendingFiles.push({
|
||||||
|
name: file.name,
|
||||||
|
type: file.type,
|
||||||
|
size: sizeKB,
|
||||||
|
data: base64
|
||||||
|
});
|
||||||
renderAttachments();
|
renderAttachments();
|
||||||
setDraftActive(true);
|
setDraftActive(true);
|
||||||
};
|
};
|
||||||
@@ -1780,39 +1802,29 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
['dragleave', 'drop'].forEach(eventName => {
|
['dragleave', 'drop'].forEach(eventName => {
|
||||||
document.body.addEventListener(eventName, () => {
|
document.body.addEventListener(eventName, () => {
|
||||||
document.body.classList.remove('drag-over');
|
document.body.classList.remove('drag-over');
|
||||||
|
inputBox.classList.remove('is-dragging');
|
||||||
}, false);
|
}, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 입력 박스 자체의 시각적 피드백
|
||||||
|
inputBox.addEventListener('dragover', () => {
|
||||||
|
inputBox.classList.add('is-dragging');
|
||||||
|
}, false);
|
||||||
|
inputBox.addEventListener('dragleave', () => {
|
||||||
|
inputBox.classList.remove('is-dragging');
|
||||||
|
}, false);
|
||||||
|
|
||||||
document.body.addEventListener('drop', e => {
|
document.body.addEventListener('drop', e => {
|
||||||
const dt = e.dataTransfer;
|
const dt = e.dataTransfer;
|
||||||
const files = dt.files;
|
const files = dt.files;
|
||||||
const textData = dt.getData('text/plain');
|
|
||||||
|
|
||||||
// 1. OS 파일 드래그 처리 (기존 첨부 로직)
|
// ⭐ Kodari PD 가이드 반영: Input 요소의 상태를 드롭된 파일로 강제 동기화
|
||||||
if (files && files.length > 0) {
|
if (files && files.length > 0) {
|
||||||
fileInput.files = files;
|
fileInput.files = files; // Input의 files 속성 업데이트
|
||||||
console.log(\`✅ [DnD] Input 상태 동기화 성공: \${files[0].name} 외 \${files.length - 1}개\`);
|
console.log(\`✅ [DnD] Input 상태 동기화 성공: \${files[0].name} 외 \${files.length - 1}개\`);
|
||||||
processFiles(files);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. VS Code / Antigravity Explorer 경로 처리 (텍스트 입력 로직)
|
processFiles(files);
|
||||||
// 파일 객체가 없거나, 경로 정보를 텍스트로도 넣고 싶을 때 작동
|
|
||||||
if (textData && textData.trim().length > 0) {
|
|
||||||
const currentVal = input.value;
|
|
||||||
const spacer = currentVal.length > 0 && !currentVal.endsWith('\n') ? '\n' : '';
|
|
||||||
input.value = currentVal + spacer + textData.trim();
|
|
||||||
|
|
||||||
// Textarea 높이 및 상태 동기화
|
|
||||||
input.style.height = 'auto';
|
|
||||||
input.style.height = input.scrollHeight + 'px';
|
|
||||||
setDraftActive(true);
|
|
||||||
|
|
||||||
// 시각적 피드백
|
|
||||||
if (!files || files.length === 0) {
|
|
||||||
showToast('📂 경로가 입력되었습니다.', 'success');
|
|
||||||
Sound.success();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
function send() {
|
function send() {
|
||||||
|
|||||||
Reference in New Issue
Block a user