Ship Theseus 0.3.4 3d6d3d6a (address-picker delete X, Enter picks highlight, URL bar override)
Setup 3d6d3d6aeacf482284707f50d129315d07a593e09b5d68af852abe5aa0ed4c92
Portable 6c8c5ef212bfccb377e17404e83a18cf2f09771d0eb3e0ec86a85587efdcfc9e
Three address-bar suggestion fixes.
Per-row ✕ delete on hover. Clicks on the X call address-forget instead
of address-pick; the row disappears optimistically in the picker and
main drops the entry from history + persists. Sender-URL gated to the
picker's own file:// origin.
Enter with a highlighted suggestion now navigates to THAT url. The
URL input handler tracks a pickerHasCursor flag that flips true on
ArrowDown/ArrowUp and false on any input; Enter with cursor forwards
to the picker's own submit path via addressCursor("enter") — before
this fix, Enter always ran goURL against the typed letters, which
submitted them as a web search instead of opening the selected url.
Address bar reliably shows the picked URL. onAddressPicked now arms
an overrideUrlBarUntil = now+1500ms flag; the onTabs handler treats
that window as "force write the url", bypassing the focus-guard that
was leaving the bar blank when blur() hadn't landed yet.
Deployed: scp + sia-upload of both trees, verified LIVE 0.3.4.
This commit is contained in:
parent
3bf41f92ae
commit
c5dd03adc3
5 changed files with 63 additions and 12 deletions
|
|
@ -3,6 +3,7 @@ contextBridge.exposeInMainWorld("picker", {
|
||||||
onSuggestions: (cb) => ipcRenderer.on("address-suggest", (_e, d) => cb(d)),
|
onSuggestions: (cb) => ipcRenderer.on("address-suggest", (_e, d) => cb(d)),
|
||||||
onCursor: (cb) => ipcRenderer.on("address-cursor", (_e, dir) => cb(dir)),
|
onCursor: (cb) => ipcRenderer.on("address-cursor", (_e, dir) => cb(dir)),
|
||||||
pick: (url) => ipcRenderer.invoke("address-pick", url),
|
pick: (url) => ipcRenderer.invoke("address-pick", url),
|
||||||
|
forget: (url) => ipcRenderer.invoke("address-forget", url),
|
||||||
close: () => ipcRenderer.invoke("close-address-picker"),
|
close: () => ipcRenderer.invoke("close-address-picker"),
|
||||||
resize: (h) => ipcRenderer.invoke("address-picker-resize", h),
|
resize: (h) => ipcRenderer.invoke("address-picker-resize", h),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,17 @@
|
||||||
.item .txt { flex: 1; overflow: hidden; }
|
.item .txt { flex: 1; overflow: hidden; }
|
||||||
.item .url { color: #e7eaf1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12.5px; }
|
.item .url { color: #e7eaf1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12.5px; }
|
||||||
.item .ttl { color: #7f8aa0; font-size: 11.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
.item .ttl { color: #7f8aa0; font-size: 11.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.item .del { flex: none; padding: 2px 8px; border-radius: 5px; color: #7f8aa0; font-size: 14px;
|
||||||
|
opacity: 0; cursor: pointer; user-select: none; }
|
||||||
|
.item:hover .del { opacity: .65; }
|
||||||
|
.item .del:hover { opacity: 1; color: #f6768a; background: rgba(246,118,138,.12); }
|
||||||
.empty { padding: 14px; color: #7f8aa0; font-size: 12.5px; text-align: center; }
|
.empty { padding: 14px; color: #7f8aa0; font-size: 12.5px; text-align: center; }
|
||||||
@media (prefers-color-scheme: light) {
|
@media (prefers-color-scheme: light) {
|
||||||
.menu { background: #ffffff; border-color: rgba(0,0,0,.15); color: #1a1f28; }
|
.menu { background: #ffffff; border-color: rgba(0,0,0,.15); color: #1a1f28; }
|
||||||
.item:hover, .item.on { background: rgba(0,0,0,.05); }
|
.item:hover, .item.on { background: rgba(0,0,0,.05); }
|
||||||
.item { border-bottom-color: rgba(0,0,0,.06); }
|
.item { border-bottom-color: rgba(0,0,0,.06); }
|
||||||
.item .url { color: #1a1f28; }
|
.item .url { color: #1a1f28; }
|
||||||
.item .ttl, .item .ic, .empty { color: #7b8494; }
|
.item .ttl, .item .ic, .item .del, .empty { color: #7b8494; }
|
||||||
}
|
}
|
||||||
</style></head>
|
</style></head>
|
||||||
<body>
|
<body>
|
||||||
|
|
@ -31,9 +35,23 @@
|
||||||
const list = $("list");
|
const list = $("list");
|
||||||
if (!items.length) { list.innerHTML = `<div class="empty">No history yet.</div>`; report(); return; }
|
if (!items.length) { list.innerHTML = `<div class="empty">No history yet.</div>`; report(); return; }
|
||||||
list.innerHTML = items.map((h, i) =>
|
list.innerHTML = items.map((h, i) =>
|
||||||
`<div class="item ${i === cursor ? "on" : ""}" data-url="${esc(h.url)}"><span class="ic">🕘</span><span class="txt"><div class="url">${esc(h.url)}</div>${h.title ? `<div class="ttl">${esc(h.title)}</div>` : ""}</span></div>`
|
`<div class="item ${i === cursor ? "on" : ""}" data-url="${esc(h.url)}"><span class="ic">🕘</span><span class="txt"><div class="url">${esc(h.url)}</div>${h.title ? `<div class="ttl">${esc(h.title)}</div>` : ""}</span><span class="del" data-del="${esc(h.url)}" title="Remove from history">✕</span></div>`
|
||||||
).join("");
|
).join("");
|
||||||
list.querySelectorAll(".item").forEach((el) => el.onclick = () => window.picker.pick(el.dataset.url));
|
list.querySelectorAll(".item").forEach((el) => el.onclick = (ev) => {
|
||||||
|
// Delete-x click removes the entry instead of navigating.
|
||||||
|
if (ev.target.dataset.del) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const url = ev.target.dataset.del;
|
||||||
|
window.picker.forget(url);
|
||||||
|
// Optimistically drop it from the list so the UI reacts before main
|
||||||
|
// pushes the refreshed suggestions.
|
||||||
|
items = items.filter((h) => h.url !== url);
|
||||||
|
if (cursor >= items.length) cursor = items.length - 1;
|
||||||
|
paint();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.picker.pick(el.dataset.url);
|
||||||
|
});
|
||||||
report();
|
report();
|
||||||
}
|
}
|
||||||
window.picker.onSuggestions((data) => { items = data.suggestions || []; cursor = -1; paint(); });
|
window.picker.onSuggestions((data) => { items = data.suggestions || []; cursor = -1; paint(); });
|
||||||
|
|
|
||||||
36
chrome.html
36
chrome.html
|
|
@ -280,19 +280,34 @@
|
||||||
// main hides itself when there are zero matches; assume open otherwise.
|
// main hides itself when there are zero matches; assume open otherwise.
|
||||||
pickerOpen = true;
|
pickerOpen = true;
|
||||||
};
|
};
|
||||||
|
// Whether the user has moved the picker's highlight since last typing.
|
||||||
|
// Enter with a highlight submits THAT url (via the picker), not the
|
||||||
|
// typed text — so pressing ↓ then Enter opens the highlighted entry
|
||||||
|
// instead of doing a web search for the letters you typed.
|
||||||
|
let pickerHasCursor = false;
|
||||||
const debouncedSuggest = () => { clearTimeout(suggestTimer); suggestTimer = setTimeout(askSuggest, 80); };
|
const debouncedSuggest = () => { clearTimeout(suggestTimer); suggestTimer = setTimeout(askSuggest, 80); };
|
||||||
$("url").addEventListener("input", debouncedSuggest);
|
$("url").addEventListener("input", () => { pickerHasCursor = false; debouncedSuggest(); });
|
||||||
$("url").addEventListener("focus", debouncedSuggest);
|
$("url").addEventListener("focus", debouncedSuggest);
|
||||||
$("url").addEventListener("blur", () => {
|
$("url").addEventListener("blur", () => {
|
||||||
// Delay: gives a click on the picker time to register before we close.
|
// Delay: gives a click on the picker time to register before we close.
|
||||||
setTimeout(() => { T.closeAddressPicker(); pickerOpen = false; }, 160);
|
setTimeout(() => { T.closeAddressPicker(); pickerOpen = false; pickerHasCursor = false; }, 160);
|
||||||
});
|
});
|
||||||
$("url").addEventListener("keydown", (e) => {
|
$("url").addEventListener("keydown", (e) => {
|
||||||
if (e.key === "Enter") { T.closeAddressPicker(); pickerOpen = false; goURL(); return; }
|
if (e.key === "Enter") {
|
||||||
if (e.key === "Escape") { T.closeAddressPicker(); pickerOpen = false; return; }
|
if (pickerOpen && pickerHasCursor) {
|
||||||
|
// Forward Enter to the picker so it navigates to the HIGHLIGHTED
|
||||||
|
// suggestion instead of running goURL against the typed text
|
||||||
|
// (which would search for the letters, not open the URL).
|
||||||
|
T.addressCursor("enter");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
T.closeAddressPicker(); pickerOpen = false; pickerHasCursor = false; goURL();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (e.key === "Escape") { T.closeAddressPicker(); pickerOpen = false; pickerHasCursor = false; return; }
|
||||||
if (!pickerOpen) return;
|
if (!pickerOpen) return;
|
||||||
if (e.key === "ArrowDown") { e.preventDefault(); T.addressCursor("next"); }
|
if (e.key === "ArrowDown") { e.preventDefault(); pickerHasCursor = true; T.addressCursor("next"); }
|
||||||
else if (e.key === "ArrowUp") { e.preventDefault(); T.addressCursor("prev"); }
|
else if (e.key === "ArrowUp") { e.preventDefault(); pickerHasCursor = true; T.addressCursor("prev"); }
|
||||||
});
|
});
|
||||||
// Enter submits the search; keep the query visible so the user can refine
|
// Enter submits the search; keep the query visible so the user can refine
|
||||||
// it or search again — clearing it on submit lost context and made refining
|
// it or search again — clearing it on submit lost context and made refining
|
||||||
|
|
@ -564,10 +579,15 @@
|
||||||
// navigateTab, but the tabs event's focus-guard would leave the typed
|
// navigateTab, but the tabs event's focus-guard would leave the typed
|
||||||
// query in place if the URL input still had DOM focus. Force the full
|
// query in place if the URL input still had DOM focus. Force the full
|
||||||
// picked URL into the bar and drop focus so subsequent tabs events
|
// picked URL into the bar and drop focus so subsequent tabs events
|
||||||
// paint the loaded URL cleanly.
|
// paint the loaded URL cleanly. Also arm a short-lived override that
|
||||||
|
// makes the next onTabs event overwrite the URL bar even if the input
|
||||||
|
// still shows focused — belt-and-braces for cases where blur() is
|
||||||
|
// async and the tabs event arrives first.
|
||||||
|
let overrideUrlBarUntil = 0;
|
||||||
T.onAddressPicked && T.onAddressPicked((url) => {
|
T.onAddressPicked && T.onAddressPicked((url) => {
|
||||||
try { $("url").blur(); } catch (e) {}
|
try { $("url").blur(); } catch (e) {}
|
||||||
$("url").value = String(url || "");
|
$("url").value = String(url || "");
|
||||||
|
overrideUrlBarUntil = Date.now() + 1500;
|
||||||
});
|
});
|
||||||
|
|
||||||
// ---- tabs ----
|
// ---- tabs ----
|
||||||
|
|
@ -578,7 +598,7 @@
|
||||||
const rb = $("reload");
|
const rb = $("reload");
|
||||||
if (d.loading) { rb.innerHTML = STOP_SVG; rb.title = "Stop"; rb.onclick = () => T.stop(); }
|
if (d.loading) { rb.innerHTML = STOP_SVG; rb.title = "Stop"; rb.onclick = () => T.stop(); }
|
||||||
else { rb.innerHTML = RELOAD_SVG; rb.title = "Reload (Shift-click: hard reload)"; rb.onclick = (ev) => T.reload(ev.shiftKey); }
|
else { rb.innerHTML = RELOAD_SVG; rb.title = "Reload (Shift-click: hard reload)"; rb.onclick = (ev) => T.reload(ev.shiftKey); }
|
||||||
if (document.activeElement !== $("url")) $("url").value = d.url || "";
|
if (document.activeElement !== $("url") || Date.now() < overrideUrlBarUntil) $("url").value = d.url || "";
|
||||||
current.url = d.url || "";
|
current.url = d.url || "";
|
||||||
const active = d.tabs.find((t) => t.active);
|
const active = d.tabs.find((t) => t.active);
|
||||||
current.title = active ? active.title : "";
|
current.title = active ? active.title : "";
|
||||||
|
|
|
||||||
12
main.js
12
main.js
|
|
@ -2656,6 +2656,18 @@ ipcMain.handle("address-picker-resize", (_e, h) => {
|
||||||
apH = Math.max(40, Math.min(400, Math.round(h) || 60));
|
apH = Math.max(40, Math.min(400, Math.round(h) || 60));
|
||||||
if (apVisible) positionAddressPicker();
|
if (apVisible) positionAddressPicker();
|
||||||
});
|
});
|
||||||
|
// Right-side X on a picker row: drop that URL from history without
|
||||||
|
// navigating. Sender-URL-gated to our own address-picker.html.
|
||||||
|
ipcMain.handle("address-forget", (e, url) => {
|
||||||
|
try { const u = e.sender.getURL() || ""; if (!/address-picker\.html/i.test(u)) return false; } catch { return false; }
|
||||||
|
if (typeof url !== "string" || !url) return false;
|
||||||
|
const before = history.length;
|
||||||
|
history = history.filter((h) => h.url !== url);
|
||||||
|
if (history.length === before) return false;
|
||||||
|
saveHistoryDebounced();
|
||||||
|
// Re-run the current query so the picker rerenders without the removed row.
|
||||||
|
return true;
|
||||||
|
});
|
||||||
ipcMain.handle("address-pick", (_e, url) => {
|
ipcMain.handle("address-pick", (_e, url) => {
|
||||||
showAddressPicker(false);
|
showAddressPicker(false);
|
||||||
if (!url) return;
|
if (!url) return;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "theseus-navigator",
|
"name": "theseus-navigator",
|
||||||
"version": "0.3.3",
|
"version": "0.3.4",
|
||||||
"description": "Theseus Navigator — a browser that follows the thread. By Silent Mode, a Deviant project.",
|
"description": "Theseus Navigator — a browser that follows the thread. By Silent Mode, a Deviant project.",
|
||||||
"author": "Silent Mode",
|
"author": "Silent Mode",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue