v0.5.0 f906adb

toast_js_cgo.go RawHistoryFolder

1//go:build webkit_cgo
2
3package webkit
4
5// getToastScript returns the injected toast notification component script.
6// Provides a reusable toast system that can display messages across all pages.
7func getToastScript() string {
8	return `(() => {
9  try {
10    if (window.__dumber_toast_loaded) return; // idempotent
11    window.__dumber_toast_loaded = true;
12
13    const T = {
14      container: null,
15      toasts: [], // {id, element, timeout}
16      counter: 0,
17      zoomToastId: null,
18      zoomDebounceTimer: null,
19
20      init() {
21        if (T.container) return;
22        
23        // Create toast container
24        const container = document.createElement('div');
25        container.id = 'dumber-toast-container';
26        container.style.cssText = 'position:fixed;bottom:20px;right:20px;z-index:2147483646;pointer-events:none;';
27        document.documentElement.appendChild(container);
28        T.container = container;
29
30        // Add CSS styles
31        const style = document.createElement('style');
32        style.textContent = ` +
33		"`" + `
34          .dumber-toast {
35            background: rgba(18, 18, 18, 0.95);
36            color: #eee;
37            padding: 12px 16px;
38            border-radius: 8px;
39            border: 1px solid #444;
40            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
41            font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, "Helvetica Neue", Arial, sans-serif;
42            font-size: 14px;
43            line-height: 1.4;
44            margin-bottom: 8px;
45            max-width: 320px;
46            word-wrap: break-word;
47            backdrop-filter: blur(8px);
48            -webkit-backdrop-filter: blur(8px);
49            pointer-events: auto;
50            cursor: default;
51            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
52            transform: translateX(100%);
53            opacity: 0;
54          }
55          
56          .dumber-toast.show {
57            transform: translateX(0);
58            opacity: 1;
59          }
60          
61          .dumber-toast:hover {
62            background: rgba(27, 27, 27, 0.95);
63            border-color: #555;
64          }
65        ` + "`" + `;
66        document.documentElement.appendChild(style);
67      },
68
69      show(message, duration = 2500) {
70        T.init();
71        
72        const id = ++T.counter;
73        const toast = document.createElement('div');
74        toast.className = 'dumber-toast';
75        toast.textContent = message || 'Notification';
76        toast.setAttribute('data-toast-id', id);
77        
78        // Click to dismiss
79        toast.addEventListener('click', () => T.dismiss(id));
80        
81        T.container.appendChild(toast);
82        
83        // Trigger show animation
84        requestAnimationFrame(() => {
85          toast.classList.add('show');
86        });
87        
88        // Auto-dismiss
89        const timeout = setTimeout(() => T.dismiss(id), duration);
90        
91        T.toasts.push({ id, element: toast, timeout });
92        
93        console.log('📋 Toast:', message);
94        return id;
95      },
96
97      dismiss(id) {
98        const toastIndex = T.toasts.findIndex(t => t.id === id);
99        if (toastIndex === -1) return;
100        
101        const toast = T.toasts[toastIndex];
102        clearTimeout(toast.timeout);
103        
104        // Clear zoom toast tracking if this is the zoom toast
105        if (T.zoomToastId === id) {
106          T.zoomToastId = null;
107        }
108        
109        // Animate out
110        toast.element.style.transform = 'translateX(100%)';
111        toast.element.style.opacity = '0';
112        
113        setTimeout(() => {
114          if (toast.element && toast.element.parentNode) {
115            toast.element.parentNode.removeChild(toast.element);
116          }
117          T.toasts.splice(toastIndex, 1);
118        }, 300);
119      },
120
121      clear() {
122        T.toasts.forEach(toast => {
123          clearTimeout(toast.timeout);
124          if (toast.element && toast.element.parentNode) {
125            toast.element.parentNode.removeChild(toast.element);
126          }
127        });
128        T.toasts = [];
129        T.zoomToastId = null;
130        clearTimeout(T.zoomDebounceTimer);
131        T.zoomDebounceTimer = null;
132      }
133    };
134
135    // Public API for native integration
136    window.__dumber_showToast = (message, duration) => T.show(message, duration);
137    window.__dumber_dismissToast = (id) => T.dismiss(id);
138    window.__dumber_clearToasts = () => T.clear();
139    window.__dumber_showZoomToast = (zoomLevel) => {
140      console.log('[dumber] showZoomToast called with level:', zoomLevel);
141      
142      // Clear existing debounce timer
143      clearTimeout(T.zoomDebounceTimer);
144      
145      // Force remove ALL zoom toasts immediately (aggressive cleanup)
146      for (let i = T.toasts.length - 1; i >= 0; i--) {
147        const toast = T.toasts[i];
148        if (toast.element && (toast.element.textContent.includes('Zoom level:') || toast.id === T.zoomToastId)) {
149          clearTimeout(toast.timeout);
150          if (toast.element.parentNode) {
151            toast.element.parentNode.removeChild(toast.element);
152          }
153          T.toasts.splice(i, 1);
154        }
155      }
156      T.zoomToastId = null;
157      
158      // Also cleanup any orphaned zoom toast elements in DOM
159      const orphanedZoomToasts = document.querySelectorAll('.dumber-toast');
160      orphanedZoomToasts.forEach(el => {
161        if (el.textContent.includes('Zoom level:')) {
162          el.parentNode && el.parentNode.removeChild(el);
163        }
164      });
165      
166      // Debounce the toast display
167      T.zoomDebounceTimer = setTimeout(() => {
168        const percentage = Math.round(zoomLevel * 100);
169        const sign = percentage > 100 ? '+' : '';
170        const diff = percentage - 100;
171        const message = 'Zoom level: ' + sign + diff + '% (' + percentage + '%)';
172        console.log('[dumber] Showing zoom toast:', message);
173        T.zoomToastId = T.show(message, 2000);
174      }, 150);
175    };
176    
177    // Test function for manual debugging
178    window.__dumber_testToast = () => {
179      console.log('[dumber] Testing toast system...');
180      return T.show('Toast system is working!', 2000);
181    };
182    
183    // Emergency cleanup function for stuck toasts
184    window.__dumber_forceCleanupToasts = () => {
185      console.log('[dumber] Force cleaning up all toasts...');
186      T.clear();
187      // Also force remove any orphaned toast elements
188      const orphans = document.querySelectorAll('.dumber-toast');
189      orphans.forEach(el => el.parentNode && el.parentNode.removeChild(el));
190      console.log('[dumber] Cleanup complete, removed', orphans.length, 'orphaned toasts');
191    };
192
193    console.log('✅ Dumber Browser toast system loaded');
194    
195  } catch (e) {
196    console.error('❌ Failed to initialize Dumber toast system:', e);
197  }
198})();`
199}

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