detached 25a9d2e

feat(homepage): add DuckDuckGo favicon fallback for history and favorites

commit
25a9d2e629292facf19b40a46cb28d694b745253
author
brice · · 2025-12-17 17:14 UTC
parent
e30e9ed
signature
unsigned
2 files · +40 -32
M webui/src/pages/homepage/favorites/FavoriteCard.svelte +20 -16
@@ -18,6 +18,14 @@
1818 }
1919 };
2020
21+ const getFaviconUrl = (fav: Favorite): string => {
22+ // Use provided favicon_url if available
23+ if (fav.favicon_url) return fav.favicon_url;
24+ // Construct DuckDuckGo favicon URL from domain
25+ const domain = getDomain(fav.url);
26+ return `https://icons.duckduckgo.com/ip3/${encodeURIComponent(domain)}.ico`;
27+ };
28+
2129 const getDisplayTitle = (fav: Favorite): string => {
2230 const trimmed = fav.title?.trim();
2331 if (trimmed) return trimmed;
@@ -55,22 +63,18 @@
5563 onkeydown={handleKeyDown}
5664 >
5765 <div class="card-favicon">
58- {#if favorite.favicon_url}
59- <img
60- src={favorite.favicon_url}
61- alt=""
62- class="favicon-img"
63- onerror={(e) => {
64- const target = e.target as HTMLImageElement;
65- target.style.display = 'none';
66- const fallback = target.nextElementSibling as HTMLElement;
67- if (fallback) fallback.style.display = 'flex';
68- }}
69- />
70- <div class="favicon-fallback" style="display: none;"></div>
71- {:else}
72- <div class="favicon-fallback"></div>
73- {/if}
66+ <img
67+ src={getFaviconUrl(favorite)}
68+ alt=""
69+ class="favicon-img"
70+ onerror={(e) => {
71+ const target = e.target as HTMLImageElement;
72+ target.style.display = 'none';
73+ const fallback = target.nextElementSibling as HTMLElement;
74+ if (fallback) fallback.style.display = 'flex';
75+ }}
76+ />
77+ <div class="favicon-fallback" style="display: none;"></div>
7478 {#if favorite.shortcut_key}
7579 <div class="shortcut-badge">{favorite.shortcut_key}</div>
7680 {/if}
@@ -18,6 +18,14 @@
18 }18 }
19 };19 };
20 20
21+ const getFaviconUrl = (fav: Favorite): string => {
22+ // Use provided favicon_url if available
23+ if (fav.favicon_url) return fav.favicon_url;
24+ // Construct DuckDuckGo favicon URL from domain
25+ const domain = getDomain(fav.url);
26+ return `https://icons.duckduckgo.com/ip3/${encodeURIComponent(domain)}.ico`;
27+ };
28+
21 const getDisplayTitle = (fav: Favorite): string => {29 const getDisplayTitle = (fav: Favorite): string => {
22 const trimmed = fav.title?.trim();30 const trimmed = fav.title?.trim();
23 if (trimmed) return trimmed;31 if (trimmed) return trimmed;
@@ -55,22 +63,18 @@
55 onkeydown={handleKeyDown}63 onkeydown={handleKeyDown}
56 >64 >
57 <div class="card-favicon">65 <div class="card-favicon">
58- {#if favorite.favicon_url}66+ <img
59- <img67+ src={getFaviconUrl(favorite)}
60- src={favorite.favicon_url}68+ alt=""
61- alt=""69+ class="favicon-img"
62- class="favicon-img"70+ onerror={(e) => {
63- onerror={(e) => {71+ const target = e.target as HTMLImageElement;
64- const target = e.target as HTMLImageElement;72+ target.style.display = 'none';
65- target.style.display = 'none';73+ const fallback = target.nextElementSibling as HTMLElement;
66- const fallback = target.nextElementSibling as HTMLElement;74+ if (fallback) fallback.style.display = 'flex';
67- if (fallback) fallback.style.display = 'flex';75+ }}
68- }}76+ />
69- />77+ <div class="favicon-fallback" style="display: none;"></div>
70- <div class="favicon-fallback" style="display: none;"></div>
71- {:else}
72- <div class="favicon-fallback"></div>
73- {/if}
74 {#if favorite.shortcut_key}78 {#if favorite.shortcut_key}
75 <div class="shortcut-badge">{favorite.shortcut_key}</div>79 <div class="shortcut-badge">{favorite.shortcut_key}</div>
76 {/if}80 {/if}
M webui/src/pages/homepage/history/HistoryItem.svelte +20 -16
@@ -20,6 +20,14 @@
2020 }
2121 };
2222
23+ const getFaviconUrl = (item: HistoryEntry): string => {
24+ // Use provided favicon_url if available
25+ if (item.favicon_url) return item.favicon_url;
26+ // Construct DuckDuckGo favicon URL from domain
27+ const domain = getDomain(item.url).replace(/^www\./, '');
28+ return `https://icons.duckduckgo.com/ip3/${encodeURIComponent(domain)}.ico`;
29+ };
30+
2331 const getDisplayTitle = (item: HistoryEntry): string => {
2432 const trimmed = item.title?.trim();
2533 if (trimmed) return trimmed;
@@ -75,22 +83,18 @@
7583 onkeydown={handleKeyDown}
7684 >
7785 <div class="item-favicon">
78- {#if entry.favicon_url}
79- <img
80- src={entry.favicon_url}
81- alt=""
82- class="favicon-img"
83- onerror={(e) => {
84- const target = e.target as HTMLImageElement;
85- target.style.display = 'none';
86- const fallback = target.nextElementSibling as HTMLElement;
87- if (fallback) fallback.style.display = 'flex';
88- }}
89- />
90- <div class="favicon-fallback" style="display: none;"></div>
91- {:else}
92- <div class="favicon-fallback"></div>
93- {/if}
86+ <img
87+ src={getFaviconUrl(entry)}
88+ alt=""
89+ class="favicon-img"
90+ onerror={(e) => {
91+ const target = e.target as HTMLImageElement;
92+ target.style.display = 'none';
93+ const fallback = target.nextElementSibling as HTMLElement;
94+ if (fallback) fallback.style.display = 'flex';
95+ }}
96+ />
97+ <div class="favicon-fallback" style="display: none;"></div>
9498 </div>
9599
96100 <div class="item-content">
@@ -20,6 +20,14 @@
20 }20 }
21 };21 };
22 22
23+ const getFaviconUrl = (item: HistoryEntry): string => {
24+ // Use provided favicon_url if available
25+ if (item.favicon_url) return item.favicon_url;
26+ // Construct DuckDuckGo favicon URL from domain
27+ const domain = getDomain(item.url).replace(/^www\./, '');
28+ return `https://icons.duckduckgo.com/ip3/${encodeURIComponent(domain)}.ico`;
29+ };
30+
23 const getDisplayTitle = (item: HistoryEntry): string => {31 const getDisplayTitle = (item: HistoryEntry): string => {
24 const trimmed = item.title?.trim();32 const trimmed = item.title?.trim();
25 if (trimmed) return trimmed;33 if (trimmed) return trimmed;
@@ -75,22 +83,18 @@
75 onkeydown={handleKeyDown}83 onkeydown={handleKeyDown}
76 >84 >
77 <div class="item-favicon">85 <div class="item-favicon">
78- {#if entry.favicon_url}86+ <img
79- <img87+ src={getFaviconUrl(entry)}
80- src={entry.favicon_url}88+ alt=""
81- alt=""89+ class="favicon-img"
82- class="favicon-img"90+ onerror={(e) => {
83- onerror={(e) => {91+ const target = e.target as HTMLImageElement;
84- const target = e.target as HTMLImageElement;92+ target.style.display = 'none';
85- target.style.display = 'none';93+ const fallback = target.nextElementSibling as HTMLElement;
86- const fallback = target.nextElementSibling as HTMLElement;94+ if (fallback) fallback.style.display = 'flex';
87- if (fallback) fallback.style.display = 'flex';95+ }}
88- }}96+ />
89- />97+ <div class="favicon-fallback" style="display: none;"></div>
90- <div class="favicon-fallback" style="display: none;"></div>
91- {:else}
92- <div class="favicon-fallback"></div>
93- {/if}
94 </div>98 </div>
95 99
96 <div class="item-content">100 <div class="item-content">

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