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"), }, });