fix: 프론트엔드 1단계 — XSS 수정 + Svelte 5 변환 + 필터/아이콘/a11y

- [critical] DOMPurify 적용 (FORBID_TAGS/ATTR, ALLOW_UNKNOWN_PROTOCOLS)
- [high] $: → $derived 변환 (documents/[id])
- [high] 태그/소스 필터 구현 (filterTag, filterSource)
- FormatIcon: docx/xlsx/pptx/odt/ods/odp/dwg/dxf 추가
- editTab 선언 순서 수정
- debounceTimer 미사용 변수 제거
- Toast role="status" aria-live 추가
- marked 옵션: mangle/headerIds false

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-04-06 12:15:02 +09:00
parent 24142ea605
commit 3374eebfc6
7 changed files with 2296 additions and 13 deletions

View File

@@ -4,12 +4,23 @@
import { api, getAccessToken } from '$lib/api';
import { addToast } from '$lib/stores/ui';
import { marked } from 'marked';
import DOMPurify from 'dompurify';
import TagPill from '$lib/components/TagPill.svelte';
let doc = null;
let loading = true;
marked.use({ mangle: false, headerIds: false });
function renderMd(text) {
return DOMPurify.sanitize(marked(text), {
USE_PROFILES: { html: true },
FORBID_TAGS: ['style', 'script'],
FORBID_ATTR: ['onerror', 'onclick'],
ALLOW_UNKNOWN_PROTOCOLS: false,
});
}
$: docId = $page.params.id;
let doc = $state(null);
let loading = $state(true);
let docId = $derived($page.params.id);
onMount(async () => {
try {
@@ -21,8 +32,7 @@
}
});
// 포맷별 뷰어 타입
$: viewerType = doc ? getViewerType(doc.file_format) : 'none';
let viewerType = $derived(doc ? getViewerType(doc.file_format) : 'none');
function getViewerType(format) {
if (['md', 'txt', 'csv', 'html'].includes(format)) return 'markdown';
@@ -52,7 +62,7 @@
<div class="lg:col-span-2 bg-[var(--surface)] rounded-xl border border-[var(--border)] p-6 min-h-[500px]">
{#if viewerType === 'markdown' || viewerType === 'hwp-markdown'}
<div class="prose prose-invert prose-sm max-w-none">
{@html marked(doc.extracted_text || '*텍스트 추출 대기 중*')}
{@html renderMd(doc.extracted_text || '*텍스트 추출 대기 중*')}
</div>
{:else if viewerType === 'pdf'}
<iframe