diff --git a/chrome.html b/chrome.html
index 80179fd7..b1a91c80 100644
--- a/chrome.html
+++ b/chrome.html
@@ -401,6 +401,9 @@
color: var(--ink); font: inherit; font-size: 13px; outline: none; }
.promptbox input:focus { border-color: rgb(from var(--acid) r g b / .55); }
.promptbox .pact { display: flex; gap: 6px; justify-content: flex-end; margin-top: 10px; }
+ .promptbox .pfield { display: flex; flex-direction: column; gap: 4px; margin-top: 8px; }
+ .promptbox .pfield label { font-size: 11.5px; color: var(--dim); }
+ .promptbox .perr { color: #f6768a; font-size: 12px; margin-top: 8px; }
.promptbox .pbtn { padding: 6px 12px; border-radius: 6px; border: 1px solid var(--line);
background: var(--surface2, #0f1621); color: var(--ink); cursor: pointer;
font: inherit; font-size: 12.5px; }
@@ -1121,10 +1124,68 @@
setTimeout(() => { input.focus(); input.select(); }, 0);
});
}
+ // "Add a page…": a two-field form (address + title). The title fills itself
+ // from the address as you type — brand-cased host, same as the chip label —
+ // until you edit it by hand; then it is yours. Resolves { url, title } or null.
+ function normalizeBookmarkUrl(s) {
+ let v = String(s || "").trim();
+ if (!v) return "";
+ if (!/^[a-z][a-z0-9+.-]*:/i.test(v)) v = "https://" + v.replace(/^\/+/, "");
+ try {
+ const u = new URL(v);
+ if (!/^https?:$|^bns:$|^file:$/.test(u.protocol)) return "";
+ // A real host: letters/digits/dots/dashes (IDNA already applied), no
+ // percent-escapes from spaces, and at least one dot or "localhost".
+ if (u.protocol !== "file:" && !(/^[a-z0-9.-]+$/i.test(u.hostname) && (u.hostname.includes(".") || u.hostname === "localhost"))) return "";
+ return u.href;
+ } catch { return ""; }
+ }
+ function inBookmarkForm(initial = {}) {
+ return new Promise((resolve) => {
+ const mask = document.createElement("div"); mask.className = "promptmask";
+ mask.innerHTML = `
Add a page to favorites
`
+ + `
`
+ + `
`
+ + `
`
+ + `
`;
+ document.body.appendChild(mask);
+ const url = mask.querySelector(".purl"), title = mask.querySelector(".ptitle"), err = mask.querySelector(".perr");
+ url.value = String(initial.url ?? ""); title.value = String(initial.title ?? "");
+ let titleTouched = !!title.value;
+ const autoTitle = () => {
+ if (titleTouched) return;
+ const norm = normalizeBookmarkUrl(url.value);
+ let host = ""; try { host = norm ? new URL(norm).hostname.replace(/^www\./, "") : ""; } catch {}
+ title.value = host ? bookmarkShortLabel("", host) : "";
+ };
+ url.addEventListener("input", autoTitle);
+ title.addEventListener("input", () => { titleTouched = title.value.trim().length > 0; if (!titleTouched) autoTitle(); });
+ const done = (val) => { try { mask.remove(); } catch {} document.removeEventListener("keydown", key); resolve(val); };
+ const submit = () => {
+ const norm = normalizeBookmarkUrl(url.value);
+ if (!norm) { err.textContent = "That doesn't look like a web address."; err.hidden = false; url.focus(); return; }
+ if (bookmarks.some((b) => b.url === norm)) { err.textContent = "Already in your favorites."; err.hidden = false; url.focus(); return; }
+ let host = ""; try { host = new URL(norm).hostname.replace(/^www\./, ""); } catch {}
+ done({ url: norm, title: title.value.trim() || (host ? bookmarkShortLabel("", host) : norm) });
+ };
+ const key = (e) => { if (e.key === "Escape") done(null); else if (e.key === "Enter") submit(); };
+ document.addEventListener("keydown", key);
+ mask.addEventListener("mousedown", (e) => { if (e.target === mask) done(null); });
+ mask.querySelector(".pcancel").addEventListener("click", () => done(null));
+ mask.querySelector(".pok").addEventListener("click", submit);
+ setTimeout(() => { url.focus(); url.select(); }, 0);
+ });
+ }
+ async function addBookmarkByHand(initial) {
+ const r = await inBookmarkForm(initial);
+ if (!r) return;
+ let favicon = null; try { const u = new URL(r.url); if (/^https?:$/.test(u.protocol)) favicon = `${u.origin}/favicon.ico`; } catch {}
+ T.addBookmark({ title: r.title, url: r.url, favicon });
+ }
// Right-click context menu on the bookmarks bar. Two modes:
// - On a specific bookmark: Open / Edit title / Remove
// - On empty area OR the placeholder text: Add current page (or Remove
- // if the page is already bookmarked)
+ // if the page is already bookmarked), and Add a page… by hand
function closeBmMenu() { const m = document.getElementById("bmMenu"); if (m) m.remove(); }
function openBmMenu(x, y, ctx) {
closeBmMenu();
@@ -1147,6 +1208,7 @@
} else {
items.push({ label: "Add current page", disabled: true });
}
+ items.push({ label: "Add a page…", act: () => addBookmarkByHand() });
const m = document.createElement("div");
m.id = "bmMenu"; m.className = "ctxmenu";
m.innerHTML = items.map((it, i) => it.sep