feat(ui): Phase E — PreviewPanel 분할 + detail inline + viewer Tabs
E.1 PreviewPanel 7개 editors/* 분할:
- frontend/src/lib/components/editors/ 신설 (7개 컴포넌트):
* NoteEditor — 사용자 메모 편집
* EditUrlEditor — 외부 편집 URL (Synology Drive 등)
* TagsEditor — 태그 추가/삭제
* AIClassificationEditor — AI 분류 read-only 표시
(breadcrumb + document_type + confidence tone Badge + importance)
* FileInfoView — 파일 메타 dl
* ProcessingStatusView — 파이프라인 단계 status dl
* DocumentDangerZone — 삭제 (ConfirmDialog 프리미티브 + id 고유화)
- PreviewPanel.svelte 344줄 → 60줄 얇은 wrapper로 축소
(header + 7개 editors 조합만)
- DocumentMetaRail (D.1)과 detail 페이지(E.2)가 동일 editors 재사용
E.2 detail 페이지 inline 편집:
- documents/[id]/+page.svelte: 기존 read-only 메타 패널 전면 교체
- 오른쪽 aside = 7개 editors 스택 (Card 프리미티브로 감쌈)
- 왼쪽 affordance row: Synology 편집 / 다운로드 / 링크 복사
- 삭제는 DocumentDangerZone이 담당 (ondelete → goto /documents)
- loading/error 상태도 EmptyState 프리미티브로 교체
- marked/DOMPurify renderer 유지, viewer 분기 그대로
E.3 관련 문서 stub:
- detail 페이지 오른쪽 aside에 "관련 문서" Card
- EmptyState "추후 지원" + TODO(backend) GET /documents/{id}/related
E.4 DocumentViewer Tabs 프리미티브:
- Markdown 편집 모드의 편집/미리보기 토글 → Tabs 프리미티브
- 키보드 nav (←→/Home/End), ARIA tablist/tab/tabpanel 자동 적용
검증:
- npm run build 통과 (editors/* 7개 모두 clean, $state 초기값
warning은 빈 문자열로 초기화하고 $effect로 doc 동기화해 해결)
- npm run lint:tokens 204 → 168 (detail 페이지 + PreviewPanel 전면
token 기반 재작성으로 -36)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
import { marked } from 'marked';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { ExternalLink, Save, RefreshCw } from 'lucide-svelte';
|
||||
import Tabs from '$lib/components/ui/Tabs.svelte';
|
||||
|
||||
// marked + sanitize
|
||||
marked.use({ mangle: false, headerIds: false });
|
||||
@@ -169,29 +170,31 @@
|
||||
{:else if fullDoc}
|
||||
{#if viewerType === 'markdown'}
|
||||
{#if editMode}
|
||||
<!-- Markdown 편집 (탭 전환) -->
|
||||
<!-- Markdown 편집 (Tabs 프리미티브 — E.4) -->
|
||||
<div class="flex flex-col h-full">
|
||||
<div class="flex gap-1 px-3 py-1 border-b border-default shrink-0">
|
||||
<button
|
||||
onclick={() => editTab = 'edit'}
|
||||
class="px-3 py-1 text-xs rounded-t {editTab === 'edit' ? 'bg-surface text-text' : 'text-dim'}"
|
||||
>편집</button>
|
||||
<button
|
||||
onclick={() => editTab = 'preview'}
|
||||
class="px-3 py-1 text-xs rounded-t {editTab === 'preview' ? 'bg-surface text-text' : 'text-dim'}"
|
||||
>미리보기</button>
|
||||
</div>
|
||||
{#if editTab === 'edit'}
|
||||
<textarea
|
||||
bind:value={editContent}
|
||||
class="flex-1 w-full p-4 bg-bg text-text text-sm font-mono resize-none outline-none"
|
||||
spellcheck="false"
|
||||
></textarea>
|
||||
{:else}
|
||||
<div class="flex-1 overflow-auto p-4 markdown-body">
|
||||
{@html renderMd(editContent)}
|
||||
</div>
|
||||
{/if}
|
||||
<Tabs
|
||||
tabs={[
|
||||
{ id: 'edit', label: '편집' },
|
||||
{ id: 'preview', label: '미리보기' },
|
||||
]}
|
||||
bind:value={editTab}
|
||||
class="flex flex-col h-full"
|
||||
>
|
||||
{#snippet children(activeId)}
|
||||
{#if activeId === 'edit'}
|
||||
<textarea
|
||||
bind:value={editContent}
|
||||
class="flex-1 w-full p-4 bg-bg text-text text-sm font-mono resize-none outline-none min-h-[300px]"
|
||||
spellcheck="false"
|
||||
aria-label="마크다운 편집"
|
||||
></textarea>
|
||||
{:else}
|
||||
<div class="flex-1 overflow-auto p-4 markdown-body">
|
||||
{@html renderMd(editContent)}
|
||||
</div>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</Tabs>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="p-4 markdown-body">
|
||||
|
||||
Reference in New Issue
Block a user