# Markdown Support in msg.html ## Current State ### How messages render today In [`msg.html`](../www/msg.html:779), the [`renderThread()`](../www/msg.html:756) function renders message content using **`textContent`**: ```js const content = document.createElement('div'); content.textContent = msg.content || ''; ``` This means all message text is displayed as plain text — no links, no bold, no formatting of any kind. Even URLs are not clickable. ### Existing assets in the project 1. **[`marked.min.js`](../www/js/marked.min.js:1)** — marked v9.0.3 is already bundled in the project. It is used by [`note.html`](../www/note.html:257) for rendering long-form note content as markdown into an iframe. 2. **[`htmlFormatText()`](../www/js/utilities.mjs:515)** — A simple utility that converts URLs to `` links and image URLs to `` tags. Used by [`post-interactions.mjs`](../www/js/post-interactions.mjs:300) for social feed posts. --- ## Industry Standard for Messaging Formatting ### What major messaging platforms do | Platform | Formatting Approach | |----------|-------------------| | **Slack** | Custom markdown subset: `*bold*`, `_italic_`, `~strikethrough~`, `` `code` ``, ` ```code blocks``` `, `> blockquote`, links, emoji shortcodes | | **Discord** | Full markdown subset: `**bold**`, `*italic*`, `~~strikethrough~~`, `` `code` ``, ` ```lang code blocks``` `, `> blockquote`, `\|\| spoiler \|\|`, links | | **Telegram** | Markdown + HTML: `**bold**`, `__italic__`, `` `code` ``, ` ```code blocks``` `, `~~strikethrough~~`, `\|\| spoiler \|\|`, links | | **WhatsApp** | Limited markdown: `*bold*`, `_italic_`, `~strikethrough~`, `` `code` `` | | **Signal** | Limited markdown: `*bold*`, `_italic_`, `~~strikethrough~~`, `||spoiler||` | | **Matrix/Element** | Full markdown rendering with HTML sanitization | | **iMessage** | No markdown; rich text via attributed strings | ### Nostr ecosystem conventions - **NIP-01** kind 1 notes and kind 14 DMs use **plain text content** — no official markdown NIP exists - In practice, most Nostr clients render: - URLs as clickable links - Image URLs as inline images - `nostr:` URIs as profile/event links - Some clients like **Amethyst**, **Damus**, and **Coracle** render basic markdown in notes - **NIP-23** long-form content (kind 30023) explicitly uses markdown ### Recommended approach for Nostr DMs The industry consensus for chat/messaging is a **sanitized markdown subset** — not full markdown. The key principles: 1. **Render inline formatting**: bold, italic, strikethrough, inline code 2. **Render code blocks**: fenced code blocks with syntax highlighting optional 3. **Auto-link URLs**: make URLs clickable 4. **Render image URLs**: show inline image previews 5. **Blockquotes**: useful for quoting previous messages 6. **Sanitize HTML**: prevent XSS — never allow raw HTML tags 7. **Do NOT render**: headings, tables, horizontal rules, or complex block elements that don't make sense in chat bubbles --- ## Implementation Plan ### Architecture ```mermaid flowchart TD A[Raw message content] --> B{Contains markdown?} B -->|Any content| C[DOMPurify sanitize raw text] C --> D[marked.parse with chat-safe options] D --> E[DOMPurify sanitize HTML output] E --> F[Set innerHTML on bubble] G[Reply input box] --> H[Get plaintext from contenteditable] H --> I[Send as plain text over NIP-04/NIP-44/NIP-17] style A fill:#1a1a2e,stroke:#e94560,color:#eee style F fill:#1a1a2e,stroke:#16c79a,color:#eee style I fill:#1a1a2e,stroke:#16c79a,color:#eee ``` ### Steps #### 1. Add marked.min.js script tag to msg.html Include the existing [`marked.min.js`](../www/js/marked.min.js:1) in the `` of [`msg.html`](../www/msg.html), just like [`note.html`](../www/note.html:257) does: ```html ``` #### 2. Add DOMPurify for XSS protection Since messages come from untrusted third parties, HTML sanitization is critical. Add DOMPurify via CDN: ```html ``` Alternatively, download and bundle it locally like `marked.min.js`. #### 3. Configure marked for chat-safe rendering Create a chat-specific marked configuration that disables features inappropriate for chat bubbles: ```js function configureMarkedForChat() { marked.setOptions({ breaks: true, // Convert \n to
- essential for chat gfm: true, // GitHub Flavored Markdown - strikethrough, tables pedantic: false, silent: true // Don't throw on bad markdown }); // Custom renderer to limit what gets rendered const renderer = new marked.Renderer(); // Disable headings in chat - render as bold text instead renderer.heading = function(text, level) { return `${text}
`; }; // Disable horizontal rules renderer.hr = function() { return '
'; }; // Make links open in new tab renderer.link = function(href, title, text) { const titleAttr = title ? ` title="${title}"` : ''; return `
${text}`; }; // Render images inline with max-width constraint renderer.image = function(href, title, text) { const titleAttr = title ? ` title="${title}"` : ''; return `${text}`; }; marked.use({ renderer }); } ``` #### 4. Create a renderMessageContent helper function Add a function that safely converts message text to formatted HTML: ```js function renderMessageContent(text) { if (!text) return ''; // Parse markdown to HTML let html = ''; try { html = marked.parse(text); } catch (e) { // Fallback to escaped plain text const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // Sanitize to prevent XSS if (window.DOMPurify) { html = DOMPurify.sanitize(html, { ALLOWED_TAGS: [ 'b', 'i', 'em', 'strong', 'a', 'code', 'pre', 'br', 'p', 'ul', 'ol', 'li', 'blockquote', 'del', 's', 'img', 'span' ], ALLOWED_ATTR: [ 'href', 'target', 'rel', 'title', 'alt', 'src', 'style' ], ALLOW_DATA_ATTR: false }); } return html; } ``` #### 5. Update renderThread to use innerHTML with sanitized markdown Change the content rendering in [`renderThread()`](../www/msg.html:756) from `textContent` to `innerHTML`: **Before** (line 779): ```js content.textContent = msg.content || ''; ``` **After**: ```js content.innerHTML = renderMessageContent(msg.content || ''); ``` #### 6. Add CSS styles for markdown inside chat bubbles Add styles scoped to `.msgBubble` so markdown elements look appropriate in chat context: ```css .msgBubble p { margin: 0 0 0.3em 0; } .msgBubble p:last-child { margin-bottom: 0; } .msgBubble code { background: rgba(128,128,128,0.2); padding: 1px 4px; border-radius: 3px; font-size: 85%; } .msgBubble pre { background: rgba(0,0,0,0.15); padding: 6px 8px; border-radius: 5px; overflow-x: auto; margin: 4px 0; } .msgBubble pre code { background: none; padding: 0; } .msgBubble blockquote { border-left: 3px solid var(--muted-color); margin: 4px 0; padding: 2px 8px; color: var(--muted-color); } .msgBubble a { color: var(--accent-color); text-decoration: underline; } .msgBubble img { max-width: 100%; border-radius: 6px; margin: 4px 0; } .msgBubble ul, .msgBubble ol { margin: 4px 0; padding-left: 20px; } .msgBubble del, .msgBubble s { text-decoration: line-through; opacity: 0.7; } ``` #### 7. Adjust white-space on msgBubble The current [`msgBubble`](../www/msg.html:178) CSS has `white-space: pre-wrap` which will conflict with markdown-rendered HTML. Change it: **Before**: ```css .msgBubble { white-space: pre-wrap; ... } ``` **After**: ```css .msgBubble { white-space: normal; word-wrap: break-word; overflow-wrap: break-word; ... } ``` The `breaks: true` option in marked handles newline conversion to `
`, so `pre-wrap` is no longer needed. --- ## Security Considerations - **DOMPurify is essential** — messages come from untrusted Nostr users. Without sanitization, a malicious user could inject `