diff --git a/unredact/static/canvas.js b/unredact/static/canvas.js index da63549..5dc1187 100644 --- a/unredact/static/canvas.js +++ b/unredact/static/canvas.js @@ -199,57 +199,57 @@ function drawRedactionPreview(r, isActive) { const fontName = state.fonts.find(f => f.id === (o.fontId ?? a.font.id))?.name ?? a.font.name; const fontSize = o.fontSize ?? a.font.size; const fontStr = `${fontSize}px "${fontName}"`; - const gapW = o.gapWidth ?? a.gap.w; + + const leftText = o.leftText ?? ""; + const rightText = o.rightText ?? ""; + const mergedText = leftText + r.preview + rightText; if (isActive) { const startX = a.line.x + (o.offsetX ?? 0); const startY = a.line.y + (o.offsetY ?? 0); ctx.textBaseline = "top"; + ctx.font = fontStr; - let cursorX = startX; - - const leftText = o.leftText ?? ""; + // Draw as one continuous string for natural kerning + // Left portion in green if (leftText) { - cursorX += drawStyledText(leftText, cursorX, startY, fontName, fontSize, "rgba(0, 200, 0, 0.7)"); + drawStyledText(leftText, startX, startY, fontName, fontSize, "rgba(0, 200, 0, 0.7)"); } + // Measure where the preview text starts and ends + const leftW = leftText ? measureStyledText(leftText, fontName, fontSize) : 0; + const previewX = startX + leftW; + const previewW = ctx.measureText(r.preview).width; + + // Highlight behind the preview portion const pad = fontSize * 0.15; ctx.fillStyle = "rgba(255, 200, 0, 0.2)"; - ctx.fillRect(cursorX, startY - pad, gapW, fontSize + pad * 2); - ctx.strokeStyle = "rgba(255, 200, 0, 0.8)"; - ctx.lineWidth = 2; - ctx.strokeRect(cursorX, startY - pad, gapW, fontSize + pad * 2); + ctx.fillRect(previewX, startY - pad, previewW, fontSize + pad * 2); + // Preview text in yellow ctx.fillStyle = "rgba(255, 200, 0, 0.9)"; ctx.font = fontStr; - ctx.fillText(r.preview, cursorX, startY); + ctx.fillText(r.preview, previewX, startY); - cursorX += gapW; - - const rightText = o.rightText ?? ""; + // Right portion in green if (rightText) { - drawStyledText(rightText, cursorX, startY, fontName, fontSize, "rgba(0, 200, 0, 0.7)"); + const rightX = previewX + previewW; + drawStyledText(rightText, rightX, startY, fontName, fontSize, "rgba(0, 200, 0, 0.7)"); } ctx.strokeStyle = "rgba(0, 200, 0, 0.3)"; ctx.lineWidth = 1; ctx.strokeRect(a.line.x, a.line.y, a.line.w, a.line.h); } else { - const gapX = a.gap.x + (o.offsetX ?? 0); + // Inactive: draw full merged text in yellow at line position + const startX = a.line.x + (o.offsetX ?? 0); const textY = a.line.y + (o.offsetY ?? 0); - const pad = fontSize * 0.1; - ctx.fillStyle = "rgba(255, 200, 0, 0.12)"; - ctx.fillRect(gapX, textY - pad, gapW, fontSize + pad * 2); - - ctx.strokeStyle = "rgba(255, 200, 0, 0.5)"; - ctx.lineWidth = 1; - ctx.strokeRect(gapX, textY - pad, gapW, fontSize + pad * 2); ctx.font = fontStr; ctx.textBaseline = "top"; ctx.fillStyle = "rgba(255, 200, 0, 0.9)"; - ctx.fillText(r.preview, gapX, textY); + ctx.fillText(mergedText, startX, textY); } } diff --git a/unredact/static/dom.js b/unredact/static/dom.js index 9c7383d..69680ce 100644 --- a/unredact/static/dom.js +++ b/unredact/static/dom.js @@ -52,7 +52,7 @@ export const gapValue = document.getElementById("gap-value"); export const textEditBar = document.getElementById("text-edit-bar"); export const leftTextInput = /** @type {HTMLInputElement} */ (document.getElementById("left-text-input")); export const rightTextInput = /** @type {HTMLInputElement} */ (document.getElementById("right-text-input")); -export const redactionMarker = document.getElementById("redaction-marker"); +export const redactionMarker = /** @type {HTMLInputElement} */ (document.getElementById("redaction-marker")); export const textReset = document.getElementById("text-reset"); export const toastContainer = document.getElementById("toast-container"); diff --git a/unredact/static/index.html b/unredact/static/index.html index 79bc3e6..b09bbc6 100644 --- a/unredact/static/index.html +++ b/unredact/static/index.html @@ -135,7 +135,7 @@ diff --git a/unredact/static/popover.js b/unredact/static/popover.js index a6ff817..f673914 100644 --- a/unredact/static/popover.js +++ b/unredact/static/popover.js @@ -44,8 +44,6 @@ export function openPopover(id) { r.preview = r.solution.text; r.status = "analyzed"; solveAccept.hidden = true; - redactionMarker.textContent = r.solution.text; - redactionMarker.className = "redaction-marker preview"; } else { solveAccept.hidden = r.preview === null; } @@ -62,7 +60,7 @@ export function openPopover(id) { textEditBar.hidden = false; leftTextInput.value = r.overrides.leftText; rightTextInput.value = r.overrides.rightText; - redactionMarker.textContent = r.preview || "???"; + redactionMarker.value = r.preview || ""; redactionMarker.className = r.preview ? "redaction-marker preview" : "redaction-marker"; } @@ -172,6 +170,16 @@ export function initPopover() { }); + redactionMarker.addEventListener("input", () => { + const r = state.redactions[state.activeRedaction]; + if (!r) return; + const guess = redactionMarker.value.trim(); + r.preview = guess || null; + redactionMarker.className = guess ? "redaction-marker preview" : "redaction-marker"; + solveAccept.hidden = !guess; + renderCanvas(); + }); + solveTolerance.addEventListener("input", () => { solveTolValue.textContent = solveTolerance.value; }); diff --git a/unredact/static/solver.js b/unredact/static/solver.js index fe2c571..2622bff 100644 --- a/unredact/static/solver.js +++ b/unredact/static/solver.js @@ -145,7 +145,7 @@ function handleSolveEvent(data, redactionId) { const r = state.redactions[redactionId]; if (!r) return; r.preview = data.text; - redactionMarker.textContent = data.text; + redactionMarker.value = data.text; redactionMarker.className = "redaction-marker preview"; renderCanvas(); solveResults.querySelectorAll(".solve-result").forEach(el => el.classList.remove("active")); diff --git a/unredact/static/style.css b/unredact/static/style.css index 735bfbb..0528fc1 100644 --- a/unredact/static/style.css +++ b/unredact/static/style.css @@ -684,12 +684,25 @@ main { .redaction-marker { flex-shrink: 0; + min-width: 3rem; + max-width: 12rem; padding: 0.25rem 0.4rem; background: var(--red); color: #fff; font-size: 0.65rem; font-weight: 700; + border: none; border-radius: 3px; + text-align: center; +} + +.redaction-marker::placeholder { + color: rgba(255, 255, 255, 0.6); +} + +.redaction-marker:focus { + outline: none; + box-shadow: 0 0 0 1px var(--accent); } .redaction-marker.preview {