Skip to content

Commit d852491

Browse files
committed
Merge "Default Search Engine and Customize UI" T3-Content#124 from Und*ck
1 parent 43d29a2 commit d852491

3 files changed

Lines changed: 296 additions & 22 deletions

File tree

public/gear.svg

Lines changed: 5 additions & 0 deletions
Loading

src/global.css

Lines changed: 201 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
/* @import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"); */
2+
13
/* Font fallback that closely matches Inter metrics */
24
@font-face {
35
font-family: "Inter Fallback";
@@ -72,7 +74,8 @@ button {
7274
}
7375

7476
input,
75-
textarea {
77+
textarea,
78+
select {
7679
font: inherit;
7780
}
7881

@@ -127,6 +130,115 @@ textarea {
127130
background: #28a745;
128131
}
129132

133+
/* Button container for centering */
134+
.button-container {
135+
display: flex;
136+
justify-content: center;
137+
margin-top: 16px;
138+
}
139+
140+
/* Customize button styles */
141+
.customize-button {
142+
display: flex;
143+
align-items: center;
144+
gap: 8px;
145+
padding: 10px 16px;
146+
background: #f5f5f5;
147+
border: 1px solid #ddd;
148+
border-radius: 6px;
149+
color: #333;
150+
font-size: 14px;
151+
font-weight: 500;
152+
transition: all 0.2s ease;
153+
cursor: pointer;
154+
}
155+
156+
.customize-button:hover {
157+
background: #e8e8e8;
158+
border-color: #bbb;
159+
}
160+
161+
.customize-button.active {
162+
background: #e0e0e0;
163+
border-color: #999;
164+
}
165+
166+
.customize-button img {
167+
width: 16px;
168+
height: 16px;
169+
transition: transform 0.2s ease;
170+
flex-shrink: 0;
171+
}
172+
173+
.customize-button.active img {
174+
transform: rotate(90deg);
175+
}
176+
177+
/* Settings panel styles */
178+
.settings-panel {
179+
max-height: 0;
180+
overflow: hidden;
181+
margin-top: 0;
182+
transition: all 0.3s ease;
183+
opacity: 0;
184+
}
185+
186+
.settings-panel.open {
187+
max-height: 500px;
188+
margin-top: 16px;
189+
opacity: 1;
190+
}
191+
192+
.settings-content {
193+
background: #f8f8f8;
194+
border: 1px solid #ddd;
195+
border-radius: 8px;
196+
padding: 20px;
197+
text-align: left;
198+
}
199+
200+
.settings-content h3 {
201+
color: #333;
202+
margin-bottom: 16px;
203+
font-size: 18px;
204+
font-weight: 600;
205+
}
206+
207+
.setting-group {
208+
margin-bottom: 16px;
209+
}
210+
211+
.setting-group label {
212+
display: block;
213+
color: #555;
214+
font-weight: 500;
215+
margin-bottom: 8px;
216+
font-size: 14px;
217+
}
218+
219+
.search-select {
220+
width: 100%;
221+
padding: 8px 12px;
222+
border: 1px solid #ccc;
223+
border-radius: 4px;
224+
background: #fff;
225+
color: #333;
226+
font-size: 14px;
227+
}
228+
229+
.search-select:focus {
230+
outline: none;
231+
border-color: #007acc;
232+
box-shadow: 0 0 0 2px rgba(0, 122, 204, 0.2);
233+
}
234+
235+
.setting-description {
236+
margin-top: 6px;
237+
font-size: 12px;
238+
color: #666;
239+
line-height: 1.4;
240+
}
241+
130242
/* Add footer styles */
131243
.footer {
132244
position: fixed;
@@ -191,4 +303,92 @@ textarea {
191303
.copy-button:active {
192304
background: #333;
193305
}
306+
307+
/* Dark theme for customize button */
308+
.customize-button {
309+
background: #191919;
310+
border-color: #3d3d3d;
311+
color: #ddd;
312+
}
313+
314+
.customize-button:hover {
315+
background: #222;
316+
border-color: #555;
317+
}
318+
319+
.customize-button.active {
320+
background: #333;
321+
border-color: #666;
322+
}
323+
324+
.customize-button img {
325+
filter: invert(1);
326+
}
327+
328+
.customize-button.active img {
329+
filter: invert(1);
330+
}
331+
332+
/* Dark theme for settings panel */
333+
.settings-content {
334+
background: #191919;
335+
border-color: #3d3d3d;
336+
}
337+
338+
.settings-content h3 {
339+
color: #ddd;
340+
}
341+
342+
.setting-group label {
343+
color: #999;
344+
}
345+
346+
.search-select {
347+
background: #191919;
348+
border-color: #3d3d3d;
349+
color: #ddd;
350+
}
351+
352+
.search-select:focus {
353+
border-color: #007acc;
354+
box-shadow: 0 0 0 2px rgba(0, 122, 204, 0.3);
355+
}
356+
357+
.setting-description {
358+
color: #999;
359+
}
194360
}
361+
362+
/* Responsive design */
363+
@media (max-width: 640px) {
364+
.content-container {
365+
max-width: 100%;
366+
padding: 0 16px;
367+
}
368+
369+
.url-container {
370+
flex-direction: column;
371+
gap: 12px;
372+
}
373+
374+
.copy-button {
375+
align-self: stretch;
376+
justify-content: center;
377+
padding: 12px;
378+
}
379+
380+
.customize-button {
381+
align-self: stretch;
382+
justify-content: center;
383+
}
384+
385+
.settings-content {
386+
padding: 16px;
387+
}
388+
389+
.footer {
390+
position: relative;
391+
margin-top: 32px;
392+
bottom: auto;
393+
}
394+
}

src/main.ts

Lines changed: 90 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,79 @@
1-
import { bangs } from "./bang";
1+
import {bangs} from "./bang";
22
import "./global.css";
33

4+
// Default search engines for when no bang is used
5+
const DEFAULT_SEARCH_ENGINES = [
6+
{name: "Google", value: "google", url: "https://www.google.com/search?q={{{s}}}"},
7+
{name: "DuckDuckGo", value: "duckduckgo", url: "https://duckduckgo.com/?q={{{s}}}"},
8+
{name: "DuckDuckGo HTML", value: "duckduckgo-html", url: "https://html.duckduckgo.com/html/?q={{{s}}}"},
9+
{name: "Google No AI (udm=14)", value: "google-no-ai", url: "https://www.google.com/search?udm=14&q={{{s}}}"},
10+
{name: "Kagi", value: "kagi", url: "https://kagi.com/search?q={{{s}}}"},
11+
{name: "Swisscows", value: "swisscows", url: "https://swisscows.com/web?query={{{s}}}"},
12+
{name: "Startpage", value: "startpage", url: "https://www.startpage.com/sp/search?query={{{s}}}"},
13+
{name: "Qwant", value: "qwant", url: "https://www.qwant.com/?q={{{s}}}"},
14+
{name: "T3 Chat", value: "t3chat", url: "https://www.t3.chat/new?q={{{s}}}"}
15+
];
16+
417
function noSearchDefaultPageRender() {
518
const app = document.querySelector<HTMLDivElement>("#app")!;
619
app.innerHTML = `
720
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh;">
821
<div class="content-container">
9-
<h1>BangBrave</h1>
10-
<p>A fork of Und*ck to set the main search engine to Brave Search </a></p>
22+
<h1>FastBangs</h1>
23+
<p>A fork of Und*ck to set the main search engine to Brave Search</p>
1124
<div class="url-container">
1225
<input
1326
type="text"
1427
class="url-input"
15-
value="https://bangbrave.xdpxi.dev?q=%s"
28+
value="https://fastbangs.xdpxi.dev?q=%s"
1629
readonly
1730
/>
1831
<button class="copy-button">
1932
<img src="/clipboard.svg" alt="Copy" />
2033
</button>
2134
</div>
35+
<div class="button-container">
36+
<button class="customize-button">
37+
<img src="/gear.svg" alt="Settings" />
38+
Customize
39+
</button>
40+
</div>
41+
42+
<div class="settings-panel">
43+
<div class="settings-content">
44+
<h3>Search Customization</h3>
45+
<div class="setting-group">
46+
<label for="default-search-select">Default search engine (when no bang is used):</label>
47+
<select id="default-search-select" class="search-select">
48+
${DEFAULT_SEARCH_ENGINES.map(engine =>
49+
`<option value="${engine.value}">${engine.name}</option>`
50+
).join('')}
51+
</select>
52+
<p class="setting-description">This search engine will be used when you search without a bang (like "!g" or "!gh")</p>
53+
</div>
54+
</div>
55+
</div>
2256
</div>
57+
<footer class="footer">
58+
<a href="https://t3.chat" target="_blank">t3.chat</a>
59+
60+
<a href="https://x.com/theo" target="_blank">theo</a>
61+
62+
<a href="https://github.com/t3dotgg/unduck" target="_blank">github</a>
63+
</footer>
2364
</div>
2465
`;
2566

2667
const copyButton = app.querySelector<HTMLButtonElement>(".copy-button")!;
2768
const copyIcon = copyButton.querySelector("img")!;
2869
const urlInput = app.querySelector<HTMLInputElement>(".url-input")!;
70+
const customizeButton = app.querySelector<HTMLButtonElement>(".customize-button")!;
71+
const settingsPanel = app.querySelector<HTMLDivElement>(".settings-panel")!;
72+
const defaultSearchSelect = app.querySelector<HTMLSelectElement>("#default-search-select")!;
73+
74+
// Load saved default search engine
75+
const savedDefaultSearch = localStorage.getItem("default-search-engine") || "google";
76+
defaultSearchSelect.value = savedDefaultSearch;
2977

3078
copyButton.addEventListener("click", async () => {
3179
await navigator.clipboard.writeText(urlInput.value);
@@ -35,6 +83,16 @@ function noSearchDefaultPageRender() {
3583
copyIcon.src = "/clipboard.svg";
3684
}, 2000);
3785
});
86+
87+
customizeButton.addEventListener("click", () => {
88+
settingsPanel.classList.toggle("open");
89+
customizeButton.classList.toggle("active");
90+
});
91+
92+
defaultSearchSelect.addEventListener("change", (e) => {
93+
const target = e.target as HTMLSelectElement;
94+
localStorage.setItem("default-search-engine", target.value);
95+
});
3896
}
3997

4098
const LS_DEFAULT_BANG = localStorage.getItem("default-bang") ?? "brave";
@@ -49,27 +107,38 @@ function getBangredirectUrl() {
49107
}
50108

51109
const match = query.match(/!(\S+)/i);
52-
53110
const bangCandidate = match?.[1]?.toLowerCase();
54-
const selectedBang = bangs.find((b) => b.t === bangCandidate) ?? defaultBang;
55111

56-
// Remove the first bang from the query
57-
const cleanQuery = query.replace(/!\S+\s*/i, "").trim();
112+
if (bangCandidate) {
113+
// Bang detected - use existing logic
114+
const selectedBang = bangs.find((b) => b.t === bangCandidate) ?? defaultBang;
115+
116+
// Remove the first bang from the query
117+
const cleanQuery = query.replace(/!\S+\s*/i, "").trim();
118+
119+
// If the query is just `!gh`, use `github.com` instead of `github.com/search?q=`
120+
if (cleanQuery === "")
121+
return selectedBang ? `https://${selectedBang.d}` : null;
122+
123+
// Format of the url is:
124+
// https://www.google.com/search?q={{{s}}}
125+
const searchUrl = selectedBang?.u.replace(
126+
"{{{s}}}",
127+
// Replace %2F with / to fix formats like "!ghr+t3dotgg/unduck"
128+
encodeURIComponent(cleanQuery).replace(/%2F/g, "/"),
129+
);
130+
if (!searchUrl) return null;
58131

59-
// If the query is just `!gh`, use `github.com` instead of `github.com/search?q=`
60-
if (cleanQuery === "")
61-
return selectedBang ? `https://${selectedBang.d}` : null;
132+
return searchUrl;
133+
} else {
134+
// No bang detected - use default search engine
135+
const defaultSearchEngine = localStorage.getItem("default-search-engine") || "brave";
136+
const engine = DEFAULT_SEARCH_ENGINES.find(e => e.value === defaultSearchEngine);
62137

63-
// Format of the url is:
64-
// https://www.google.com/search?q={{{s}}}
65-
const searchUrl = selectedBang?.u.replace(
66-
"{{{s}}}",
67-
// Replace %2F with / to fix formats like "!ghr+t3dotgg/unduck"
68-
encodeURIComponent(cleanQuery).replace(/%2F/g, "/"),
69-
);
70-
if (!searchUrl) return null;
138+
if (!engine) return null;
71139

72-
return searchUrl;
140+
return engine.url.replace("{{{s}}}", encodeURIComponent(query));
141+
}
73142
}
74143

75144
function doRedirect() {
@@ -78,4 +147,4 @@ function doRedirect() {
78147
window.location.replace(searchUrl);
79148
}
80149

81-
doRedirect();
150+
doRedirect();

0 commit comments

Comments
 (0)