Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 46 additions & 59 deletions staged/src/lib/NoteViewerModal.svelte
Original file line number Diff line number Diff line change
@@ -1,70 +1,25 @@
<script module lang="ts">
// Simple markdown renderer - converts basic markdown to HTML
// For a production app, you'd want to use a proper markdown library
function renderMarkdown(content: string): string {
let html = escapeHtml(content);

// Headers
html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>');
html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>');
html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>');

// Bold and italic
html = html.replace(/\*\*\*(.+?)\*\*\*/g, '<strong><em>$1</em></strong>');
html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');

// Code blocks
html = html.replace(
/```(\w*)\n([\s\S]*?)```/g,
'<pre><code class="language-$1">$2</code></pre>'
);

// Inline code
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');

// Links
html = html.replace(
/\[([^\]]+)\]\(([^)]+)\)/g,
'<a href="$2" target="_blank" rel="noopener">$1</a>'
);

// Unordered lists
html = html.replace(/^- (.+)$/gm, '<li>$1</li>');
html = html.replace(/(<li>.*<\/li>\n?)+/g, '<ul>$&</ul>');

// Paragraphs (double newlines)
html = html.replace(/\n\n+/g, '</p><p>');
html = '<p>' + html + '</p>';

// Clean up empty paragraphs and fix list wrapping
html = html.replace(/<p>\s*<\/p>/g, '');
html = html.replace(/<p>\s*(<ul>)/g, '$1');
html = html.replace(/(<\/ul>)\s*<\/p>/g, '$1');
html = html.replace(/<p>\s*(<h[123]>)/g, '$1');
html = html.replace(/(<\/h[123]>)\s*<\/p>/g, '$1');
html = html.replace(/<p>\s*(<pre>)/g, '$1');
html = html.replace(/(<\/pre>)\s*<\/p>/g, '$1');

return html;
}

function escapeHtml(text: string): string {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
</script>

<!--
NoteViewerModal.svelte - View a branch note's markdown content

Shows the rendered markdown content of a note in a modal.
-->
<script lang="ts">
import { X } from 'lucide-svelte';
import { marked } from 'marked';
import DOMPurify from 'dompurify';
import type { BranchNote } from './services/branch';

// Configure marked for safe rendering
marked.setOptions({
breaks: true,
gfm: true,
});

// Render markdown content safely
function renderMarkdown(content: string): string {
return DOMPurify.sanitize(marked.parse(content) as string);
}

interface Props {
note: BranchNote;
onClose: () => void;
Expand Down Expand Up @@ -230,7 +185,8 @@
margin: 0 0 12px 0;
}

.markdown-content :global(ul) {
.markdown-content :global(ul),
.markdown-content :global(ol) {
margin: 0 0 12px 0;
padding-left: 24px;
}
Expand All @@ -239,6 +195,37 @@
margin: 4px 0;
}

.markdown-content :global(blockquote) {
margin: 12px 0;
padding-left: 12px;
border-left: 3px solid var(--border-muted);
color: var(--text-muted);
}

.markdown-content :global(hr) {
margin: 16px 0;
border: none;
border-top: 1px solid var(--border-subtle);
}

.markdown-content :global(table) {
border-collapse: collapse;
margin: 12px 0;
width: 100%;
}

.markdown-content :global(th),
.markdown-content :global(td) {
border: 1px solid var(--border-subtle);
padding: 6px 12px;
text-align: left;
}

.markdown-content :global(th) {
font-weight: 600;
background: var(--bg-hover);
}

.markdown-content :global(code) {
font-family: 'SF Mono', 'Menlo', 'Monaco', 'Courier New', monospace;
font-size: 0.9em;
Expand Down