v0.8.0 0fa6785

color-scheme.ts RawHistoryFolder

1/**
2 * Color Scheme Detection Module
3 *
4 * Handles GTK theme detection and applies appropriate color scheme preferences
5 * to web pages. This module is injected at document-start to ensure proper
6 * theme initialization before page content loads.
7 */
8
9declare global {
10  interface Window {
11    __dumber_gtk_prefers_dark?: boolean;
12    __dumber_setTheme?: (theme: string) => void;
13  }
14}
15
16interface ColorSchemeConfig {
17  isDark: boolean;
18  colorScheme: string;
19  metaContent: string;
20  rootStyle: string;
21}
22
23class ColorSchemeManager {
24  private config: ColorSchemeConfig;
25
26  constructor() {
27    // Use GTK preference if available, otherwise detect from system
28    const prefersDark = window.__dumber_gtk_prefers_dark ?? false;
29
30    this.config = {
31      isDark: prefersDark,
32      colorScheme: prefersDark ? 'dark' : 'light',
33      metaContent: prefersDark ? 'dark light' : 'light dark',
34      rootStyle: prefersDark ? ':root{color-scheme:dark;}' : ':root{color-scheme:light;}'
35    };
36
37    this.initialize();
38  }
39
40  private initialize(): void {
41    try {
42      console.log(`[dumber] color-scheme set: ${this.config.colorScheme}`);
43      console.log(`[dumber] GTK detected color mode: ${this.config.colorScheme}`);
44
45      // Notify native layer of theme preference
46      this.notifyNativeLayer();
47
48      // Inject color-scheme meta tag
49      this.injectColorSchemeMeta();
50
51      // Apply root styles
52      this.applyRootStyles();
53
54      // Override matchMedia for proper prefers-color-scheme support
55      this.overrideMatchMedia();
56
57      // Set up theme integration with existing UI components
58      this.setupThemeIntegration();
59
60    } catch (error) {
61      console.warn('[dumber] color-scheme injection failed', error);
62    }
63  }
64
65  private notifyNativeLayer(): void {
66    try {
67      (window as any).webkit?.messageHandlers?.dumber?.postMessage(
68        JSON.stringify({
69          type: 'theme',
70          value: this.config.colorScheme
71        })
72      );
73    } catch (error) {
74      // Ignore errors - native layer may not be available
75    }
76  }
77
78  private injectColorSchemeMeta(): void {
79    const meta = document.createElement('meta');
80    meta.name = 'color-scheme';
81    meta.content = this.config.metaContent;
82    document.documentElement.appendChild(meta);
83  }
84
85  private applyRootStyles(): void {
86    const style = document.createElement('style');
87    style.textContent = this.config.rootStyle;
88    document.documentElement.appendChild(style);
89  }
90
91  private overrideMatchMedia(): void {
92    const darkQuery = '(prefers-color-scheme: dark)';
93    const lightQuery = '(prefers-color-scheme: light)';
94    const originalMatchMedia = window.matchMedia;
95
96    window.matchMedia = (query: string): MediaQueryList => {
97      if (typeof query === 'string' && (query.includes(darkQuery) || query.includes(lightQuery))) {
98        // Create a mock MediaQueryList that reflects our GTK preference
99        const matches = query.includes('dark') ? this.config.isDark : !this.config.isDark;
100
101        return {
102          matches,
103          media: query,
104          onchange: null,
105          addListener: () => {},
106          removeListener: () => {},
107          addEventListener: () => {},
108          removeEventListener: () => {},
109          dispatchEvent: () => false
110        } as MediaQueryList;
111      }
112
113      // For non-color-scheme queries, use the original implementation
114      return originalMatchMedia.call(window, query);
115    };
116  }
117
118  private setupThemeIntegration(): void {
119    // Integrate with unified theme setter if available
120    if (window.__dumber_setTheme) {
121      window.__dumber_setTheme(this.config.colorScheme);
122    } else {
123      // Retry after a short delay in case the bridge hasn't loaded yet
124      setTimeout(() => {
125        if (window.__dumber_setTheme) {
126          window.__dumber_setTheme(this.config.colorScheme);
127        } else {
128          // Fallback: directly apply dark class for Tailwind compatibility
129          console.warn('[dumber] Theme setter not available, using fallback');
130          if (this.config.isDark) {
131            document.documentElement.classList.add('dark');
132          } else {
133            document.documentElement.classList.remove('dark');
134          }
135        }
136      }, 100);
137    }
138  }
139
140  /**
141   * Updates the color scheme preference at runtime
142   * @param isDark Whether to use dark mode
143   */
144  public updateColorScheme(isDark: boolean): void {
145    this.config.isDark = isDark;
146    this.config.colorScheme = isDark ? 'dark' : 'light';
147    this.config.metaContent = isDark ? 'dark light' : 'light dark';
148    this.config.rootStyle = isDark ? ':root{color-scheme:dark;}' : ':root{color-scheme:light;}';
149
150    // Re-apply all configurations
151    this.initialize();
152  }
153}
154
155// Initialize color scheme management
156const colorSchemeManager = new ColorSchemeManager();
157
158// Export for potential runtime updates
159(window as any).__dumber_color_scheme_manager = colorSchemeManager;
160
161export default colorSchemeManager;

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close