diff --git a/README.md b/README.md index 1b64902..c9adb72 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,11 @@ * **고속 워크플로우 (Instant Edit)**: 메모 카드 위에 마우스를 올리고 `e`를 누르면 즉시 수정 모드 진입. 모달 클릭의 피로감을 제로로 만듭니다. * **드래그 앤 드롭 링크**: 메모 카드를 작성기(Composer)로 드래그하여 즉시 참조 링크를 삽입하세요. +### 🛠️ 패치 노트 (2026-04-19) +* **파일 첨부 접근성 개선**: 지식 작성기(Composer)에 명시적인 파일 첨부(📎) 버튼을 추가했습니다. (드래그 앤 드롭과 병행 가능) +* **모바일 UX 최적화**: 모바일 기기에서도 조작이 편리하도록 "파일추가" 텍스트 레이블을 추가했습니다. +* **다국어 지원 안정화**: 첨부파일 관련 툴팁 및 레이블에 한/영 i18n을 적용했습니다. + --- ## 🆚 memos vs 뇌사료 (Comparison) @@ -127,6 +132,11 @@ We provide a security model where user data is practically undecipherable. Built - **Instant Edit (e-key)**: Hover over a memo and press `e` to jump straight into editing mode. Zero-click productivity. - **Drag & Drop Workflow**: Drag memo cards into the composer to instantly insert a semantic reference. +### 🛠️ Patch Notes (2026-04-19) +- **Improved Attachment Accessibility**: Added a dedicated Attach File (📎) button to the Composer. +- **Mobile UI Optimization**: Added an "Add File" text label next to the icon on mobile devices for better touch usability. +- **I18n Stabilization**: Implemented full Korean/English translation for all attachment-related UI elements. + --- ## 🆚 memos vs Brain Dogfood (Comparison) diff --git a/docs/Bug/20260418_missing_brain_py.md b/docs/Bug/20260418_missing_brain_py.md deleted file mode 100644 index 6024304..0000000 --- a/docs/Bug/20260418_missing_brain_py.md +++ /dev/null @@ -1,13 +0,0 @@ -# 버그 리포트: Stable 레포지토리 brain.py 누락 - -## 버그 내용 -- `brain_dogfood_stable` 레포지토리에 Flask 애플리케이션의 진입점인 `brain.py` 파일이 누락되어 서버 구동이 불가능했던 현상. - -## 조치 사항 -1. 루트 디렉토리의 `brain.py` 파일을 `brain_dogfood_stable` 디렉토리로 복사. -2. Git commit 및 원격 저장소(`origin main`)에 푸시 완료. -3. `tools/sync_stable.py`와 `tools/final_audit.py`에서 `brain.py`를 제외 목록에서 삭제하여 향후 자동 동기화되도록 조치. - -## 향후 주의사항 -- 배포용 레포지토리(stable) 업데이트 시, 실행에 필수적인 진입점 파일(`brain.py`)이 포함되어 있는지 반드시 확인해야 함. -- 동기화 도구(`sync_stable.py`)의 제외 목록(`EXCLUDE_FILES`)을 수정할 때는 서비스 코어 파일이 포함되지 않도록 주의 필요. diff --git a/docs/Bug/20260419_attachment_button_fix.md b/docs/Bug/20260419_attachment_button_fix.md new file mode 100644 index 0000000..adef133 --- /dev/null +++ b/docs/Bug/20260419_attachment_button_fix.md @@ -0,0 +1,28 @@ +# 💡 지식 작성기(Composer) 파일 첨부 버튼 누락 및 모바일 UI 개선 + +- **날짜**: 2026-04-19 +- **작성자**: Antigravity (AI Agent) + +## 1. 개요 +지식 작성기(Composer)에서 드래그 앤 드롭 방식 외에 파일을 직접 선택할 수 있는 물리적 버튼이 없어 접근성이 낮음. 특히 모바일 환경에서 아이콘만 있을 경우 조작 오탐점이 높고 가시성이 떨어지는 문제 발생. + +## 2. 조치 사항 +- **UI 개선**: + - `templates/components/composer.html` 내부에 클립(📎) 아이콘 버튼 및 숨겨진 `input[type="file"]` 추가. + - 모바일 해상도(768px 이하)에서만 나타나는 텍스트 레이블("파일추가") 추가. +- **다국어 지원(i18n)**: + - `static/locales/ko.json`, `en.json`에 `composer_attach_file`, `composer_attach_label` 키 추가. +- **로직 최적화**: + - `static/js/editor.js`: 드롭 이벤트와 버튼 클릭 이벤트를 공통으로 처리하기 위한 `handleFiles()` 함수 추출. + - `static/js/components/ComposerManager.js`: 신규 버튼과 파일 인풋 간의 이벤트 바인딩 처리. +- **스타일링**: + - `static/css/layout.css`: 기본 레이블 숨김 처리. + - `static/css/mobile.css`: 모바일 환경에서 레이블 노출 및 터치 영역 최적화. + +## 3. 결과 및 검증 +- 홈 서버 배포 완료. +- 데스크톱: 툴팁 지원 및 깔끔한 아이콘 UI 확인. +- 모바일: 텍스트 레이블 노출로 조작 편의성 증대 확인. + +## 4. 향후 주의 사항 +- `action-btn` 스타일 변경 시 모바일에서의 `min-width`, `min-height`가 `44px` 이상 유지되는지 확인 필요 (Apple/Google UI 가이드라인 준수). diff --git a/static/css/layout.css b/static/css/layout.css index 8a6fa42..3795199 100644 --- a/static/css/layout.css +++ b/static/css/layout.css @@ -104,6 +104,7 @@ } .action-btn:hover { background: rgba(184, 59, 94, 0.8); } +.btn-label { display: none; } .ai-btn:hover { background: var(--ai-accent); color: white; } /* Memo Footer Metadata Styling */ diff --git a/static/css/mobile.css b/static/css/mobile.css index de89a95..1653eac 100644 --- a/static/css/mobile.css +++ b/static/css/mobile.css @@ -33,6 +33,7 @@ .memo-actions { opacity: 1 !important; bottom: 8px; right: 8px; } .action-btn { padding: 8px 12px; font-size: 1rem; min-width: 44px; min-height: 44px; display: flex; align-items: center; justify-content: center; } + .btn-label { display: inline; margin-left: 5px; font-size: 0.9rem; font-weight: bold; } .modal-content { width: 95%; max-height: 90vh; border-radius: 12px; } .tag-badge, .group-badge { padding: 4px 10px; font-size: 0.85rem; } diff --git a/static/js/components/ComposerManager.js b/static/js/components/ComposerManager.js index 6e89f42..b9a3c31 100644 --- a/static/js/components/ComposerManager.js +++ b/static/js/components/ComposerManager.js @@ -28,7 +28,9 @@ export const ComposerManager = { foldBtn: document.getElementById('foldBtn'), discardBtn: document.getElementById('discardBtn'), deleteBtn: document.getElementById('deleteMemoBtn'), // NEW - categoryBar: document.getElementById('composerCategoryBar') + categoryBar: document.getElementById('composerCategoryBar'), + attachBtn: document.getElementById('attachBtn'), + fileInput: document.getElementById('composerFileInput') }; if (!this.DOM.composer || !this.DOM.trigger) return; @@ -71,6 +73,18 @@ export const ComposerManager = { this.DOM.encryptionToggle.onclick = () => this.toggleEncryption(); this.initShortcutHint(); + // 💡 파일 첨부 버튼 연동 + if (this.DOM.attachBtn && this.DOM.fileInput) { + this.DOM.attachBtn.onclick = () => this.DOM.fileInput.click(); + this.DOM.fileInput.onchange = (e) => { + const files = e.target.files; + if (files.length > 0) { + EditorManager.handleFiles(files); + e.target.value = ''; // 같은 파일 다시 올릴 수 있게 초기화 + } + }; + } + // 2. 자동 임시저장 및 키보드 리스너 등록 this.draftTimer = setInterval(() => this.saveDraft(), 3000); ComposerDraft.checkRestore((draft) => this.restoreDraft(draft)); diff --git a/static/js/editor.js b/static/js/editor.js index f07100c..c18e67b 100644 --- a/static/js/editor.js +++ b/static/js/editor.js @@ -147,37 +147,46 @@ export const EditorManager = { if (!files || files.length === 0) return; // 에디터가 닫혀있다면 상위에서 열어줘야 함 - onDropComplete(true); + if (onDropComplete) onDropComplete(true); - for (let file of files) { - try { - const data = await API.uploadFile(file); - if (data.url) { - const filename = data.url.split('/').pop(); - const isImg = ['png','jpg','jpeg','gif','webp','svg'].includes(data.ext?.toLowerCase()); - const name = data.name || 'file'; - - // Ensure editor is focused before inserting - this.editor.focus(); - - if (isImg) { - this.editor.exec('addImage', { altText: name, imageUrl: data.url }); - } - - // 공통: 첨부 파일 목록에 추가 및 UI 갱신 - this.attachedFiles.push({ - filename: filename, - original_name: name, - file_type: file.type - }); - this.sessionFiles.add(filename); // 세션 트래킹 추가 - this.refreshAttachmentUI(); - } - } catch (err) { console.error(err); } - } + await this.handleFiles(files); }); }, + /** + * 다중 파일을 처리(업로드 및 UI 반영)함 + */ + async handleFiles(files) { + if (!files || files.length === 0) return; + + for (let file of files) { + try { + const data = await API.uploadFile(file); + if (data.url) { + const filename = data.url.split('/').pop(); + const isImg = ['png','jpg','jpeg','gif','webp','svg'].includes(data.ext?.toLowerCase()); + const name = data.name || 'file'; + + // Ensure editor is focused before inserting + this.editor.focus(); + + if (isImg) { + this.editor.exec('addImage', { altText: name, imageUrl: data.url }); + } + + // 공통: 첨부 파일 목록에 추가 및 UI 갱신 + this.attachedFiles.push({ + filename: filename, + original_name: name, + file_type: file.type + }); + this.sessionFiles.add(filename); // 세션 트래킹 추가 + this.refreshAttachmentUI(); + } + } catch (err) { console.error('[Editor] File process error:', err); } + } + }, + getAttachedFilenames() { return this.attachedFiles.map(f => f.filename); }, diff --git a/static/locales/ko.json b/static/locales/ko.json index b58dd8d..34a77bf 100644 --- a/static/locales/ko.json +++ b/static/locales/ko.json @@ -23,6 +23,8 @@ "composer_save": "메모 저장", "composer_discard": "취소 (삭제)", "composer_encrypt": "암호화", + "composer_attach_file": "파일 첨부", + "composer_attach_label": "파일추가", "composer_password": "비밀번호", "tooltip_fold": "창 접기 (내용 보존)", diff --git a/templates/components/composer.html b/templates/components/composer.html index 8825635..e9adcb5 100644 --- a/templates/components/composer.html +++ b/templates/components/composer.html @@ -17,6 +17,10 @@ + +