-
{slotPreview[slot.name]}
+
{@html sanitizePreview(slotPreview[slot.name])}
{/if}
diff --git a/frontend/src/routes/template-configs/+page.svelte b/frontend/src/routes/template-configs/+page.svelte
index f62f875..37f357c 100644
--- a/frontend/src/routes/template-configs/+page.svelte
+++ b/frontend/src/routes/template-configs/+page.svelte
@@ -3,6 +3,7 @@
import { slide } from 'svelte/transition';
import { api } from '$lib/api';
import { t } from '$lib/i18n';
+ import { sanitizePreview } from '$lib/sanitize';
import { templateConfigsCache, capabilitiesCache } from '$lib/stores/caches.svelte';
import PageHeader from '$lib/components/PageHeader.svelte';
import Card from '$lib/components/Card.svelte';
@@ -213,44 +214,6 @@
setTimeout(() => refreshAllPreviews(), 100);
}
- function sanitizePreview(html: string): string {
- // DOM-based sanitizer: parse HTML, walk tree, keep only safe elements
- const ALLOWED_TAGS = new Set(['B', 'I', 'CODE', 'PRE', 'A', 'BR']);
- const doc = new DOMParser().parseFromString(html, 'text/html');
- const fragment = document.createDocumentFragment();
-
- function walkNodes(parent: Node, target: Node) {
- for (const node of Array.from(parent.childNodes)) {
- if (node.nodeType === Node.TEXT_NODE) {
- target.appendChild(document.createTextNode(node.textContent || ''));
- } else if (node.nodeType === Node.ELEMENT_NODE) {
- const el = node as Element;
- if (ALLOWED_TAGS.has(el.tagName)) {
- const safe = document.createElement(el.tagName);
- if (el.tagName === 'A') {
- const href = el.getAttribute('href') || '';
- if (/^https?:\/\//i.test(href)) {
- safe.setAttribute('href', href);
- safe.setAttribute('target', '_blank');
- safe.setAttribute('rel', 'noopener noreferrer');
- }
- }
- walkNodes(el, safe);
- target.appendChild(safe);
- } else {
- // Unwrap: keep text content of disallowed tags
- walkNodes(el, target);
- }
- }
- }
- }
-
- walkNodes(doc.body, fragment);
- const wrapper = document.createElement('div');
- wrapper.appendChild(fragment);
- return wrapper.innerHTML;
- }
-
function remove(id: number) {
confirmDelete = {
id,
diff --git a/packages/core/src/notify_bridge_core/templates/command_defaults/en/albums.jinja2 b/packages/core/src/notify_bridge_core/templates/command_defaults/en/albums.jinja2
index 596a0d5..07284f7 100644
--- a/packages/core/src/notify_bridge_core/templates/command_defaults/en/albums.jinja2
+++ b/packages/core/src/notify_bridge_core/templates/command_defaults/en/albums.jinja2
@@ -1,7 +1,7 @@
📚 Tracked albums:
{%- if albums %}
{%- for album in albums %}
- • {{ album.name }} ({{ album.asset_count }} assets)
+ • {% if album.public_url %}