detached d4d9d5e

feat(webkit): add zoom level toast notifications with debouncing

- Add __dumber_showZoomToast function for displaying zoom changes
- Implement proper debouncing to prevent toast spam during rapid zoom
- Add aggressive cleanup to prevent stuck/colliding toasts
- Include emergency cleanup function for orphaned toasts
- Toast shows format: 'Zoom level: ±X% (Y%)'
- 150ms debounce with 2s display duration
commit
d4d9d5e7505c34d8a92be299f6b8c3b04d5e2802
author
bamen · · 2025-09-10 20:35 UTC
committer
bamen · · 2025-09-10 18:49 UTC
parent
c1b1192
signature
unsigned
1 file · +63 -0
M pkg/webkit/toast_js_cgo.go +63 -0
@@ -14,6 +14,8 @@ func getToastScript() string {
1414 container: null,
1515 toasts: [], // {id, element, timeout}
1616 counter: 0,
17+ zoomToastId: null,
18+ zoomDebounceTimer: null,
1719
1820 init() {
1921 if (T.container) return;
@@ -99,6 +101,11 @@ func getToastScript() string {
99101 const toast = T.toasts[toastIndex];
100102 clearTimeout(toast.timeout);
101103
104+ // Clear zoom toast tracking if this is the zoom toast
105+ if (T.zoomToastId === id) {
106+ T.zoomToastId = null;
107+ }
108+
102109 // Animate out
103110 toast.element.style.transform = 'translateX(100%)';
104111 toast.element.style.opacity = '0';
@@ -119,6 +126,9 @@ func getToastScript() string {
119126 }
120127 });
121128 T.toasts = [];
129+ T.zoomToastId = null;
130+ clearTimeout(T.zoomDebounceTimer);
131+ T.zoomDebounceTimer = null;
122132 }
123133 };
124134
@@ -126,6 +136,59 @@ func getToastScript() string {
126136 window.__dumber_showToast = (message, duration) => T.show(message, duration);
127137 window.__dumber_dismissToast = (id) => T.dismiss(id);
128138 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+ };
129192
130193 console.log('✅ Dumber Browser toast system loaded');
131194
@@ -14,6 +14,8 @@ func getToastScript() string {
14 container: null,14 container: null,
15 toasts: [], // {id, element, timeout}15 toasts: [], // {id, element, timeout}
16 counter: 0,16 counter: 0,
17+ zoomToastId: null,
18+ zoomDebounceTimer: null,
17 19
18 init() {20 init() {
19 if (T.container) return;21 if (T.container) return;
@@ -99,6 +101,11 @@ func getToastScript() string {
99 const toast = T.toasts[toastIndex];101 const toast = T.toasts[toastIndex];
100 clearTimeout(toast.timeout);102 clearTimeout(toast.timeout);
101 103
104+ // Clear zoom toast tracking if this is the zoom toast
105+ if (T.zoomToastId === id) {
106+ T.zoomToastId = null;
107+ }
108+
102 // Animate out109 // Animate out
103 toast.element.style.transform = 'translateX(100%)';110 toast.element.style.transform = 'translateX(100%)';
104 toast.element.style.opacity = '0';111 toast.element.style.opacity = '0';
@@ -119,6 +126,9 @@ func getToastScript() string {
119 }126 }
120 });127 });
121 T.toasts = [];128 T.toasts = [];
129+ T.zoomToastId = null;
130+ clearTimeout(T.zoomDebounceTimer);
131+ T.zoomDebounceTimer = null;
122 }132 }
123 };133 };
124 134
@@ -126,6 +136,59 @@ func getToastScript() string {
126 window.__dumber_showToast = (message, duration) => T.show(message, duration);136 window.__dumber_showToast = (message, duration) => T.show(message, duration);
127 window.__dumber_dismissToast = (id) => T.dismiss(id);137 window.__dumber_dismissToast = (id) => T.dismiss(id);
128 window.__dumber_clearToasts = () => T.clear();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+ };
129 192
130 console.log('✅ Dumber Browser toast system loaded');193 console.log('✅ Dumber Browser toast system loaded');
131 194

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