diff --git a/bundled-addons/screenshot/addon.json b/bundled-addons/screenshot/addon.json
index c7d41da..5f06560 100644
--- a/bundled-addons/screenshot/addon.json
+++ b/bundled-addons/screenshot/addon.json
@@ -1,7 +1,7 @@
{
"id": "screenshot",
"name": "Screenshot",
- "version": "0.6.0",
+ "version": "0.6.1",
"description": "Capture the current tab β visible viewport, full page, or a rectangle you draw. Preview + annotate editor (crop, arrow, rect, ellipse, pen, text, blur/mosaic redaction, undo, copy, save) live inside the sidebar. Expand the sidebar to full window for a canvas-sized editor.",
"author": "Silent Mode",
"icon": "πΈ",
diff --git a/bundled-addons/screenshot/editor.css b/bundled-addons/screenshot/editor.css
index 4591a12..cf165e1 100644
--- a/bundled-addons/screenshot/editor.css
+++ b/bundled-addons/screenshot/editor.css
@@ -25,8 +25,15 @@ body { background: var(--bg); color: var(--ink);
user-select: none; flex-wrap: wrap; }
/* Older versions grouped the drawing tools in the middle of the bar β keep
the drawing / swatch / width / undo cluster centred while the top-bar
- (back / max / sound / name β save / copy) stays edge-anchored. */
-.toolbar { justify-content: center; }
+ (back / name β save / copy / sound / max / close) stays edge-anchored. */
+.toolbar { justify-content: center; gap: 10px; row-gap: 6px; }
+/* Each visible group of tools wraps as a unit β when the sidebar gets
+ narrow, the whole swatch row moves to the next line instead of a
+ single dot spilling by itself. */
+.tgroup { display: inline-flex; align-items: center; gap: 4px; flex: 0 0 auto; }
+
+/* Close-sidebar button β reads as danger without shouting. */
+#close-sidebar:hover { border-color: rgba(255,91,91,.55); color: var(--danger); }
.topbar .name { color: var(--dim); font-size: 11.5px; overflow: hidden;
text-overflow: ellipsis; white-space: nowrap; max-width: 200px; margin: 0 4px; }
.topbar .spacer, .toolbar .spacer { flex: 1; }
diff --git a/bundled-addons/screenshot/editor.html b/bundled-addons/screenshot/editor.html
index 3d1e176..7c798e7 100644
--- a/bundled-addons/screenshot/editor.html
+++ b/bundled-addons/screenshot/editor.html
@@ -37,57 +37,66 @@
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
diff --git a/bundled-addons/screenshot/editor.js b/bundled-addons/screenshot/editor.js
index 902743f..3e641a5 100644
--- a/bundled-addons/screenshot/editor.js
+++ b/bundled-addons/screenshot/editor.js
@@ -422,7 +422,11 @@ function commitOver() {
over.style.pointerEvents = "none"; // draw layer never blocks input; base receives
base.addEventListener("pointerdown", (ev) => {
if (state.tool === "select") return;
- if (state.tool === "text") { openTextInput(pointToCanvas(ev)); return; }
+ if (state.tool === "text") {
+ ev.preventDefault();
+ openTextInput(pointToCanvas(ev));
+ return;
+ }
base.setPointerCapture(ev.pointerId);
const p = pointToCanvas(ev);
if (state.tool === "pen") { state.pen = [p]; }
@@ -475,39 +479,58 @@ base.addEventListener("pointerup", (ev) => {
});
// -- text tool -----------------------------------------------------------
+// A little floats over the canvas at the click point; Enter commits
+// as fillText, Escape drops. Chases a few browser quirks:
+// - Focus after the DOM mutation, not before: some Chromium builds
+// drop focus() when the element hasn't yet been laid out.
+// - Contain the input's own pointer events so a mousedown inside it
+// doesn't bubble to the canvas and immediately re-fire openTextInput,
+// spawning a fresh empty box.
+// - Track height instead of a hard-coded 14 px offset so tall fonts sit
+// on the click's baseline rather than 14 px above it.
function openTextInput(pt) {
cancelTextInput();
+ const size = Math.max(16, state.width * 4);
const el = document.createElement("input");
el.type = "text";
el.className = "text-input";
el.placeholder = "textβ¦";
el.style.color = state.color;
- el.style.font = `${Math.max(14, state.width * 4)}px system-ui, -apple-system, Segoe UI, Roboto, sans-serif`;
+ el.style.font = `${size}px system-ui, -apple-system, Segoe UI, Roboto, sans-serif`;
+ el.style.lineHeight = "1.15";
const rect = base.getBoundingClientRect();
const scale = stage._scale || 1;
const cssX = pt.x * scale + rect.left;
const cssY = pt.y * scale + rect.top;
el.style.left = cssX + "px";
- el.style.top = (cssY - 14) + "px";
+ el.style.top = (cssY - size) + "px";
document.body.appendChild(el);
- el.focus();
- state.textInput = { x: pt.x, y: pt.y, 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).
+ requestAnimationFrame(() => el.focus());
+ 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.
+ 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(); }
- else if (ev.key === "Escape") { cancelTextInput(); ev.preventDefault(); }
+ if (ev.key === "Enter") { commitTextInput(); ev.preventDefault(); ev.stopPropagation(); }
+ else if (ev.key === "Escape") { cancelTextInput(); ev.preventDefault(); ev.stopPropagation(); }
});
el.addEventListener("blur", commitTextInput);
}
function commitTextInput() {
const ti = state.textInput;
if (!ti) return;
- const value = ti.el.value.trim();
+ const value = ti.el.value;
+ const trimmed = value.trim();
ti.el.remove();
state.textInput = null;
- if (!value) return;
- const size = Math.max(14, state.width * 4);
+ if (!trimmed) return;
bctx.fillStyle = state.color;
- bctx.font = `${size}px system-ui, -apple-system, Segoe UI, Roboto, sans-serif`;
+ bctx.font = `${ti.size}px system-ui, -apple-system, Segoe UI, Roboto, sans-serif`;
bctx.textBaseline = "alphabetic";
bctx.fillText(value, ti.x, ti.y);
pushSnapshot();
@@ -575,6 +598,16 @@ if (cancelCropBtn) cancelCropBtn.addEventListener("click", cancelCrop);
// -- top-bar navigation ----------------------------------------------
$("back").addEventListener("click", () => { playDiscard(); location.href = "panel.html"; });
+// Close the sidebar entirely β same IPC the dock icon toggles. We stop
+// on the panel side (see panel.html); the editor mirrors the affordance
+// so users don't need to navigate back before hiding the addon.
+const closeSbBtn = document.getElementById("close-sidebar");
+if (closeSbBtn && window.silentmode?.sidebar) {
+ closeSbBtn.addEventListener("click", () => {
+ try { window.silentmode.sidebar.close(); } catch {}
+ });
+}
+
// Sound-toggle button β mirrors the sidebar panel's, both persist via
// silentmode.storage so opening either surface reflects the same setting.
const soundBtn = $("toggle-sound");
diff --git a/bundled-addons/screenshot/panel.html b/bundled-addons/screenshot/panel.html
index f2038db..c5f46a2 100644
--- a/bundled-addons/screenshot/panel.html
+++ b/bundled-addons/screenshot/panel.html
@@ -30,6 +30,7 @@
}
header .max:hover { background: var(--btn-h); }
header .max svg { width: 14px; height: 14px; display: block; }
+ header .max.close:hover { border-color: rgba(255,91,91,.55); color: var(--err); }
main { flex: 1; overflow-y: auto; padding: 12px 14px; display: flex; flex-direction: column; gap: 12px; }
.modes { display: flex; gap: 6px; }
button.mode {
@@ -115,6 +116,11 @@
+
@@ -431,6 +437,14 @@
btnCopy.addEventListener("click", doCopy);
btnSave.addEventListener("click", doSave);
btnMax.addEventListener("click", toggleMax);
+ document.getElementById("btn-close").addEventListener("click", async () => {
+ // Explicit close from the sidebar itself β same IPC the toolbar dock
+ // icon toggles. No need to un-maximize first; setSidebar(false) on the
+ // main side flips visibility and the maximize state is remembered so
+ // the next open lands where it left off.
+ try { await window.silentmode.sidebar.close(); }
+ catch (e) { setStatus("Close failed: " + (e && e.message || e), "err"); }
+ });
// Inline confirmation for "clear all" β the native confirm() looks like an
// OS-modal from the last decade AND opens over the tab area (out of the
diff --git a/sidebar-preload.js b/sidebar-preload.js
index d426909..598a14e 100644
--- a/sidebar-preload.js
+++ b/sidebar-preload.js
@@ -27,6 +27,10 @@ contextBridge.exposeInMainWorld("silentmode", {
toggleMax:() => ipcRenderer.invoke("sidebar-toggle-max"),
isMax: () => ipcRenderer.invoke("sidebar-is-max"),
onMaxChange: (cb) => ipcRenderer.on("sidebar-max-change", (_e, max) => cb(!!max)),
+ // Close the sidebar entirely. Same IPC the toolbar dock icon toggles
+ // β the panel/editor can offer an explicit X button so users don't
+ // have to hunt for the dock icon just to hide the panel.
+ close: () => ipcRenderer.invoke("sidebar-close"),
},
});