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:
@@ -2,8 +2,20 @@
|
||||
import { api, getAccessToken } from '$lib/api';
|
||||
import { addToast } from '$lib/stores/ui';
|
||||
import { marked } from 'marked';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { ExternalLink, Save, RefreshCw } from 'lucide-svelte';
|
||||
|
||||
// marked + sanitize
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
let { doc } = $props();
|
||||
let fullDoc = $state(null);
|
||||
let loading = $state(true);
|
||||
@@ -12,6 +24,7 @@
|
||||
// Markdown 편집
|
||||
let editMode = $state(false);
|
||||
let editContent = $state('');
|
||||
let editTab = $state('edit');
|
||||
let saving = $state(false);
|
||||
let rawMarkdown = $state('');
|
||||
|
||||
@@ -74,8 +87,6 @@
|
||||
editTab = 'edit';
|
||||
}
|
||||
|
||||
let editTab = $state('edit'); // 'edit' | 'preview'
|
||||
|
||||
async function saveContent() {
|
||||
saving = true;
|
||||
try {
|
||||
@@ -178,13 +189,13 @@
|
||||
></textarea>
|
||||
{:else}
|
||||
<div class="flex-1 overflow-auto p-4 markdown-body">
|
||||
{@html marked(editContent)}
|
||||
{@html renderMd(editContent)}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="p-4 markdown-body">
|
||||
{@html marked(fullDoc.extracted_text || rawMarkdown || '*텍스트 추출 대기 중*')}
|
||||
{@html renderMd(fullDoc.extracted_text || rawMarkdown || '*텍스트 추출 대기 중*')}
|
||||
</div>
|
||||
{/if}
|
||||
{:else if viewerType === 'pdf'}
|
||||
|
||||
@@ -22,6 +22,17 @@
|
||||
eml: Mail,
|
||||
odoc: FileText,
|
||||
osheet: FileSpreadsheet,
|
||||
docx: FileText,
|
||||
doc: FileText,
|
||||
xlsx: FileSpreadsheet,
|
||||
xls: FileSpreadsheet,
|
||||
pptx: Presentation,
|
||||
ppt: Presentation,
|
||||
odt: FileText,
|
||||
ods: FileSpreadsheet,
|
||||
odp: Presentation,
|
||||
dwg: FileCode,
|
||||
dxf: FileCode,
|
||||
};
|
||||
|
||||
let Icon = $derived(ICON_MAP[format?.toLowerCase()] || FileQuestion);
|
||||
|
||||
Reference in New Issue
Block a user