From 59b032aab085d6b500fd4c49ab7106920403ea3e Mon Sep 17 00:00:00 2001 From: Sam Chau Date: Fri, 2 Jan 2026 20:21:03 +1030 Subject: [PATCH] feat(highlight): integrate Highlight.js for syntax highlighting in JSON and SQL views --- deno.json | 3 +- deno.lock | 2 + package-lock.json | 10 ++ package.json | 1 + src/app.css | 67 ++++++++++ src/lib/client/ui/meta/JsonView.svelte | 57 +++++++++ src/lib/client/ui/modal/Modal.svelte | 11 +- src/lib/client/ui/table/Table.svelte | 3 +- src/routes/settings/logs/+page.svelte | 162 ++++++++++--------------- 9 files changed, 216 insertions(+), 100 deletions(-) create mode 100644 src/lib/client/ui/meta/JsonView.svelte diff --git a/deno.json b/deno.json index 64744bf..28ed70f 100644 --- a/deno.json +++ b/deno.json @@ -20,7 +20,8 @@ "@soapbox/kysely-deno-sqlite": "jsr:@soapbox/kysely-deno-sqlite@^2.2.0", "@std/assert": "jsr:@std/assert@^1.0.0", "marked": "npm:marked@^15.0.6", - "simple-icons": "npm:simple-icons@^15.17.0" + "simple-icons": "npm:simple-icons@^15.17.0", + "highlight.js": "npm:highlight.js@^11.11.1" }, "tasks": { "dev": "APP_BASE_PATH=./dist/dev PARSER_HOST=localhost PARSER_PORT=5000 deno run -A npm:vite dev", diff --git a/deno.lock b/deno.lock index 5c0f5ee..c4c8abf 100644 --- a/deno.lock +++ b/deno.lock @@ -1947,6 +1947,7 @@ "dependencies": [ "jsr:@soapbox/kysely-deno-sqlite@^2.2.0", "jsr:@std/assert@1", + "npm:highlight.js@^11.11.1", "npm:marked@^15.0.6", "npm:simple-icons@^15.17.0" ], @@ -1965,6 +1966,7 @@ "npm:eslint-plugin-svelte@^3.12.4", "npm:eslint@^9.36.0", "npm:globals@^16.4.0", + "npm:highlight.js@^11.11.1", "npm:kysely@0.27.6", "npm:lucide-svelte@0.546", "npm:marked@^15.0.6", diff --git a/package-lock.json b/package-lock.json index 90dbdaf..7ba1372 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@deno/vite-plugin": "^1.0.5", "@jsr/db__sqlite": "^0.12.0", + "highlight.js": "^11.11.1", "kysely": "0.27.6", "lucide-svelte": "^0.546.0", "marked": "^15.0.6", @@ -2586,6 +2587,15 @@ "node": ">=8" } }, + "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/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", diff --git a/package.json b/package.json index 1ed757b..bcbf0ab 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "dependencies": { "@deno/vite-plugin": "^1.0.5", "@jsr/db__sqlite": "^0.12.0", + "highlight.js": "^11.11.1", "kysely": "0.27.6", "lucide-svelte": "^0.546.0", "marked": "^15.0.6", diff --git a/src/app.css b/src/app.css index 8eb4676..aa814b7 100644 --- a/src/app.css +++ b/src/app.css @@ -2,6 +2,73 @@ @import url('https://fonts.googleapis.com/css2?family=Geist+Mono:wght@100..900&display=swap'); @import 'tailwindcss'; +/* Highlight.js: atom-one-light for light mode */ +@import 'highlight.js/styles/atom-one-light.css'; + +/* Highlight.js: atom-one-dark for dark mode */ +.dark .hljs { + color: #abb2bf; +} +.dark .hljs-comment, +.dark .hljs-quote { + color: #5c6370; + font-style: italic; +} +.dark .hljs-doctag, +.dark .hljs-keyword, +.dark .hljs-formula { + color: #c678dd; +} +.dark .hljs-section, +.dark .hljs-name, +.dark .hljs-selector-tag, +.dark .hljs-deletion, +.dark .hljs-subst { + color: #e06c75; +} +.dark .hljs-literal { + color: #56b6c2; +} +.dark .hljs-string, +.dark .hljs-regexp, +.dark .hljs-addition, +.dark .hljs-attribute, +.dark .hljs-meta .hljs-string { + color: #98c379; +} +.dark .hljs-attr, +.dark .hljs-variable, +.dark .hljs-template-variable, +.dark .hljs-type, +.dark .hljs-selector-class, +.dark .hljs-selector-attr, +.dark .hljs-selector-pseudo, +.dark .hljs-number { + color: #d19a66; +} +.dark .hljs-symbol, +.dark .hljs-bullet, +.dark .hljs-link, +.dark .hljs-meta, +.dark .hljs-selector-id, +.dark .hljs-title { + color: #61aeee; +} +.dark .hljs-built_in, +.dark .hljs-title.class_, +.dark .hljs-class .hljs-title { + color: #e6c07b; +} +.dark .hljs-emphasis { + font-style: italic; +} +.dark .hljs-strong { + font-weight: bold; +} +.dark .hljs-link { + text-decoration: underline; +} + @custom-variant dark (&:where(.dark, .dark *)); @theme { diff --git a/src/lib/client/ui/meta/JsonView.svelte b/src/lib/client/ui/meta/JsonView.svelte new file mode 100644 index 0000000..955bf92 --- /dev/null +++ b/src/lib/client/ui/meta/JsonView.svelte @@ -0,0 +1,57 @@ + + +
+ +
{@html highlightedJson}
+ + + {#if queries.length > 0} +
+
+ Queries ({queries.length}) +
+
+ {#each queries as query, i} +
+
{@html highlightSql(query)}
+
+ {/each} +
+
+ {/if} +
+ + diff --git a/src/lib/client/ui/modal/Modal.svelte b/src/lib/client/ui/modal/Modal.svelte index 77e7f99..85fc784 100644 --- a/src/lib/client/ui/modal/Modal.svelte +++ b/src/lib/client/ui/modal/Modal.svelte @@ -9,6 +9,15 @@ export let confirmText = 'Confirm'; export let cancelText = 'Cancel'; export let confirmDanger = false; // If true, confirm button is styled as danger (red) + export let size: 'sm' | 'md' | 'lg' | 'xl' | '2xl' = 'md'; + + const sizeClasses = { + sm: 'max-w-sm', + md: 'max-w-md', + lg: 'max-w-2xl', + xl: 'max-w-4xl', + '2xl': 'max-w-6xl' + }; const dispatch = createEventDispatcher(); @@ -53,7 +62,7 @@ >
diff --git a/src/lib/client/ui/table/Table.svelte b/src/lib/client/ui/table/Table.svelte index 5e2cf72..35327c6 100644 --- a/src/lib/client/ui/table/Table.svelte +++ b/src/lib/client/ui/table/Table.svelte @@ -105,7 +105,8 @@ return sortDirection === 'desc' ? sorted.reverse() : sorted; } - $: sortedData = sortData(data); + $: sortedData = sortKey ? sortData(data) : data; + $: sortKey, sortDirection, sortedData = sortData(data);
diff --git a/src/routes/settings/logs/+page.svelte b/src/routes/settings/logs/+page.svelte index 0d33c23..a6cca5f 100644 --- a/src/routes/settings/logs/+page.svelte +++ b/src/routes/settings/logs/+page.svelte @@ -2,6 +2,9 @@ import { Eye, Copy } from 'lucide-svelte'; import { alertStore } from '$alerts/store'; import Modal from '$ui/modal/Modal.svelte'; + import JsonView from '$ui/meta/JsonView.svelte'; + import Table from '$ui/table/Table.svelte'; + import type { Column } from '$ui/table/types'; import LogsActionsBar from './components/LogsActionsBar.svelte'; import { createSearchStore } from '$lib/client/stores/search'; import { invalidateAll } from '$app/navigation'; @@ -45,6 +48,38 @@ ERROR: 'text-red-600 dark:text-red-400' }; + // Table columns + const columns: Column[] = [ + { + key: 'timestamp', + header: 'Timestamp', + sortable: true, + sortAccessor: (row) => new Date(row.timestamp).getTime(), + cell: (row) => ({ + html: `${new Date(row.timestamp).toLocaleString()}` + }) + }, + { + key: 'level', + header: 'Level', + sortable: true, + cell: (row) => ({ + html: `${row.level}` + }) + }, + { + key: 'source', + header: 'Source', + sortable: true, + cell: (row) => row.source || '-' + }, + { + key: 'message', + header: 'Message', + cell: (row) => row.message + } + ]; + // Meta modal state let showMetaModal = false; let selectedMeta: unknown = null; @@ -155,98 +190,31 @@ {/if}
- -
- - - -
+ + +
+ + {#if row.meta} +
- - - - - - - {#each filteredLogs as log, index (`${log.timestamp}-${log.level}-${log.source}-${log.message}-${index}`)} - - - - - - - - {:else} - - - - {/each} - -
- Level - - Source - - Message - - Actions -
- {new Date(log.timestamp).toLocaleString()} - - {log.level} - - {log.source || '-'} - - {log.message} - -
- - {#if log.meta} - - {/if} -
-
- No logs found -
- + + + {/if} + + +
@@ -256,14 +224,14 @@ bodyMessage="" confirmText="Close" cancelText="Close" + size="2xl" on:confirm={closeMetaModal} on:cancel={closeMetaModal} > -
{JSON.stringify(
-			selectedMeta,
-			null,
-			2
-		)}
+ class="max-h-[70vh] overflow-auto rounded-lg bg-neutral-50 p-4 font-mono text-sm dark:bg-neutral-800" + > + +