v0.3.0 d226234

html-builder.ts RawHistoryFolder

1export class HTMLBuilder {
2  static createBaseHTML(): string {
3    const faviconData = this.getFaviconDataURL();
4    return `
5<!DOCTYPE html>
6<html lang="en">
7<head>
8    <meta charset="UTF-8">
9    <meta name="viewport" content="width=device-width, initial-scale=1.0">
10    <title>Dumber Browser</title>
11    <link rel="icon" type="image/svg+xml" href="${faviconData}">
12    <style>
13        ${this.getStyles()}
14    </style>
15</head>
16<body>
17    <div class="container">
18        <div class="history-section">
19            <h2 class="section-title">Recent History</h2>
20            <div id="historyList" class="history-list">
21                <div class="loading">Loading history...</div>
22            </div>
23        </div>
24        
25        <div class="shortcuts-section">
26            <h2 class="section-title">Search Shortcuts</h2>
27            <div id="shortcuts" class="shortcuts-grid">
28                <div class="loading">Loading shortcuts...</div>
29            </div>
30        </div>
31    </div>
32    <script type="module" src="./main.js"></script>
33</body>
34</html>`;
35  }
36
37  private static getFaviconDataURL(): string {
38    const svg = HTMLBuilder.getFaviconSVG();
39    // Minimal encoding for safe inline usage
40    const encoded = encodeURIComponent(svg)
41      .replace(/'/g, '%27')
42      .replace(/\(/g, '%28')
43      .replace(/\)/g, '%29');
44    return `data:image/svg+xml;utf8,${encoded}`;
45  }
46
47  static getFaviconSVG(): string {
48    // Stylized D with speed bolt on a subtle dark rounded card
49    return `<?xml version="1.0" encoding="UTF-8"?>
50<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
51  <defs>
52    <linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
53      <stop offset="0%" stop-color="#121212"/>
54      <stop offset="100%" stop-color="#1e1e1e"/>
55    </linearGradient>
56    <filter id="inset" x="-20%" y="-20%" width="140%" height="140%">
57      <feOffset dx="0" dy="1"/>
58      <feGaussianBlur stdDeviation="0.6" result="blur"/>
59      <feComposite in2="SourceAlpha" operator="out"/>
60      <feColorMatrix type="matrix" values="0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 0.5 0"/>
61      <feBlend in="SourceGraphic" mode="normal"/>
62    </filter>
63  </defs>
64  <rect x="4" y="4" width="56" height="56" rx="14" ry="14" fill="url(#bg)" filter="url(#inset)"/>
65  <rect x="4.5" y="4.5" width="55" height="55" rx="13.5" ry="13.5" fill="none" stroke="#252a2f" stroke-width="1"/>
66  <!-- D glyph (slightly larger and wider) -->
67  <path d="M20 16 L20 48 M20 16 H34 q12 0 12 16 t-12 16 H20" fill="none" stroke="#e9eef3" stroke-width="4.5" stroke-linecap="round" stroke-linejoin="round" transform="translate(33,32) scale(1.12,1.10) translate(-33,-32)"/>
68  <!-- Speed bolt accent (significantly larger, centered) -->
69  <path d="M36 22 L32 34 h6 l-8 16 4-12 h-6 z" fill="#ff9800" opacity="0.95" transform="translate(33,32) scale(1.8) translate(-33,-32) translate(0,-3)"/>
70</svg>`;
71  }
72
73  private static getStyles(): string {
74    return `
75        * {
76            margin: 0;
77            padding: 0;
78            box-sizing: border-box;
79        }
80        
81        body {
82            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
83            background: #1a1a1a;
84            color: #ffffff;
85            height: 100vh;
86            display: flex;
87            flex-direction: column;
88            overflow-x: hidden; /* prevent horizontal scroll from long content */
89        }
90        
91        .container {
92            flex: 1;
93            display: flex;
94            max-width: 1200px;
95            margin: 0 auto;
96            width: 100%;
97            padding: 2rem;
98            gap: 2rem;
99            overflow-x: hidden;
100        }
101        
102        .history-section {
103            flex: 1;
104            min-height: 0;
105            min-width: 0; /* allow flex child to shrink */
106        }
107        
108        .shortcuts-section {
109            flex: 1;
110            min-height: 0;
111            min-width: 0; /* allow flex child to shrink */
112        }
113        
114        .section-title {
115            font-size: 1.5rem;
116            margin-bottom: 1rem;
117            color: #ffffff;
118            border-bottom: 2px solid #404040;
119            padding-bottom: 0.5rem;
120        }
121        
122        .history-list {
123            overflow-y: auto;
124            overflow-x: hidden;
125            max-height: calc(100vh - 8rem);
126            max-width: 100%;
127        }
128        
129        .history-item {
130            padding: 0.75rem;
131            margin-bottom: 0.5rem;
132            background: #2d2d2d;
133            border-radius: 6px;
134            cursor: pointer;
135            transition: background 0.2s;
136            border-left: 3px solid #404040;
137            overflow: hidden;
138            max-width: 100%;
139        }
140        
141        .history-item:hover {
142            background: #3d3d3d;
143            border-left-color: #0066cc;
144        }
145        
146        .history-line {
147            display: flex;
148            gap: 0.5rem;
149            white-space: nowrap;
150            align-items: center;
151            overflow: hidden;
152            width: 100%;
153            min-width: 0; /* allow flex children to shrink */
154        }
155        .history-favicon-chip {
156            flex: 0 0 20px;
157            width: 20px; height: 20px;
158            border-radius: 50%;
159            background: #ccc;
160            border: 1px solid rgba(0,0,0,.12);
161            box-shadow: 0 1px 2px rgba(0,0,0,.12);
162            display: flex;
163            align-items: center;
164            justify-content: center;
165        }
166        .history-favicon-img {
167            width: 16px; height: 16px;
168            filter: brightness(1.06) contrast(1.03);
169            image-rendering: -webkit-optimize-contrast;
170        }
171
172        .history-title { font-size: 0.95rem; color: #e6e6e6; flex: 0 0 auto; }
173        .history-domain { font-size: 0.9rem; color: #a5a5a5; flex: 0 0 auto; }
174        .history-sep { color: #666; flex: 0 0 auto; }
175        .history-url {
176            font-size: 0.9rem;
177            color: #7a7a7a; /* darker than domain */
178            flex: 1 1 auto;
179            min-width: 0; /* critical for flex truncation */
180            overflow: hidden;
181            text-overflow: ellipsis; /* fallback */
182            -webkit-mask-image: linear-gradient(to right, rgba(0,0,0,1) 85%, rgba(0,0,0,0) 100%);
183            mask-image: linear-gradient(to right, rgba(0,0,0,1) 85%, rgba(0,0,0,0) 100%);
184        }
185        
186        .shortcuts-grid {
187            display: grid;
188            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
189            gap: 1rem;
190            max-height: calc(100vh - 8rem);
191            overflow-y: auto;
192        }
193        
194        .shortcut {
195            padding: 1rem;
196            background: #2d2d2d;
197            border: 2px solid #404040;
198            border-radius: 8px;
199            cursor: pointer;
200            transition: all 0.2s;
201            text-align: center;
202        }
203        
204        .shortcut:hover {
205            background: #3d3d3d;
206            border-color: #0066cc;
207            transform: translateY(-2px);
208        }
209        
210        .shortcut-key {
211            font-weight: bold;
212            color: #0066cc;
213            margin-bottom: 0.5rem;
214            font-size: 1.1rem;
215        }
216        
217        .shortcut-desc {
218            color: #888;
219            font-size: 0.9rem;
220        }
221        
222        .loading {
223            padding: 2rem;
224            text-align: center;
225            color: #888;
226        }
227        
228        .empty-state {
229            padding: 3rem 2rem;
230            text-align: center;
231            color: #666;
232        }
233        
234        .empty-state h3 {
235            margin-bottom: 1rem;
236            color: #888;
237        }
238        
239        @media (max-width: 768px) {
240            .container {
241                flex-direction: column;
242                padding: 1rem;
243            }
244            
245            .shortcuts-grid {
246                grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
247            }
248        }
249    `;
250  }
251}

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