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