Skip to content

Commit a1f637f

Browse files
committed
Add dark mode UI
This uses the OS appearance `prefers-color-scheme` CSS support in tailwind. There is no support for an explicit preference right now, and I'm not sure I'll ever add one. This preference also drives a svelte store that we use to select the syntax highlighting theme, such that we use the GitHub dark theme when dark mode is enabled. This also involves changing the color scheme for the SVG search preference buttons, as it's either not easy or not possible to use media queries to drive SVG fill/stroke colors, and the existing ones only worked for the light theme. I've changed the color styling of these buttons to be driven purely by CSS, which has resulted in a materially different appearance.
1 parent 8199a9c commit a1f637f

21 files changed

+85
-58
lines changed

.changeset/calm-berries-leave.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"neogrok": minor
3+
---
4+
5+
Add dark mode (toggle using OS appearance)

src/app.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@
2323
<meta name="viewport" content="width=device-width" />
2424
%sveltekit.head%
2525
</head>
26-
<body data-sveltekit-preload-data="hover">
26+
<body
27+
data-sveltekit-preload-data="hover"
28+
class="dark:bg-black dark:text-white"
29+
>
2730
<div style="display: contents">%sveltekit.body%</div>
2831
</body>
2932
</html>

src/lib/expression.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
</script>
44

55
<code
6-
class="bg-gray-100 p-1 text-sm"
6+
class="bg-gray-100 dark:bg-gray-800 p-1 text-sm"
77
class:whitespace-pre-wrap={wrap}
88
class:whitespace-nowrap={!wrap}><slot /></code
99
>

src/lib/integer-input.svelte

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,10 @@
3333
inputmode="numeric"
3434
{size}
3535
bind:value={stringValue}
36-
class={`p-1 border shadow-sm focus:outline-none ${computeInputColor({
37-
error: !valid,
38-
pending,
39-
})}`}
36+
class={`p-1 border shadow-sm focus:outline-none dark:bg-black ${computeInputColor(
37+
{
38+
error: !valid,
39+
pending,
40+
},
41+
)}`}
4042
/>

src/lib/link.svelte

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
export let to: string;
33
</script>
44

5-
<a class="text-cyan-700 hover:underline decoration-1" href={to}>
5+
<a
6+
class="text-cyan-700 dark:text-cyan-400 hover:underline decoration-1"
7+
href={to}
8+
>
69
<slot />
710
</a>

src/lib/theme.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { readonly, writable } from "svelte/store";
2+
3+
export type BrowserTheme = "light" | "dark";
4+
5+
const query =
6+
"matchMedia" in globalThis ? matchMedia("(prefers-color-scheme:dark)") : null;
7+
const writableTheme = writable<BrowserTheme>(query?.matches ? "dark" : "light");
8+
query?.addEventListener("change", (event) => {
9+
writableTheme.set(event.matches ? "dark" : "light");
10+
});
11+
12+
export const browserTheme = readonly(writableTheme);

src/lib/toggle-match-sort-order.svelte

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
</script>
77

88
<button
9-
class={`rounded w-6 grid place-items-center ${
9+
class={`border-[1.5px] rounded w-6 grid place-items-center ${
1010
$matchSortOrder === "line-number"
11-
? "bg-sky-500 hover:bg-slate-500"
12-
: "hover:bg-slate-300"
11+
? "text-cyan-600 dark:text-cyan-400 border-cyan-600 dark:border-cyan-400"
12+
: "border-dashed border-black dark:border-white"
1313
}`}
1414
title={$matchSortOrder === "line-number"
1515
? "sort matches by score"
@@ -19,11 +19,5 @@
1919
$matchSortOrder === "line-number" ? "score" : "line-number";
2020
}}
2121
>
22-
<ListOrdered
23-
class="inline"
24-
size={18}
25-
color={$matchSortOrder === "line-number" ? "white" : "black"}
26-
strokeWidth={1}
27-
fill={$matchSortOrder === "line-number" ? "white" : "black"}
28-
/>
22+
<ListOrdered class="inline" size={18} strokeWidth={1.5} />
2923
</button>

src/lib/toggle-search-type.svelte

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,15 @@
66
</script>
77

88
<button
9-
class={`rounded w-6 grid place-items-center ${
9+
class={`border-[1.5px] rounded w-6 grid place-items-center ${
1010
$searchType === "live"
11-
? "bg-sky-500 hover:bg-slate-500"
12-
: "hover:bg-slate-300"
11+
? "text-cyan-600 dark:text-cyan-400 border-cyan-600 dark:border-cyan-400"
12+
: "border-dashed border-black dark:border-white"
1313
}`}
1414
title={$searchType === "live" ? "disable live search" : "enable live search"}
1515
on:click={() => {
1616
$searchType = $searchType === "live" ? "manual" : "live";
1717
}}
1818
>
19-
<Zap
20-
class="inline"
21-
size={18}
22-
color={$searchType === "live" ? "white" : "black"}
23-
strokeWidth={1}
24-
fill={$searchType === "live" ? "white" : "black"}
25-
/>
19+
<Zap class="inline" size={18} strokeWidth={1.5} />
2620
</button>

src/routes/(opengrok-compat)/search/search-query-conversion.svelte

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
kind: "highlighted",
3636
content: luceneQuery.slice(
3737
highlightedLocation.start,
38-
highlightedLocation.end
38+
highlightedLocation.end,
3939
),
4040
},
4141
{
@@ -70,10 +70,11 @@
7070
<h3 class="text-center font-semibold">
7171
Detected OpenGrok URL parameters
7272
</h3>
73-
<pre class="bg-gray-100 p-2 text-sm whitespace-normal">{JSON.stringify(
73+
<pre
74+
class="bg-gray-100 dark:bg-gray-800 p-2 text-sm whitespace-normal">{JSON.stringify(
7475
openGrokParams,
7576
null,
76-
2
77+
2,
7778
)}</pre>
7879
</div>
7980
<span
@@ -88,7 +89,7 @@
8889
{:else}
8990
<Expression wrap
9091
>{#each renderedLuceneQuery as token}{#if token.kind === "highlighted"}<span
91-
class="bg-orange-200">{token.content}</span
92+
class="bg-orange-200 dark:bg-orange-800">{token.content}</span
9293
>{:else}{token.content}{/if}{/each}</Expression
9394
>
9495
{/if}

src/routes/(search-page)/line-group.svelte

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
<script lang="ts">
22
import type { ResultFile } from "$lib/server/search-api";
33
import { onMount } from "svelte";
4+
import type { ThemedToken, BundledLanguage } from "shikiji";
5+
import { browserTheme, type BrowserTheme } from "$lib/theme";
46
import type { LineGroup } from "./chunk-renderer";
57
import RenderedContent from "./rendered-content.svelte";
6-
import type { ThemedToken, BundledLanguage } from "shikiji";
78
89
export let lines: LineGroup;
910
export let file: ResultFile;
@@ -26,7 +27,7 @@
2627
let visible = false;
2728
let highlights: undefined | ReadonlyArray<ReadonlyArray<ThemedToken>>;
2829
29-
const highlight = async (code: string) => {
30+
const highlight = async (code: string, theme: BrowserTheme) => {
3031
// We dynamically import shiki itself because it's huge and won't be
3132
// needed by those landing on the home page with no search query, or
3233
// on the server at all.
@@ -50,7 +51,7 @@
5051
// It's worth checking again, as downloading that chunk can take a
5152
// while, and highlighting can occupy meaningful CPU time.
5253
highlights = await codeToThemedTokens(code, {
53-
theme: "github-light",
54+
theme: `github-${theme}`,
5455
lang,
5556
});
5657
} else if (language !== "text") {
@@ -72,7 +73,10 @@
7273
if (!lines.some(({ line }) => line.text.length >= 1000)) {
7374
// Shikiji only accepts a single string even though it goes
7475
// right ahead and splits it :(.
75-
highlight(lines.map(({ line: { text } }) => text).join("\n"));
76+
highlight(
77+
lines.map(({ line: { text } }) => text).join("\n"),
78+
$browserTheme,
79+
);
7680
} else {
7781
// We can have defined `highlights` here if our LineGroup was cut in two
7882
// by a now-removed "hidden" threshold. Having highlights for part of
@@ -108,7 +112,7 @@
108112
class="py-1 grid grid-cols-[minmax(2rem,_min-content)_1fr] gap-x-2 whitespace-pre overflow-x-auto"
109113
>
110114
{#each lines as { lineNumber, line }, i}
111-
<span class="select-none text-gray-600 text-right pr-1">
115+
<span class="select-none text-gray-600 dark:text-gray-500 text-right pr-1">
112116
{#if file.fileUrl && file.lineNumberTemplate}
113117
<a
114118
class="hover:underline decoration-1"

0 commit comments

Comments
 (0)