diff options
| -rw-r--r-- | renderer/src/components/DocumentPane.tsx | 67 | ||||
| -rw-r--r-- | renderer/src/styles/globals.css | 19 |
2 files changed, 85 insertions, 1 deletions
diff --git a/renderer/src/components/DocumentPane.tsx b/renderer/src/components/DocumentPane.tsx index a22ecad..c2e1b2c 100644 --- a/renderer/src/components/DocumentPane.tsx +++ b/renderer/src/components/DocumentPane.tsx | |||
| @@ -9,9 +9,72 @@ interface DocumentPaneProps { | |||
| 9 | showOnboarding?: boolean; | 9 | showOnboarding?: boolean; |
| 10 | } | 10 | } |
| 11 | 11 | ||
| 12 | function renderTable(tableLines: string[]): string { | ||
| 13 | if (tableLines.length < 2) return tableLines.join("\n"); | ||
| 14 | |||
| 15 | const parseRow = (line: string): string[] => { | ||
| 16 | return line | ||
| 17 | .split("|") | ||
| 18 | .slice(1, -1) // Remove empty first/last from |col|col| | ||
| 19 | .map((cell) => cell.trim()); | ||
| 20 | }; | ||
| 21 | |||
| 22 | const headerCells = parseRow(tableLines[0]); | ||
| 23 | // Skip separator row (index 1) | ||
| 24 | const bodyRows = tableLines.slice(2); | ||
| 25 | |||
| 26 | let html = '<table><thead><tr>'; | ||
| 27 | headerCells.forEach((cell) => { | ||
| 28 | html += `<th>${cell}</th>`; | ||
| 29 | }); | ||
| 30 | html += '</tr></thead><tbody>'; | ||
| 31 | |||
| 32 | bodyRows.forEach((row) => { | ||
| 33 | if (row.trim()) { | ||
| 34 | html += '<tr>'; | ||
| 35 | parseRow(row).forEach((cell) => { | ||
| 36 | html += `<td>${cell}</td>`; | ||
| 37 | }); | ||
| 38 | html += '</tr>'; | ||
| 39 | } | ||
| 40 | }); | ||
| 41 | |||
| 42 | html += '</tbody></table>'; | ||
| 43 | return html; | ||
| 44 | } | ||
| 45 | |||
| 12 | function renderMarkdown(md: string): string { | 46 | function renderMarkdown(md: string): string { |
| 47 | // First, handle tables (before other transformations) | ||
| 48 | const lines = md.split("\n"); | ||
| 49 | const processedLines: string[] = []; | ||
| 50 | let tableBuffer: string[] = []; | ||
| 51 | let inTable = false; | ||
| 52 | |||
| 53 | for (const line of lines) { | ||
| 54 | const isTableLine = /^\|.*\|$/.test(line.trim()); | ||
| 55 | |||
| 56 | if (isTableLine) { | ||
| 57 | inTable = true; | ||
| 58 | tableBuffer.push(line); | ||
| 59 | } else { | ||
| 60 | if (inTable && tableBuffer.length > 0) { | ||
| 61 | processedLines.push(renderTable(tableBuffer)); | ||
| 62 | tableBuffer = []; | ||
| 63 | inTable = false; | ||
| 64 | } | ||
| 65 | processedLines.push(line); | ||
| 66 | } | ||
| 67 | } | ||
| 68 | |||
| 69 | // Handle table at end of content | ||
| 70 | if (tableBuffer.length > 0) { | ||
| 71 | processedLines.push(renderTable(tableBuffer)); | ||
| 72 | } | ||
| 73 | |||
| 74 | let result = processedLines.join("\n"); | ||
| 75 | |||
| 13 | return ( | 76 | return ( |
| 14 | md | 77 | result |
| 15 | // Headers | 78 | // Headers |
| 16 | .replace(/^### (.*$)/gm, "<h3>$1</h3>") | 79 | .replace(/^### (.*$)/gm, "<h3>$1</h3>") |
| 17 | .replace(/^## (.*$)/gm, "<h2>$1</h2>") | 80 | .replace(/^## (.*$)/gm, "<h2>$1</h2>") |
| @@ -43,6 +106,8 @@ function renderMarkdown(md: string): string { | |||
| 43 | .replace(/(<\/pre>)<\/p>/g, "$1") | 106 | .replace(/(<\/pre>)<\/p>/g, "$1") |
| 44 | .replace(/<p>(<li)/g, "$1") | 107 | .replace(/<p>(<li)/g, "$1") |
| 45 | .replace(/(<\/li>)<\/p>/g, "$1") | 108 | .replace(/(<\/li>)<\/p>/g, "$1") |
| 109 | .replace(/<p>(<table>)/g, "$1") | ||
| 110 | .replace(/(<\/table>)<\/p>/g, "$1") | ||
| 46 | ); | 111 | ); |
| 47 | } | 112 | } |
| 48 | 113 | ||
diff --git a/renderer/src/styles/globals.css b/renderer/src/styles/globals.css index e1d945a..380a744 100644 --- a/renderer/src/styles/globals.css +++ b/renderer/src/styles/globals.css | |||
| @@ -200,6 +200,25 @@ body { | |||
| 200 | .document-content.rendered li.task.done { | 200 | .document-content.rendered li.task.done { |
| 201 | color: var(--success); | 201 | color: var(--success); |
| 202 | } | 202 | } |
| 203 | .document-content.rendered table { | ||
| 204 | width: 100%; | ||
| 205 | border-collapse: collapse; | ||
| 206 | margin: 16px 0; | ||
| 207 | font-size: 14px; | ||
| 208 | } | ||
| 209 | .document-content.rendered th, | ||
| 210 | .document-content.rendered td { | ||
| 211 | padding: 10px 12px; | ||
| 212 | text-align: left; | ||
| 213 | border: 1px solid var(--border); | ||
| 214 | } | ||
| 215 | .document-content.rendered th { | ||
| 216 | background: var(--bg-tertiary); | ||
| 217 | font-weight: 600; | ||
| 218 | } | ||
| 219 | .document-content.rendered tr:nth-child(even) td { | ||
| 220 | background: var(--bg-secondary); | ||
| 221 | } | ||
| 203 | .document-content.rendered mark.review { | 222 | .document-content.rendered mark.review { |
| 204 | background: var(--warning); | 223 | background: var(--warning); |
| 205 | color: black; | 224 | color: black; |
