termsearch 0.3.9 → 0.3.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/frontend/dist/app.js +86 -24
- package/frontend/dist/style.css +65 -8
- package/package.json +1 -1
- package/src/autostart/manager.js +4 -2
package/frontend/dist/app.js
CHANGED
|
@@ -286,6 +286,27 @@ const ENGINE_PRESETS = [
|
|
|
286
286
|
{ id: 'github', label: 'GitHub Focus', engines: ['github-api', 'github', 'duckduckgo', 'wikipedia'] },
|
|
287
287
|
];
|
|
288
288
|
|
|
289
|
+
// ─── Engine availability (requires config) ────────────────────────────────
|
|
290
|
+
const SEARXNG_ROUTED = new Set([
|
|
291
|
+
'bing', 'google', 'yahoo', 'startpage', 'qwant',
|
|
292
|
+
'youtube', 'reddit', 'hackernews', 'sepiasearch',
|
|
293
|
+
'wikidata', 'crossref', 'openalex', 'openlibrary',
|
|
294
|
+
'mastodon users', 'mastodon hashtags', 'tootfinder',
|
|
295
|
+
'lemmy communities', 'lemmy posts',
|
|
296
|
+
'piratebay', '1337x', 'nyaa',
|
|
297
|
+
]);
|
|
298
|
+
|
|
299
|
+
function isEngineAvailable(engine) {
|
|
300
|
+
const cfg = state.config || {};
|
|
301
|
+
if (engine === 'brave') return Boolean(cfg.brave?.enabled && cfg.brave?.api_key);
|
|
302
|
+
if (engine === 'mojeek') return Boolean(cfg.mojeek?.enabled && cfg.mojeek?.api_key);
|
|
303
|
+
if (engine === 'yandex') return cfg.yandex?.enabled !== false;
|
|
304
|
+
if (engine === 'ahmia') return cfg.ahmia?.enabled !== false;
|
|
305
|
+
if (engine === 'marginalia') return cfg.marginalia?.enabled !== false;
|
|
306
|
+
if (SEARXNG_ROUTED.has(engine)) return Boolean(cfg.searxng?.enabled && cfg.searxng?.url);
|
|
307
|
+
return true; // duckduckgo, wikipedia, github, github-api — sempre disponibili
|
|
308
|
+
}
|
|
309
|
+
|
|
289
310
|
function detectPresetFromBase(base) {
|
|
290
311
|
const raw = String(base || '').toLowerCase();
|
|
291
312
|
if (!raw) return 'custom';
|
|
@@ -316,8 +337,11 @@ function LangPicker() {
|
|
|
316
337
|
|
|
317
338
|
function EnginePicker(opts = {}) {
|
|
318
339
|
const compact = Boolean(opts.compact);
|
|
340
|
+
// [] = ALL (no filter sent to backend)
|
|
341
|
+
const isAll = state.selectedEngines.length === 0;
|
|
342
|
+
const selectedCount = isAll ? 0 : state.selectedEngines.length;
|
|
343
|
+
|
|
319
344
|
const details = el('details', { className: `engine-picker${compact ? ' engine-picker-compact' : ''}` });
|
|
320
|
-
const selectedCount = state.selectedEngines.length;
|
|
321
345
|
const summary = compact
|
|
322
346
|
? el('summary', { className: 'engine-picker-summary engine-picker-summary-icon', title: 'Search engines' },
|
|
323
347
|
iconEl('filter', 'engine-filter-icon'),
|
|
@@ -329,45 +353,64 @@ function EnginePicker(opts = {}) {
|
|
|
329
353
|
);
|
|
330
354
|
|
|
331
355
|
const body = el('div', { className: 'engine-picker-body' });
|
|
356
|
+
|
|
357
|
+
// Preset buttons
|
|
332
358
|
const presetRow = el('div', { className: 'engine-preset-row' });
|
|
333
359
|
ENGINE_PRESETS.forEach((preset) => {
|
|
360
|
+
const isActive = preset.id === 'all' ? isAll
|
|
361
|
+
: preset.engines.length > 0 && preset.engines.every(e => state.selectedEngines.includes(e)) && state.selectedEngines.length === preset.engines.length;
|
|
334
362
|
presetRow.append(el('button', {
|
|
335
|
-
className: `btn ${preset.id === 'balanced' ? 'btn-primary' : ''}`,
|
|
363
|
+
className: `btn ${isActive || preset.id === 'balanced' ? 'btn-primary' : ''}`,
|
|
336
364
|
type: 'button',
|
|
337
365
|
onClick: () => {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
366
|
+
// 'all' preset → clear filter (backend uses all configured providers)
|
|
367
|
+
setSelectedEngines(preset.id === 'all' ? [] : preset.engines);
|
|
368
|
+
// visually check/uncheck all available chips
|
|
369
|
+
[...details.querySelectorAll('.engine-chip input:not(:disabled)')].forEach((input) => {
|
|
370
|
+
input.checked = preset.id === 'all' || preset.engines.includes(input.closest('.engine-chip')?.querySelector('span')?.textContent?.trim().toLowerCase() || '');
|
|
371
|
+
});
|
|
342
372
|
},
|
|
343
373
|
}, preset.label));
|
|
344
374
|
});
|
|
345
375
|
body.append(presetRow);
|
|
346
376
|
|
|
377
|
+
// Engine chips per group
|
|
347
378
|
ENGINE_GROUPS.forEach((group) => {
|
|
348
379
|
const card = el('div', { className: 'engine-group' });
|
|
349
380
|
card.append(el('div', { className: 'engine-group-title' }, group.label));
|
|
350
381
|
const list = el('div', { className: 'engine-chip-wrap' });
|
|
351
382
|
group.items.forEach((engine) => {
|
|
352
|
-
const
|
|
383
|
+
const available = isEngineAvailable(engine);
|
|
384
|
+
// checked = explicitly selected, OR in "all" mode ([] = all available)
|
|
385
|
+
const checked = available && (isAll || state.selectedEngines.includes(engine));
|
|
353
386
|
const id = `engine-${engine.replace(/[^a-z0-9]+/g, '-')}-${Math.random().toString(36).slice(2, 6)}`;
|
|
354
|
-
const
|
|
355
|
-
const
|
|
387
|
+
const inputAttrs = { id, type: 'checkbox', ...(checked ? { checked: '' } : {}), ...(available ? {} : { disabled: '' }) };
|
|
388
|
+
const input = el('input', inputAttrs);
|
|
389
|
+
const chipClass = `engine-chip${available ? '' : ' engine-chip-unavailable'}`;
|
|
390
|
+
const title = available ? engine : `${engine} — not configured (Settings)`;
|
|
391
|
+
const label = el('label', { className: chipClass, for: id, title },
|
|
392
|
+
input,
|
|
393
|
+
el('span', {}, engine),
|
|
394
|
+
);
|
|
356
395
|
list.append(label);
|
|
357
396
|
});
|
|
358
397
|
card.append(list);
|
|
359
398
|
body.append(card);
|
|
360
399
|
});
|
|
361
400
|
|
|
401
|
+
// Apply / Reset
|
|
362
402
|
body.append(el('div', { className: 'engine-actions' },
|
|
363
403
|
el('button', {
|
|
364
404
|
className: 'btn btn-primary',
|
|
365
405
|
type: 'button',
|
|
366
406
|
onClick: () => {
|
|
367
|
-
const
|
|
368
|
-
.map((node) => node.
|
|
407
|
+
const checked = [...details.querySelectorAll('.engine-chip input:not(:disabled):checked')]
|
|
408
|
+
.map((node) => node.closest('.engine-chip')?.querySelector('span')?.textContent?.trim().toLowerCase())
|
|
369
409
|
.filter(Boolean);
|
|
370
|
-
|
|
410
|
+
const availableAll = ENGINE_GROUPS.flatMap(g => g.items).filter(isEngineAvailable);
|
|
411
|
+
// If all available engines are checked, send [] (no filter)
|
|
412
|
+
const allChecked = availableAll.every(e => checked.includes(e));
|
|
413
|
+
setSelectedEngines(allChecked ? [] : checked);
|
|
371
414
|
details.open = false;
|
|
372
415
|
if (state.query) doSearch(state.query, state.category);
|
|
373
416
|
else renderApp();
|
|
@@ -396,7 +439,6 @@ function EnginePicker(opts = {}) {
|
|
|
396
439
|
document.removeEventListener('click', onClickOutside, true);
|
|
397
440
|
}
|
|
398
441
|
};
|
|
399
|
-
// Defer to avoid catching the same click that opened it
|
|
400
442
|
requestAnimationFrame(() => document.addEventListener('click', onClickOutside, true));
|
|
401
443
|
});
|
|
402
444
|
|
|
@@ -1165,11 +1207,34 @@ function addToBrowser() {
|
|
|
1165
1207
|
el('div', { className: 'add-browser-label' }, 'Search URL (paste in browser settings):'),
|
|
1166
1208
|
urlBox,
|
|
1167
1209
|
);
|
|
1168
|
-
document.querySelector('.
|
|
1210
|
+
document.querySelector('.footer')?.after(hint);
|
|
1169
1211
|
setTimeout(() => hint.remove(), 20000);
|
|
1170
1212
|
}
|
|
1171
1213
|
|
|
1172
1214
|
function renderHome(app) {
|
|
1215
|
+
// Top-right controls
|
|
1216
|
+
const topBar = el('div', { className: 'home-topbar' },
|
|
1217
|
+
LangPicker(),
|
|
1218
|
+
el('button', { className: 'btn-icon', title: 'Settings', onClick: () => navigate('#/settings') }, iconEl('settings')),
|
|
1219
|
+
el('button', { className: 'btn-icon', title: 'Toggle theme', onClick: toggleTheme }, iconEl('theme')),
|
|
1220
|
+
);
|
|
1221
|
+
|
|
1222
|
+
// History below search (if present)
|
|
1223
|
+
const historyEl = (state.historyEnabled && state.searchHistory.length > 0)
|
|
1224
|
+
? el('div', { className: 'home-history' },
|
|
1225
|
+
...state.searchHistory.slice(0, 8).map((q) =>
|
|
1226
|
+
el('button', {
|
|
1227
|
+
className: 'home-history-item',
|
|
1228
|
+
type: 'button',
|
|
1229
|
+
onClick: () => { state.query = q; state.category = 'web'; doSearch(q, 'web'); },
|
|
1230
|
+
},
|
|
1231
|
+
el('span', { className: 'home-history-icon', html: ICONS.search }),
|
|
1232
|
+
el('span', {}, q),
|
|
1233
|
+
),
|
|
1234
|
+
),
|
|
1235
|
+
)
|
|
1236
|
+
: null;
|
|
1237
|
+
|
|
1173
1238
|
const home = el('div', { className: 'home' },
|
|
1174
1239
|
el('div', { className: 'home-logo' }, 'Term', el('strong', {}, 'Search')),
|
|
1175
1240
|
el('div', { className: 'home-tagline' },
|
|
@@ -1177,23 +1242,20 @@ function renderHome(app) {
|
|
|
1177
1242
|
el('span', { className: 'tagline-mobile' }, 'Private local search'),
|
|
1178
1243
|
),
|
|
1179
1244
|
el('div', { className: 'home-search' }, SearchForm('', (q) => { state.query = q; state.category = 'web'; doSearch(q, 'web'); })),
|
|
1180
|
-
|
|
1181
|
-
LangPicker(),
|
|
1182
|
-
el('button', { className: 'btn', onClick: () => navigate('#/settings') }, iconEl('settings'), ' Settings'),
|
|
1183
|
-
el('button', { className: 'btn', onClick: toggleTheme }, iconEl('theme'), ' Theme'),
|
|
1184
|
-
el('button', { className: 'btn', onClick: addToBrowser, title: 'Add as browser search engine' }, iconEl('search'), ' Add to browser'),
|
|
1185
|
-
el('a', { className: 'btn', href: 'https://github.com/DioNanos/termsearch', target: '_blank', rel: 'noopener noreferrer' }, iconEl('github'), ' GitHub'),
|
|
1186
|
-
),
|
|
1245
|
+
historyEl,
|
|
1187
1246
|
);
|
|
1188
1247
|
|
|
1189
1248
|
const footer = el('div', { className: 'footer' },
|
|
1190
1249
|
el('span', { className: 'footer-link' }, '© 2026 DioNanos'),
|
|
1191
|
-
el('a', { className: 'footer-link', href: 'https://github.com/DioNanos/termsearch', target: '_blank', rel: 'noopener' },
|
|
1192
|
-
iconEl('github'), 'GitHub',
|
|
1250
|
+
el('a', { className: 'footer-link', href: 'https://github.com/DioNanos/termsearch', target: '_blank', rel: 'noopener noreferrer' },
|
|
1251
|
+
iconEl('github'), ' GitHub',
|
|
1252
|
+
),
|
|
1253
|
+
el('button', { className: 'footer-link footer-add-btn', onClick: addToBrowser, title: 'Add as default search engine' },
|
|
1254
|
+
iconEl('search'), ' Add to browser',
|
|
1193
1255
|
),
|
|
1194
1256
|
);
|
|
1195
1257
|
|
|
1196
|
-
app.append(home, footer);
|
|
1258
|
+
app.append(topBar, home, footer);
|
|
1197
1259
|
}
|
|
1198
1260
|
|
|
1199
1261
|
// ─── Settings ─────────────────────────────────────────────────────────────
|
package/frontend/dist/style.css
CHANGED
|
@@ -97,7 +97,7 @@ a:hover { color: var(--link-h); }
|
|
|
97
97
|
.pulse { animation: pulse 1.5s ease-in-out infinite; }
|
|
98
98
|
|
|
99
99
|
/* ─── Layout ──────────────────────────────────────────────────────────────── */
|
|
100
|
-
#app { min-height: 100vh; display: flex; flex-direction: column; }
|
|
100
|
+
#app { min-height: 100vh; display: flex; flex-direction: column; position: relative; }
|
|
101
101
|
|
|
102
102
|
/* ─── Header ──────────────────────────────────────────────────────────────── */
|
|
103
103
|
.header {
|
|
@@ -281,6 +281,17 @@ a:hover { color: var(--link-h); }
|
|
|
281
281
|
.engine-chip input {
|
|
282
282
|
margin: 0;
|
|
283
283
|
}
|
|
284
|
+
.engine-chip-unavailable {
|
|
285
|
+
opacity: 0.35;
|
|
286
|
+
cursor: not-allowed;
|
|
287
|
+
border-style: dashed;
|
|
288
|
+
pointer-events: none;
|
|
289
|
+
}
|
|
290
|
+
.engine-chip-unavailable span {
|
|
291
|
+
font-weight: 700;
|
|
292
|
+
text-decoration: line-through;
|
|
293
|
+
text-decoration-color: rgba(255,255,255,0.25);
|
|
294
|
+
}
|
|
284
295
|
.engine-actions {
|
|
285
296
|
display: flex;
|
|
286
297
|
gap: 8px;
|
|
@@ -289,13 +300,23 @@ a:hover { color: var(--link-h); }
|
|
|
289
300
|
}
|
|
290
301
|
|
|
291
302
|
/* ─── Homepage ────────────────────────────────────────────────────────────── */
|
|
303
|
+
.home-topbar {
|
|
304
|
+
position: absolute;
|
|
305
|
+
top: 12px;
|
|
306
|
+
right: 16px;
|
|
307
|
+
display: flex;
|
|
308
|
+
align-items: center;
|
|
309
|
+
gap: 4px;
|
|
310
|
+
z-index: 10;
|
|
311
|
+
}
|
|
312
|
+
|
|
292
313
|
.home {
|
|
293
314
|
flex: 1;
|
|
294
315
|
display: flex;
|
|
295
316
|
flex-direction: column;
|
|
296
317
|
align-items: center;
|
|
297
318
|
justify-content: center;
|
|
298
|
-
padding: 48px 20px
|
|
319
|
+
padding: 48px 20px 40px;
|
|
299
320
|
gap: 0;
|
|
300
321
|
}
|
|
301
322
|
|
|
@@ -326,14 +347,44 @@ a:hover { color: var(--link-h); }
|
|
|
326
347
|
.tagline-mobile { display: none; }
|
|
327
348
|
.tagline-desktop { display: inline; }
|
|
328
349
|
|
|
329
|
-
.home-search { width: 100%; max-width: 560px; margin-bottom:
|
|
350
|
+
.home-search { width: 100%; max-width: 560px; margin-bottom: 0; }
|
|
330
351
|
|
|
331
|
-
|
|
352
|
+
/* History below search */
|
|
353
|
+
.home-history {
|
|
354
|
+
width: 100%;
|
|
355
|
+
max-width: 560px;
|
|
356
|
+
margin-top: 6px;
|
|
357
|
+
display: flex;
|
|
358
|
+
flex-direction: column;
|
|
359
|
+
gap: 1px;
|
|
360
|
+
border: 1px solid var(--border);
|
|
361
|
+
border-radius: var(--radius);
|
|
362
|
+
overflow: hidden;
|
|
363
|
+
background: var(--bg2);
|
|
364
|
+
}
|
|
365
|
+
.home-history-item {
|
|
332
366
|
display: flex;
|
|
333
367
|
align-items: center;
|
|
334
|
-
gap:
|
|
335
|
-
|
|
336
|
-
|
|
368
|
+
gap: 10px;
|
|
369
|
+
padding: 8px 14px;
|
|
370
|
+
font-size: 13px;
|
|
371
|
+
color: var(--text2);
|
|
372
|
+
text-align: left;
|
|
373
|
+
border-bottom: 1px solid var(--border2);
|
|
374
|
+
cursor: pointer;
|
|
375
|
+
transition: background 0.1s;
|
|
376
|
+
background: transparent;
|
|
377
|
+
border-left: none;
|
|
378
|
+
border-right: none;
|
|
379
|
+
border-top: none;
|
|
380
|
+
}
|
|
381
|
+
.home-history-item:last-child { border-bottom: none; }
|
|
382
|
+
.home-history-item:hover { background: var(--bg3); color: var(--text); }
|
|
383
|
+
.home-history-icon {
|
|
384
|
+
opacity: 0.35;
|
|
385
|
+
flex-shrink: 0;
|
|
386
|
+
display: inline-flex;
|
|
387
|
+
align-items: center;
|
|
337
388
|
}
|
|
338
389
|
|
|
339
390
|
/* ─── Search Bar ──────────────────────────────────────────────────────────── */
|
|
@@ -1017,12 +1068,18 @@ a:hover { color: var(--link-h); }
|
|
|
1017
1068
|
/* ─── Footer ──────────────────────────────────────────────────────────────── */
|
|
1018
1069
|
.footer {
|
|
1019
1070
|
border-top: 1px solid var(--border2);
|
|
1020
|
-
padding:
|
|
1071
|
+
padding: 10px 16px;
|
|
1021
1072
|
display: flex;
|
|
1022
1073
|
align-items: center;
|
|
1023
1074
|
justify-content: center;
|
|
1024
1075
|
gap: 16px;
|
|
1025
1076
|
}
|
|
1077
|
+
.footer-add-btn {
|
|
1078
|
+
background: none;
|
|
1079
|
+
border: none;
|
|
1080
|
+
cursor: pointer;
|
|
1081
|
+
padding: 0;
|
|
1082
|
+
}
|
|
1026
1083
|
.footer-link {
|
|
1027
1084
|
color: var(--text3);
|
|
1028
1085
|
font-size: 10px;
|
package/package.json
CHANGED
package/src/autostart/manager.js
CHANGED
|
@@ -50,7 +50,7 @@ function termuxStatus() {
|
|
|
50
50
|
function termuxEnable() {
|
|
51
51
|
fs.mkdirSync(TERMUX_BOOT_DIR, { recursive: true });
|
|
52
52
|
const bin = findBin();
|
|
53
|
-
const sh = `#!/data/data/com.termux/files/usr/bin/sh\n# TermSearch autostart\n${bin} &\n`;
|
|
53
|
+
const sh = `#!/data/data/com.termux/files/usr/bin/sh\n# TermSearch autostart\n${bin} start --fg &\n`;
|
|
54
54
|
fs.writeFileSync(TERMUX_BOOT_FILE, sh, { mode: 0o755 });
|
|
55
55
|
}
|
|
56
56
|
|
|
@@ -103,7 +103,7 @@ function linuxEnable() {
|
|
|
103
103
|
'',
|
|
104
104
|
'[Service]',
|
|
105
105
|
'Type=simple',
|
|
106
|
-
`ExecStart=${bin}`,
|
|
106
|
+
`ExecStart=${bin} start --fg`,
|
|
107
107
|
'Restart=on-failure',
|
|
108
108
|
'RestartSec=5',
|
|
109
109
|
'',
|
|
@@ -156,6 +156,8 @@ function macosEnable() {
|
|
|
156
156
|
<key>ProgramArguments</key>
|
|
157
157
|
<array>
|
|
158
158
|
<string>${bin}</string>
|
|
159
|
+
<string>start</string>
|
|
160
|
+
<string>--fg</string>
|
|
159
161
|
</array>
|
|
160
162
|
<key>RunAtLoad</key>
|
|
161
163
|
<true/>
|