feat: 수동 편집 URL — 정보 패널에서 Synology Drive 링크 입력/관리

- edit_url 컬럼 추가 (migration 006)
- PreviewPanel: 편집 링크 입력/수정/표시 UI
- DocumentViewer: edit_url 있으면 편집 버튼에서 해당 URL로 새 탭
- API: DocumentResponse/DocumentUpdate에 edit_url 필드

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-04-03 10:37:44 +09:00
parent 4bea408bbd
commit 41072a2e6d
5 changed files with 58 additions and 7 deletions

View File

@@ -37,6 +37,7 @@ class DocumentResponse(BaseModel):
ai_tags: list | None
ai_summary: str | None
user_note: str | None
edit_url: str | None
preview_status: str | None
source_channel: str | None
data_origin: str | None
@@ -63,6 +64,7 @@ class DocumentUpdate(BaseModel):
ai_sub_group: str | None = None
ai_tags: list | None = None
user_note: str | None = None
edit_url: str | None = None
source_channel: str | None = None
data_origin: str | None = None

View File

@@ -47,6 +47,9 @@ class Document(Base):
# 사용자 메모
user_note: Mapped[str | None] = mapped_column(Text)
# 외부 편집 URL
edit_url: Mapped[str | None] = mapped_column(Text)
# 미리보기
preview_status: Mapped[str | None] = mapped_column(String(20), default="none")
preview_hash: Mapped[str | None] = mapped_column(String(64))

View File

@@ -26,12 +26,10 @@
}
function getEditUrl(doc) {
if (['odoc', 'osheet', 'docx', 'xlsx', 'pptx', 'odt', 'ods', 'odp'].includes(doc.file_format)) {
return `https://link.hyungi.net`;
}
if (['dwg', 'dxf'].includes(doc.file_format)) {
return 'https://web.autocad.com';
}
// DB에 저장된 편집 URL 우선
if (doc.edit_url) return doc.edit_url;
// CAD는 AutoCAD Web
if (['dwg', 'dxf'].includes(doc.file_format)) return 'https://web.autocad.com';
return null;
}

View File

@@ -16,12 +16,18 @@
let newTag = $state('');
let tagEditing = $state(false);
// doc 변경 시 메모 초기화
// 편집 URL
let editUrlText = $state('');
let editUrlEditing = $state(false);
// doc 변경 시 초기화
$effect(() => {
if (doc) {
noteText = doc.user_note || '';
editUrlText = doc.edit_url || '';
noteEditing = false;
tagEditing = false;
editUrlEditing = false;
newTag = '';
}
});
@@ -43,6 +49,20 @@
}
}
async function saveEditUrl() {
try {
await api(`/documents/${doc.id}`, {
method: 'PATCH',
body: JSON.stringify({ edit_url: editUrlText.trim() || null }),
});
doc.edit_url = editUrlText.trim() || null;
editUrlEditing = false;
addToast('success', '편집 URL 저장됨');
} catch (err) {
addToast('error', '편집 URL 저장 실패');
}
}
async function addTag() {
const tag = newTag.trim();
if (!tag) return;
@@ -138,6 +158,32 @@
{/if}
</div>
<!-- 편집 URL -->
<div>
<h4 class="text-xs font-semibold text-[var(--text-dim)] uppercase mb-1.5">편집 링크</h4>
{#if editUrlEditing}
<div class="flex gap-1">
<input
bind:value={editUrlText}
placeholder="Synology Drive URL 붙여넣기..."
class="flex-1 px-2 py-1 bg-[var(--bg)] border border-[var(--border)] rounded text-xs text-[var(--text)] outline-none focus:border-[var(--accent)]"
/>
<button onclick={saveEditUrl} class="px-2 py-1 text-xs bg-[var(--accent)] text-white rounded">저장</button>
<button onclick={() => { editUrlEditing = false; editUrlText = doc.edit_url || ''; }} class="px-2 py-1 text-xs text-[var(--text-dim)]">취소</button>
</div>
{:else if doc.edit_url}
<div class="flex items-center gap-1">
<a href={doc.edit_url} target="_blank" class="text-xs text-[var(--accent)] truncate hover:underline">{doc.edit_url}</a>
<button onclick={() => editUrlEditing = true} class="text-[10px] text-[var(--text-dim)] hover:text-[var(--text)]">수정</button>
</div>
{:else}
<button
onclick={() => editUrlEditing = true}
class="text-xs text-[var(--text-dim)] hover:text-[var(--accent)]"
>+ URL 추가</button>
{/if}
</div>
<!-- 태그 -->
<div>
<h4 class="text-xs font-semibold text-[var(--text-dim)] uppercase mb-1.5">태그</h4>

View File

@@ -0,0 +1,2 @@
-- 외부 편집 URL (Synology Drive 공유 링크 등)
ALTER TABLE documents ADD COLUMN IF NOT EXISTS edit_url TEXT;