diff --git a/src/pages/content-management/AddNewPracticePage.tsx b/src/pages/content-management/AddNewPracticePage.tsx
index bdb8ce0..3160d1a 100644
--- a/src/pages/content-management/AddNewPracticePage.tsx
+++ b/src/pages/content-management/AddNewPracticePage.tsx
@@ -96,6 +96,53 @@ function isDirectVideoFile(url: string): boolean {
return /\.(mp4|webm|ogg|mov|m4v)$/.test(clean)
}
+function escapeHtml(raw: string): string {
+ return raw
+ .replaceAll("&", "&")
+ .replaceAll("<", "<")
+ .replaceAll(">", ">")
+ .replaceAll('"', """)
+ .replaceAll("'", "'")
+}
+
+function sanitizeAdminRichTextHtml(input: string): string {
+ if (!input.trim()) return ""
+ try {
+ const parser = new DOMParser()
+ const doc = parser.parseFromString(input, "text/html")
+ const blockedTags = new Set(["script", "style", "iframe", "object", "embed", "link", "meta"])
+ doc.body.querySelectorAll("*").forEach((el) => {
+ const tagName = el.tagName.toLowerCase()
+ if (blockedTags.has(tagName)) {
+ el.remove()
+ return
+ }
+ const attrs = [...el.attributes]
+ attrs.forEach((attr) => {
+ const name = attr.name.toLowerCase()
+ const value = attr.value.trim().toLowerCase()
+ if (name.startsWith("on")) {
+ el.removeAttribute(attr.name)
+ return
+ }
+ if ((name === "href" || name === "src") && value.startsWith("javascript:")) {
+ el.removeAttribute(attr.name)
+ }
+ })
+ })
+ return doc.body.innerHTML
+ } catch {
+ return escapeHtml(input).replace(/\r?\n/g, "
")
+ }
+}
+
+function formatDescriptionForPreview(raw: string): string {
+ if (!raw.trim()) return ""
+ const hasHtml = /<\/?[a-z][\s\S]*>/i.test(raw)
+ if (hasHtml) return sanitizeAdminRichTextHtml(raw)
+ return escapeHtml(raw).replace(/\r?\n/g, "
")
+}
+
function createEmptyQuestion(id: string): Question {
return {
id,
@@ -230,6 +277,11 @@ export function AddNewPracticePage() {
return null
}, [introVideoUrl])
+ const descriptionPreviewHtml = useMemo(
+ () => formatDescriptionForPreview(practiceDescription),
+ [practiceDescription],
+ )
+
const addQuestion = () => {
setQuestions([...questions, createEmptyQuestion(String(Date.now()))])
}
@@ -430,6 +482,9 @@ export function AddNewPracticePage() {
className="min-h-[88px] w-full rounded-lg border border-grayScale-200 px-3 py-2.5 text-sm transition-colors focus:border-brand-500 focus:outline-none focus:ring-2 focus:ring-brand-100"
rows={3}
/>
+
+ Supports plain text and formatted HTML (for headings, lists, italics, and emphasis). +
—
+ )}
+ {hint === "image" ? (
+
Preview not available for this URL type.
+ )} + +
- {hint === "image" ? (
-
Preview not available for this URL type.
- )} - -+ Sample answer text +
++ {q.audio_correct_answer_text} +
+