-
diff --git a/bundled-addons/screenshot/editor.js b/bundled-addons/screenshot/editor.js
index e22594a..3e82c45 100644
--- a/bundled-addons/screenshot/editor.js
+++ b/bundled-addons/screenshot/editor.js
@@ -298,8 +298,14 @@ function cancelCrop() {
clearOver();
updateCropUI();
}
+// Clicking an already-active tool toggles it off (back to "select" = no
+// tool). The dedicated "Select" button used to live here but read as
+// broken to users β clicking it did nothing visible. Toggle-off gives the
+// same "put the pen down" affordance without a mystery button.
for (const b of document.querySelectorAll(".tool")) {
- b.addEventListener("click", () => setTool(b.dataset.tool));
+ b.addEventListener("click", () => {
+ setTool(state.tool === b.dataset.tool ? "select" : b.dataset.tool);
+ });
}
for (const b of document.querySelectorAll(".swatch")) {
b.addEventListener("click", () => {
@@ -494,10 +500,19 @@ function openTextInput(pt) {
const el = document.createElement("input");
el.type = "text";
el.className = "text-input";
- el.placeholder = "textβ¦";
+ el.placeholder = "type, then Enter";
+ el.autocomplete = "off";
+ el.setAttribute("autocorrect", "off");
+ el.setAttribute("spellcheck", "false");
+ // Visual weight β a bright acid halo around the box + the actual
+ // ink colour on the text itself, so users see something clearly
+ // happened when they clicked.
el.style.color = state.color;
el.style.font = `${size}px system-ui, -apple-system, Segoe UI, Roboto, sans-serif`;
el.style.lineHeight = "1.15";
+ el.style.background = "rgba(11,14,20,0.94)";
+ el.style.border = "2px solid var(--acid, #d6ff3d)";
+ el.style.boxShadow = "0 0 0 3px rgba(214,255,61,.25), 0 4px 14px rgba(0,0,0,.45)";
const rect = base.getBoundingClientRect();
const scale = stage._scale || 1;
const cssX = pt.x * scale + rect.left;
@@ -505,19 +520,24 @@ function openTextInput(pt) {
el.style.left = cssX + "px";
el.style.top = (cssY - size) + "px";
document.body.appendChild(el);
- // Focus after a paint so Chromium reliably picks it up (the input goes
- // from `display:absolute` to laid-out; focus() called synchronously
- // right after appendChild races that in some builds).
+ // Three-way focus attempt β Chromium is racy about focusing an element
+ // that appeared mid-pointer-event. Synchronous focus() first (works on
+ // most builds), then a paint tick, then a short timer as belt-and-braces.
+ el.focus();
requestAnimationFrame(() => el.focus());
+ setTimeout(() => { if (state.textInput && state.textInput.el === el) el.focus(); }, 30);
state.textInput = { x: pt.x, y: pt.y, size, el };
// Don't let the input's own pointerdown / mousedown / click bubble to
- // the canvas β otherwise every keystroke click-through re-fires
- // openTextInput on the base and spawns duplicate boxes.
+ // the canvas β otherwise every click-through re-fires openTextInput on
+ // the base and spawns duplicate boxes.
const swallow = (ev) => ev.stopPropagation();
for (const t of ["pointerdown", "mousedown", "click"]) el.addEventListener(t, swallow);
el.addEventListener("keydown", (ev) => {
- if (ev.key === "Enter") { commitTextInput(); ev.preventDefault(); ev.stopPropagation(); }
+ if (ev.key === "Enter") { commitTextInput(); ev.preventDefault(); ev.stopPropagation(); }
else if (ev.key === "Escape") { cancelTextInput(); ev.preventDefault(); ev.stopPropagation(); }
+ // Every other key stays inside the input β belt against a stray
+ // document-level keydown handler stealing focus.
+ else ev.stopPropagation();
});
el.addEventListener("blur", commitTextInput);
}
diff --git a/bundled-addons/screenshot/panel.html b/bundled-addons/screenshot/panel.html
index c5f46a2..317ea11 100644
--- a/bundled-addons/screenshot/panel.html
+++ b/bundled-addons/screenshot/panel.html
@@ -18,12 +18,16 @@
body { background: var(--bg); color: var(--ink);
font: 13px/1.5 system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
display: flex; flex-direction: column; }
- header { display: flex; align-items: center; justify-content: space-between;
+ header { display: flex; align-items: center;
padding: 10px 14px; border-bottom: 1px solid var(--line);
background: var(--panel); gap: 8px; }
header .t { font-weight: 600; display: flex; gap: 8px; align-items: center; min-width: 0; }
header .t .em { font-size: 15px; }
header .m { color: var(--dim); font-size: 11.5px; min-height: 15px; flex: 1; text-align: right; }
+ /* Force the sound / max / close cluster to hug the right edge no matter
+ what else fills the header. justify-content on the parent kept nudging
+ them mid-row when the status text was empty. */
+ header #btn-sound { margin-left: auto; }
header .max {
border: 1px solid var(--line); border-radius: 6px; background: var(--btn);
color: var(--ink); cursor: pointer; padding: 4px 6px; font: inherit; line-height: 0;
@@ -102,6 +106,16 @@
}
.recent .tile img { width: 100%; height: 100%; object-fit: cover; background: #fff; }
.recent .tile:hover { border-color: var(--acid); }
+ .recent .tile .tdel {
+ position: absolute; top: 2px; right: 2px;
+ width: 18px; height: 18px; line-height: 16px; text-align: center;
+ background: rgba(11,14,20,.85); color: #fff; border-radius: 50%;
+ font-size: 14px; font-weight: 700; cursor: pointer;
+ opacity: 0; transition: opacity 120ms, background 120ms, color 120ms;
+ user-select: none;
+ }
+ .recent .tile:hover .tdel { opacity: 1; }
+ .recent .tile .tdel:hover { background: #ff5b5b; color: #101418; }
.recent .clear { background: none; border: 0; color: var(--dim); font: inherit; cursor: pointer; text-transform: none; letter-spacing: 0; }
.recent .clear:hover { color: var(--err); }
@@ -319,16 +333,27 @@
return;
}
recentBox.classList.add("on");
- recentStrip.innerHTML = list.map((r) =>
- `
`
- ).join("");
+ recentStrip.innerHTML = list.map((r) => {
+ const nameAttr = r.name.replace(/"/g, """);
+ return `
`
+ + `Γ`
+ + `
`;
+ }).join("");
for (const tile of recentStrip.querySelectorAll(".tile")) {
const name = tile.dataset.name;
try {
const b = await window.silentmode.invoke("getBytes", { name });
- if (b && b.dataUrl) tile.innerHTML = `

`;
+ if (b && b.dataUrl) {
+ // Keep the delete button; drop it back on top of the image.
+ const del = tile.querySelector(".tdel");
+ tile.innerHTML = `

`;
+ if (del) tile.appendChild(del);
+ }
} catch {}
- tile.addEventListener("click", async () => {
+ tile.addEventListener("click", async (ev) => {
+ // The per-tile X sits inside the tile; don't count its click as
+ // a tile click.
+ if (ev.target && ev.target.classList.contains("tdel")) return;
try {
const b = await window.silentmode.invoke("getBytes", { name });
if (!b) return;
@@ -343,6 +368,22 @@
}
});
}
+ // Per-tile delete β no confirmation strip (single-item destroy is
+ // small enough to be safe with just the X; the sweeping "clear all"
+ // still gates on the inline red confirmation).
+ for (const del of recentStrip.querySelectorAll(".tdel")) {
+ del.addEventListener("click", async (ev) => {
+ ev.stopPropagation();
+ const nm = del.dataset.del;
+ try {
+ await window.silentmode.invoke("clearRecent", { name: nm });
+ if (last && last.name === nm) { last = null; renderPreview(); }
+ await refreshRecent();
+ } catch (e) {
+ setStatus("Delete failed: " + (e && e.message || e), "err");
+ }
+ });
+ }
} catch (e) {
console.warn("refreshRecent failed:", e);
}