diff --git a/ka-note/client/package.json b/ka-note/client/package.json index 58353bb..6999f93 100644 --- a/ka-note/client/package.json +++ b/ka-note/client/package.json @@ -24,6 +24,7 @@ "@tiptap/starter-kit": "^3.20.0", "dexie": "^4.0.11", "dompurify": "^3.3.1", + "highlight.js": "^11.11.1", "marked": "^17.0.3", "tiptap-markdown": "^0.9.0" }, diff --git a/ka-note/client/src/app.css b/ka-note/client/src/app.css index a03a4f6..f4d11ed 100644 --- a/ka-note/client/src/app.css +++ b/ka-note/client/src/app.css @@ -2,6 +2,8 @@ @tailwind components; @tailwind utilities; +@import 'highlight.js/styles/github-dark-dimmed.css'; + :root { --scope-color: #555566; /* updated dynamically by layout */ --bg-color: #1a1a22; @@ -79,6 +81,87 @@ ul.tree-list li { margin-bottom: 0; } +/* Callout blocks */ +.callout { + border-radius: 6px; + padding: 0.6rem 0.9rem; + margin: 0.75rem 0; + border-left: 4px solid; +} + +.callout-title { + font-weight: 600; + font-size: 0.8rem; + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: 0.25rem; +} + +.callout-body > :first-child { margin-top: 0; } +.callout-body > :last-child { margin-bottom: 0; } + +.callout-note { background: #1a2a3a; border-color: var(--accent); } +.callout-note .callout-title { color: var(--accent); } +.callout-info { background: #1a2a3a; border-color: var(--info); } +.callout-info .callout-title { color: var(--info); } +.callout-tip { background: #1a2e22; border-color: var(--success); } +.callout-tip .callout-title { color: var(--success); } +.callout-success { background: #1a2e22; border-color: var(--success); } +.callout-success .callout-title { color: var(--success); } +.callout-warning { background: #2e2510; border-color: var(--warning); } +.callout-warning .callout-title { color: var(--warning); } +.callout-danger { background: #2e1515; border-color: var(--danger); } +.callout-danger .callout-title { color: var(--danger); } +.callout-caution { background: #2e1515; border-color: var(--danger); } +.callout-caution .callout-title { color: var(--danger); } + +/* Tables */ +.markdown-content .table-wrapper { + overflow-x: auto; + margin: 0.75rem 0; +} + +.markdown-content table { + border-collapse: collapse; + width: 100%; + font-size: 0.875rem; +} + +.markdown-content th { + background: #2a2a38; + color: #e0e0e0; + font-weight: 600; + text-align: left; + padding: 0.4rem 0.75rem; + border: 1px solid var(--border-color); +} + +.markdown-content td { + padding: 0.35rem 0.75rem; + border: 1px solid var(--border-color); + vertical-align: top; +} + +.markdown-content tbody tr:nth-child(even) td { + background: #22222e; +} + +/* Typography tweaks */ +.markdown-content blockquote { + border-left: 3px solid var(--accent); + padding-left: 0.75rem; + color: #aaa; + font-style: italic; + margin-left: 0; +} + +.markdown-content hr { + border: none; + height: 1px; + background: linear-gradient(to right, transparent, var(--border-color), transparent); + margin: 1.25rem 0; +} + /* Tiptap Editor */ .ka-editor-wrapper { @apply rounded border border-[#444] bg-bg font-mono text-white; @@ -122,6 +205,38 @@ ul.tree-list li { padding-left: 20px; } +/* Table styles in editor */ +.ka-editor-wrapper .ProseMirror table { + border-collapse: collapse; + width: 100%; + font-size: 0.875rem; + margin: 0.5rem 0; + overflow-x: auto; + display: block; +} + +.ka-editor-wrapper .ProseMirror th, +.ka-editor-wrapper .ProseMirror td { + border: 1px solid #444; + padding: 0.3rem 0.6rem; + vertical-align: top; + min-width: 60px; + position: relative; +} + +.ka-editor-wrapper .ProseMirror th { + background: #2a2a38; + font-weight: 600; +} + +.ka-editor-wrapper .ProseMirror .selectedCell::after { + content: ''; + position: absolute; + inset: 0; + background: rgba(74, 158, 255, 0.15); + pointer-events: none; +} + /* Rating indicators on @NAME tags */ .person-ref.rating-1 { border-bottom: 3px solid #d9534f !important; diff --git a/ka-note/client/src/lib/components/MarkdownEditor.svelte b/ka-note/client/src/lib/components/MarkdownEditor.svelte index e51f4b0..3ec2574 100644 --- a/ka-note/client/src/lib/components/MarkdownEditor.svelte +++ b/ka-note/client/src/lib/components/MarkdownEditor.svelte @@ -28,6 +28,7 @@ let element: HTMLDivElement; let bubbleMenuEl: HTMLDivElement; + let tableMenuEl: HTMLDivElement; let editor: Editor | null = null; let skipNextUpdate = false; let lastExternalContent = $state(content); @@ -103,10 +104,17 @@ if (!view.hasFocus()) return false; if (from === to) return false; if (e.isActive('image')) return false; + if (e.isActive('table')) return false; const { empty } = e.state.selection; return !empty; }, }), + BubbleMenu.configure({ + element: tableMenuEl, + pluginKey: 'tableMenu', + tippyOptions: { duration: 100, placement: 'top-start', hideOnClick: false }, + shouldShow: ({ editor: e }) => e.isActive('table'), + }), ], content: initialContent, editorProps: { @@ -163,6 +171,27 @@
e.stopPropagation()}>
+
+ Zeile + + + + Spalte + + + + +
'); +} + +// --- Callout blocks post-processing --- + +const CALLOUT_ICONS: Record = { + note: '📝', info: 'ℹ️', tip: '💡', success: '✅', + warning: '⚠️', danger: '🚨', caution: '⚠️' +}; + +function processCallouts(html: string): string { + return html.replace( + /
\s*

\[!(NOTE|INFO|TIP|SUCCESS|WARNING|DANGER|CAUTION)\](.*?)<\/p>([\s\S]*?)<\/blockquote>/gi, + (_, type: string, title: string, body: string) => { + const t = type.toLowerCase(); + const icon = CALLOUT_ICONS[t] ?? ''; + const label = title.trim() || type.toUpperCase(); + const content = body.trim(); + return `

` + + `
${icon} ${label}
` + + (content ? `
${content}
` : '') + + `
`; + } + ); +} + // --- Collapsible list post-processing --- function addCollapsibleToggles(html: string): string { @@ -161,6 +201,8 @@ export function renderMarkdown(text: string): string { // tiptap-markdown escapes [ as \[ — restore [[WikiLinks]] before parsing const unescaped = text.replace(/\\\[\\\[(.+?)\\\]\\\]/g, '[[$1]]'); const raw = markedInstance.parse(unescaped) as string; - const sanitized = DOMPurify.sanitize(raw, PURIFY_CONFIG); + const withTables = wrapTables(raw); + const withCallouts = processCallouts(withTables); + const sanitized = DOMPurify.sanitize(withCallouts, PURIFY_CONFIG); return addCollapsibleToggles(sanitized); } diff --git a/ka-note/package-lock.json b/ka-note/package-lock.json index 5a0c85f..4a7e8dd 100644 --- a/ka-note/package-lock.json +++ b/ka-note/package-lock.json @@ -35,6 +35,7 @@ "@tiptap/starter-kit": "^3.20.0", "dexie": "^4.0.11", "dompurify": "^3.3.1", + "highlight.js": "^11.11.1", "marked": "^17.0.3", "tiptap-markdown": "^0.9.0" }, @@ -6595,6 +6596,15 @@ "he": "bin/he" } }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/hono": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.0.tgz", diff --git a/ka-note/server/ka-note.db-shm b/ka-note/server/ka-note.db-shm index 04d9b0c..430e1cf 100644 Binary files a/ka-note/server/ka-note.db-shm and b/ka-note/server/ka-note.db-shm differ diff --git a/ka-note/server/ka-note.db-wal b/ka-note/server/ka-note.db-wal index fce3160..a9c9863 100644 Binary files a/ka-note/server/ka-note.db-wal and b/ka-note/server/ka-note.db-wal differ