fix(study): AI 풀이 본문 markdown 렌더링 (review + edit)
기존엔 whitespace-pre-line 으로 plain 표시 → '**굵게**' 같은 markdown 문법이 그대로 노출. DocumentViewer 와 동일한 marked + DOMPurify 패턴 적용. prose 타이포그래피 클래스로 list/heading/inline 코드 스타일 자동.
This commit is contained in:
@@ -12,12 +12,26 @@
|
||||
import { api } from '$lib/api';
|
||||
import { addToast } from '$lib/stores/toast';
|
||||
import { ArrowLeft, Save, Trash2, AlertCircle, Sparkles, GitCompare, ArrowRight, CheckCircle2, XCircle } from 'lucide-svelte';
|
||||
import { marked } from 'marked';
|
||||
import DOMPurify from 'dompurify';
|
||||
import Button from '$lib/components/ui/Button.svelte';
|
||||
import Card from '$lib/components/ui/Card.svelte';
|
||||
import Skeleton from '$lib/components/ui/Skeleton.svelte';
|
||||
import TextInput from '$lib/components/ui/TextInput.svelte';
|
||||
import Textarea from '$lib/components/ui/Textarea.svelte';
|
||||
|
||||
// AI 풀이 markdown 렌더링 (DocumentViewer 와 동일 패턴)
|
||||
marked.use({ mangle: false, headerIds: false });
|
||||
function renderMd(text) {
|
||||
if (!text) return '';
|
||||
return DOMPurify.sanitize(marked(text), {
|
||||
USE_PROFILES: { html: true },
|
||||
FORBID_TAGS: ['style', 'script'],
|
||||
FORBID_ATTR: ['onerror', 'onclick'],
|
||||
ALLOW_UNKNOWN_PROTOCOLS: false,
|
||||
});
|
||||
}
|
||||
|
||||
let topicId = $derived(Number($page.params.id));
|
||||
let questionId = $derived(Number($page.params.qid));
|
||||
|
||||
@@ -289,7 +303,9 @@
|
||||
{/if}
|
||||
|
||||
{#if aiOpen && aiBody}
|
||||
<div class="text-sm text-text whitespace-pre-line leading-relaxed bg-bg/30 border border-default rounded p-3">{aiBody}</div>
|
||||
<div class="text-sm text-text leading-relaxed bg-bg/30 border border-default rounded p-3 prose prose-sm prose-invert max-w-none">
|
||||
{@html renderMd(aiBody)}
|
||||
</div>
|
||||
{#if aiEvidence?.length}
|
||||
<details class="text-[10px] text-dim">
|
||||
<summary class="cursor-pointer hover:text-text">참고 근거 {aiEvidence.length}건</summary>
|
||||
|
||||
@@ -12,11 +12,25 @@
|
||||
import { api } from '$lib/api';
|
||||
import { addToast } from '$lib/stores/toast';
|
||||
import { ArrowLeft, Play, CheckCircle2, XCircle, RotateCcw, Sparkles, AlertCircle, GitCompare, ArrowRight } from 'lucide-svelte';
|
||||
import { marked } from 'marked';
|
||||
import DOMPurify from 'dompurify';
|
||||
import Button from '$lib/components/ui/Button.svelte';
|
||||
import Card from '$lib/components/ui/Card.svelte';
|
||||
import Skeleton from '$lib/components/ui/Skeleton.svelte';
|
||||
import TextInput from '$lib/components/ui/TextInput.svelte';
|
||||
|
||||
// AI 풀이 markdown 렌더링 (DocumentViewer 와 동일 패턴)
|
||||
marked.use({ mangle: false, headerIds: false });
|
||||
function renderMd(text) {
|
||||
if (!text) return '';
|
||||
return DOMPurify.sanitize(marked(text), {
|
||||
USE_PROFILES: { html: true },
|
||||
FORBID_TAGS: ['style', 'script'],
|
||||
FORBID_ATTR: ['onerror', 'onclick'],
|
||||
ALLOW_UNKNOWN_PROTOCOLS: false,
|
||||
});
|
||||
}
|
||||
|
||||
let topicId = $derived(Number($page.params.id));
|
||||
let topicName = $state('');
|
||||
|
||||
@@ -357,7 +371,9 @@
|
||||
<div class="text-[11px] text-error">{aiExplError}</div>
|
||||
{/if}
|
||||
{#if aiExpl?.ai_explanation}
|
||||
<div class="text-xs text-text whitespace-pre-line leading-relaxed">{aiExpl.ai_explanation}</div>
|
||||
<div class="text-xs text-text leading-relaxed prose prose-sm prose-invert max-w-none">
|
||||
{@html renderMd(aiExpl.ai_explanation)}
|
||||
</div>
|
||||
{/if}
|
||||
{#if aiExpl?.evidence?.length}
|
||||
<details class="text-[10px] text-dim">
|
||||
|
||||
Reference in New Issue
Block a user