diff --git a/bundled-addons/screenshot/addon.json b/bundled-addons/screenshot/addon.json index ed8f36b..71da99d 100644 --- a/bundled-addons/screenshot/addon.json +++ b/bundled-addons/screenshot/addon.json @@ -1,8 +1,8 @@ { "id": "screenshot", "name": "Screenshot", - "version": "0.6.4", - "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.", + "version": "0.6.5", + "description": "Capture the current tab โ€” visible viewport, full page, or a rectangle you draw. Preview + annotate editor (crop, arrow, line, rect, ellipse, pen, resizable 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": "๐Ÿ“ธ", "main": "index.js", diff --git a/bundled-addons/screenshot/editor.css b/bundled-addons/screenshot/editor.css index 7b6c12d..c48657b 100644 --- a/bundled-addons/screenshot/editor.css +++ b/bundled-addons/screenshot/editor.css @@ -130,6 +130,7 @@ body { background: var(--bg); color: var(--ink); .stage #base { position: static; } .stage #over { pointer-events: none; } .stage[data-tool="arrow"] { cursor: crosshair; } +.stage[data-tool="line"] { cursor: crosshair; } .stage[data-tool="rect"] { cursor: crosshair; } .stage[data-tool="ellipse"]{ cursor: crosshair; } .stage[data-tool="pen"] { cursor: crosshair; } @@ -149,7 +150,12 @@ body { background: var(--bg); color: var(--ink); background: rgba(20,26,36,.92); color: var(--ink); border: 1px solid var(--acid); padding: 2px 6px; font: 15px/1.2 system-ui, -apple-system, Segoe UI, Roboto, sans-serif; - outline: none; border-radius: 3px; min-width: 80px; + outline: none; border-radius: 3px; + /* Textarea variant โ€” corner drag-to-resize is on (see resize:both in + JS). These floors keep the box visible even if the user resizes it + down to almost nothing. */ + min-width: 80px; min-height: 24px; + overflow: auto; } .toast { diff --git a/bundled-addons/screenshot/editor.html b/bundled-addons/screenshot/editor.html index 2513681..7e9e493 100644 --- a/bundled-addons/screenshot/editor.html +++ b/bundled-addons/screenshot/editor.html @@ -59,6 +59,9 @@ + diff --git a/bundled-addons/screenshot/editor.js b/bundled-addons/screenshot/editor.js index f305c19..565b765 100644 --- a/bundled-addons/screenshot/editor.js +++ b/bundled-addons/screenshot/editor.js @@ -392,6 +392,14 @@ function drawArrow(a, b) { octx.stroke(); } +// Plain line โ€” same drag flow as arrow, minus the head. Useful for +// underlines / separators / crossing-out without pointing at anything. +function drawLine(a, b) { + strokeStyle(); + octx.beginPath(); + octx.moveTo(a.x, a.y); octx.lineTo(b.x, b.y); octx.stroke(); +} + function drawRect(a, b) { strokeStyle(); const x = Math.min(a.x, b.x), y = Math.min(a.y, b.y); @@ -492,6 +500,7 @@ base.addEventListener("pointermove", (ev) => { state.drag.x = p.x; state.drag.y = p.y; const a = { x: state.drag.x0, y: state.drag.y0 }, b = { x: p.x, y: p.y }; if (state.tool === "arrow") { clearOver(); drawArrow(a, b); } + else if (state.tool === "line") { clearOver(); drawLine(a, b); } else if (state.tool === "rect") { clearOver(); drawRect(a, b); } else if (state.tool === "ellipse") { clearOver(); drawEllipse(a, b); } else if (state.tool === "crop" @@ -539,13 +548,16 @@ base.addEventListener("pointerup", (ev) => { function openTextInput(pt) { cancelTextInput(); const size = Math.max(16, state.width * 4); - const el = document.createElement("input"); - el.type = "text"; + // Textarea (not input) so the user can drag the corner to resize the + // box, and so text can wrap / span multiple lines. Enter commits; + // Shift+Enter inserts a newline. + const el = document.createElement("textarea"); el.className = "text-input"; - el.placeholder = "type, then Enter"; + el.placeholder = "type, then Enter (Shift+Enter for newline ยท drag corner to resize)"; el.autocomplete = "off"; el.setAttribute("autocorrect", "off"); el.setAttribute("spellcheck", "false"); + el.rows = 1; // 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. @@ -555,6 +567,9 @@ function openTextInput(pt) { 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)"; + el.style.resize = "both"; + el.style.overflow = "auto"; + el.style.whiteSpace = "pre"; const rect = base.getBoundingClientRect(); const scale = stage._scale || 1; const cssX = pt.x * scale + rect.left; @@ -569,13 +584,18 @@ function openTextInput(pt) { 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 click-through re-fires openTextInput on - // the base and spawns duplicate boxes. + // Don't let the box's own pointer events bubble to the canvas โ€” + // otherwise every click-through re-fires openTextInput on the base and + // spawns duplicate boxes, and drag-to-resize on the corner handle would + // start a canvas drag on the tool underneath. const swallow = (ev) => ev.stopPropagation(); - for (const t of ["pointerdown", "mousedown", "click"]) el.addEventListener(t, swallow); + for (const t of ["pointerdown", "mousedown", "click", "pointerup", "pointermove"]) { + el.addEventListener(t, swallow); + } el.addEventListener("keydown", (ev) => { - if (ev.key === "Enter") { commitTextInput(); ev.preventDefault(); ev.stopPropagation(); } + if (ev.key === "Enter" && !ev.shiftKey) { + 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. @@ -594,7 +614,14 @@ function commitTextInput() { bctx.fillStyle = state.color; bctx.font = `${ti.size}px system-ui, -apple-system, Segoe UI, Roboto, sans-serif`; bctx.textBaseline = "alphabetic"; - bctx.fillText(value, ti.x, ti.y); + // Multi-line rendering โ€” one fillText per line, stepping down by the + // font's line height so the visible box's layout matches the baked-in + // pixels. + const lineH = ti.size * 1.15; + const lines = value.split(/\r?\n/); + for (let i = 0; i < lines.length; i++) { + bctx.fillText(lines[i], ti.x, ti.y + i * lineH); + } pushSnapshot(); } function cancelTextInput() { @@ -751,6 +778,7 @@ window.addEventListener("keydown", (ev) => { if (ev.key === "Escape") { cancelCrop(); ev.preventDefault(); return; } } if (ev.key === "a") setTool("arrow"); + else if (ev.key === "l") setTool("line"); else if (ev.key === "r") setTool("rect"); else if (ev.key === "o") setTool("ellipse"); else if (ev.key === "p") setTool("pen"); diff --git a/bundled-addons/screenshot/panel.html b/bundled-addons/screenshot/panel.html index cba5244..e565edc 100644 --- a/bundled-addons/screenshot/panel.html +++ b/bundled-addons/screenshot/panel.html @@ -172,7 +172,7 @@
- +