Initial commit
diff --git a/loop/webui/src/timeline/markdown/renderer.ts b/loop/webui/src/timeline/markdown/renderer.ts
new file mode 100644
index 0000000..8199b69
--- /dev/null
+++ b/loop/webui/src/timeline/markdown/renderer.ts
@@ -0,0 +1,40 @@
+import { marked } from "marked";
+
+/**
+ * Renders markdown content as HTML with proper security handling.
+ *
+ * @param markdownContent - The markdown string to render
+ * @returns The rendered HTML content as a string
+ */
+export async function renderMarkdown(markdownContent: string): Promise<string> {
+ try {
+ // Set markdown options for proper code block highlighting and safety
+ const markedOptions = {
+ gfm: true, // GitHub Flavored Markdown
+ breaks: true, // Convert newlines to <br>
+ headerIds: false, // Disable header IDs for safety
+ mangle: false, // Don't mangle email addresses
+ // DOMPurify is recommended for production, but not included in this implementation
+ };
+
+ return await marked.parse(markdownContent, markedOptions);
+ } catch (error) {
+ console.error("Error rendering markdown:", error);
+ // Fallback to plain text if markdown parsing fails
+ return markdownContent;
+ }
+}
+
+/**
+ * Process rendered markdown HTML element, adding security attributes to links.
+ *
+ * @param element - The HTML element containing rendered markdown
+ */
+export function processRenderedMarkdown(element: HTMLElement): void {
+ // Make sure links open in a new tab and have proper security attributes
+ const links = element.querySelectorAll("a");
+ links.forEach((link) => {
+ link.setAttribute("target", "_blank");
+ link.setAttribute("rel", "noopener noreferrer");
+ });
+}