OCR-Viewer (#1)
Co-authored-by: nathan <nathan.kehler@gmail.com> Reviewed-on: #1
This commit is contained in:
409
prompts/ocr-viewer-redesign2.md
Normal file
409
prompts/ocr-viewer-redesign2.md
Normal file
@@ -0,0 +1,409 @@
|
||||
P44 OCR Viewer — Pass 2 Diff View + Edit Mode Reference Pane
|
||||
|
||||
Scope: p44-ocr-viewer.js, p44-ocr-viewer.css, p44-ocr-viewer.html. Do not touch PDF rendering, zoom, Pass 1 flag logic, or the corrections storage model.
|
||||
|
||||
BACKGROUND — how corrections are stored
|
||||
|
||||
Each page's corrections live in state.corrections[pageNum] with this shape:
|
||||
javascript
|
||||
|
||||
{
|
||||
original: "raw OCR text as loaded",
|
||||
pass1: "volunteer-corrected text",
|
||||
pass2: "senior editor text"
|
||||
}
|
||||
|
||||
The diff always compares original against pass1. Never use state.currentPage inside diff functions — always pass pageNum explicitly to avoid page-bleed bugs.
|
||||
|
||||
PART 1 — HTML (p44-ocr-viewer.html)
|
||||
|
||||
Inside #text-panel-body, replace:
|
||||
html
|
||||
|
||||
<div id="pass2-revert-bar" style="display:none;"></div>
|
||||
<div id="pass2-change-summary" style="display:none;"></div>
|
||||
<div id="text-output" style="display:none;"></div>
|
||||
<div id="editor-wrap" style="display:none;">
|
||||
<div id="line-numbers"></div>
|
||||
<textarea id="text-editor" aria-label="OCR text editor"></textarea>
|
||||
</div>
|
||||
|
||||
With:
|
||||
html
|
||||
|
||||
<div id="pass2-revert-bar" style="display:none;"></div>
|
||||
<div id="diff-toolbar" style="display:none;"></div>
|
||||
<div id="text-output" style="display:none;"></div>
|
||||
<div id="editor-wrap" style="display:none;">
|
||||
<div id="diff-reference" style="display:none;"></div>
|
||||
<div id="editor-bottom">
|
||||
<div id="line-numbers"></div>
|
||||
<textarea id="text-editor" aria-label="OCR text editor"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
In the right panel header, add a diff toggle button immediately before #btn-edit:
|
||||
html
|
||||
|
||||
<button id="btn-diff" class="tb-btn" title="Toggle diff view — shows Pass 1 changes highlighted">⇄ Diff</button>
|
||||
|
||||
Remove <div id="pass2-change-summary" style="display:none;"></div> entirely — it is replaced by the new diff system.
|
||||
|
||||
PART 2 — CSS (p44-ocr-viewer.css)
|
||||
|
||||
Add at the end of the file:
|
||||
css
|
||||
|
||||
/* ── Diff toolbar ─────────────────────────────────────────────── */
|
||||
#diff-toolbar {
|
||||
display: none;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 5px 16px;
|
||||
background: var(--surface);
|
||||
border-bottom: 1px solid var(--border);
|
||||
font-size: 10px;
|
||||
color: var(--text-dim);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
#diff-toolbar .diff-legend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex: 1;
|
||||
}
|
||||
#diff-toolbar .diff-count {
|
||||
font-size: 10px;
|
||||
color: var(--gold-dim);
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.diff-swatch {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
font-size: 10px;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
.diff-swatch-del {
|
||||
width: 24px; height: 10px;
|
||||
background: rgba(208,80,80,0.22);
|
||||
border-radius: 2px;
|
||||
}
|
||||
.diff-swatch-ins {
|
||||
width: 24px; height: 10px;
|
||||
background: rgba(92,184,92,0.18);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* ── Diff inline highlights ───────────────────────────────────── */
|
||||
.diff-del {
|
||||
background: rgba(208,80,80,0.22);
|
||||
color: #e07070;
|
||||
border-radius: 2px;
|
||||
padding: 0 2px;
|
||||
text-decoration: line-through;
|
||||
font-size: 11px;
|
||||
}
|
||||
.diff-ins {
|
||||
background: rgba(92,184,92,0.18);
|
||||
color: #7ed87e;
|
||||
border-radius: 2px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
/* ── Editor wrap — vertical split ─────────────────────────────── */
|
||||
#editor-wrap {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
border-top: 1px solid var(--gold-dim);
|
||||
}
|
||||
|
||||
/* ── Diff reference pane (top half of editor split) ───────────── */
|
||||
#diff-reference {
|
||||
display: none;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 12px 20px;
|
||||
font-size: 12.5px;
|
||||
line-height: 1.75;
|
||||
color: var(--text);
|
||||
background: var(--surface);
|
||||
white-space: pre-wrap;
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
}
|
||||
#diff-reference-label {
|
||||
font-size: 9px;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: var(--gold-dim);
|
||||
margin-bottom: 8px;
|
||||
padding-bottom: 5px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
/* ── Editor bottom half ───────────────────────────────────────── */
|
||||
#editor-bottom {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
Also update the existing #editor-wrap rule — remove flex-direction and border-top from it since those are now set above. The existing rule in the file should become:
|
||||
css
|
||||
|
||||
#editor-wrap {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
The new rule added above replaces it fully — delete the old one to avoid conflict.
|
||||
|
||||
PART 3 — JS (p44-ocr-viewer.js)
|
||||
|
||||
3a — New state flag
|
||||
|
||||
Inside the state object, add:
|
||||
javascript
|
||||
|
||||
diffMode: false,
|
||||
|
||||
3b — DOM refs
|
||||
|
||||
Add after the existing editor DOM refs:
|
||||
javascript
|
||||
|
||||
const btnDiff = document.getElementById('btn-diff');
|
||||
const diffToolbar = document.getElementById('diff-toolbar');
|
||||
const diffReference = document.getElementById('diff-reference');
|
||||
const editorBottom = document.getElementById('editor-bottom');
|
||||
|
||||
3c — Word-level diff engine
|
||||
|
||||
Add this new function. Place it directly above buildChangeSummary():
|
||||
javascript
|
||||
|
||||
function buildInlineDiff(originalText, pass1Text) {
|
||||
const orig = (originalText || '').split(/(\s+)/);
|
||||
const edit = (pass1Text || '').split(/(\s+)/);
|
||||
const result = [];
|
||||
|
||||
let i = 0, j = 0;
|
||||
while (i < orig.length || j < edit.length) {
|
||||
if (i >= orig.length) {
|
||||
result.push({ type: 'ins', text: edit[j++] });
|
||||
} else if (j >= edit.length) {
|
||||
result.push({ type: 'del', text: orig[i++] });
|
||||
} else if (orig[i] === edit[j]) {
|
||||
result.push({ type: 'same', text: orig[i] });
|
||||
i++; j++;
|
||||
} else {
|
||||
result.push({ type: 'del', text: orig[i++] });
|
||||
result.push({ type: 'ins', text: edit[j++] });
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
3d — Diff renderer
|
||||
|
||||
Add this new function directly below buildInlineDiff():
|
||||
javascript
|
||||
|
||||
function renderInlineDiff(pageNum) {
|
||||
const corr = state.corrections && state.corrections[pageNum];
|
||||
const originalText = (corr && corr.original) || state.ocrPages[pageNum] || '';
|
||||
const pass1Text = corr && corr.pass1;
|
||||
|
||||
if (!pass1Text) return null;
|
||||
|
||||
const tokens = buildInlineDiff(originalText, pass1Text);
|
||||
const delCount = tokens.filter(t => t.type === 'del' && t.text.trim()).length;
|
||||
const insCount = tokens.filter(t => t.type === 'ins' && t.text.trim()).length;
|
||||
const total = Math.max(delCount, insCount);
|
||||
|
||||
const html = tokens.map(t => {
|
||||
if (t.type === 'same') return escH(t.text);
|
||||
if (t.type === 'del') return '<span class="diff-del">' + escH(t.text) + '</span>';
|
||||
if (t.type === 'ins') return '<span class="diff-ins">' + escH(t.text) + '</span>';
|
||||
return '';
|
||||
}).join('');
|
||||
|
||||
return { html, total };
|
||||
}
|
||||
|
||||
3e — Diff toolbar renderer
|
||||
|
||||
Add this new function:
|
||||
javascript
|
||||
|
||||
function showDiffToolbar(pageNum) {
|
||||
const result = renderInlineDiff(pageNum);
|
||||
if (!result) {
|
||||
diffToolbar.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
diffToolbar.innerHTML =
|
||||
'<div class="diff-legend">' +
|
||||
'<span class="diff-swatch"><span class="diff-swatch-del"></span> removed</span>' +
|
||||
'<span class="diff-swatch"><span class="diff-swatch-ins"></span> added</span>' +
|
||||
'</div>' +
|
||||
'<span class="diff-count">' + result.total + ' change' + (result.total === 1 ? '' : 's') + ' from Pass 1</span>';
|
||||
diffToolbar.style.display = 'flex';
|
||||
}
|
||||
|
||||
3f — Apply diff to display panel
|
||||
|
||||
Add this new function:
|
||||
javascript
|
||||
|
||||
function applyDiffToDisplay(pageNum) {
|
||||
if (!state.diffMode) return;
|
||||
const result = renderInlineDiff(pageNum);
|
||||
if (!result) return;
|
||||
textOutput.innerHTML =
|
||||
'<pre style="white-space:pre-wrap;font-family:inherit;font-size:12.5px;' +
|
||||
'line-height:1.75;color:var(--text);padding:16px 20px;">' +
|
||||
result.html + '</pre>';
|
||||
}
|
||||
|
||||
3g — Apply diff to reference pane (edit mode)
|
||||
|
||||
Add this new function:
|
||||
javascript
|
||||
|
||||
function applyDiffToReference(pageNum) {
|
||||
const result = renderInlineDiff(pageNum);
|
||||
if (!result || !state.diffMode) {
|
||||
diffReference.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
diffReference.innerHTML =
|
||||
'<div id="diff-reference-label">Pass 1 corrections — reference</div>' +
|
||||
'<pre style="white-space:pre-wrap;font-family:inherit;font-size:12.5px;' +
|
||||
'line-height:1.75;color:var(--text);margin:0;">' +
|
||||
result.html + '</pre>';
|
||||
diffReference.style.display = 'block';
|
||||
}
|
||||
|
||||
3h — Toggle diff mode
|
||||
|
||||
Add this new function:
|
||||
javascript
|
||||
|
||||
function toggleDiff(pageNum) {
|
||||
const corr = state.corrections && state.corrections[pageNum];
|
||||
const hasPass1 = corr && corr.pass1;
|
||||
|
||||
if (!hasPass1) {
|
||||
setStatus('No Pass 1 corrections on this page — nothing to diff.');
|
||||
return;
|
||||
}
|
||||
|
||||
state.diffMode = !state.diffMode;
|
||||
btnDiff.classList.toggle('active', state.diffMode);
|
||||
|
||||
if (state.editMode) {
|
||||
applyDiffToReference(pageNum);
|
||||
} else {
|
||||
if (state.diffMode) {
|
||||
applyDiffToDisplay(pageNum);
|
||||
showDiffToolbar(pageNum);
|
||||
} else {
|
||||
showOCRPage(pageNum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Add the event listener immediately after the function:
|
||||
javascript
|
||||
|
||||
btnDiff.addEventListener('click', () => toggleDiff(state.currentPage));
|
||||
|
||||
3i — Update showOCRPage()
|
||||
|
||||
At the very end of showOCRPage(), after updateCorrectionIndicator(pageNum) and before the Pass 2 auto-edit block, add:
|
||||
javascript
|
||||
|
||||
// Show diff toolbar if page has Pass 1 corrections
|
||||
const corrForPage = state.corrections && state.corrections[pageNum];
|
||||
const hasPass1 = corrForPage && corrForPage.pass1;
|
||||
if (hasPass1) {
|
||||
showDiffToolbar(pageNum);
|
||||
} else {
|
||||
diffToolbar.style.display = 'none';
|
||||
state.diffMode = false;
|
||||
btnDiff.classList.remove('active');
|
||||
}
|
||||
|
||||
// Auto-enable diff in Pass 2 if Pass 1 corrections exist
|
||||
if (currentRole === 'pass2' && hasPass1 && !state.editMode) {
|
||||
state.diffMode = true;
|
||||
btnDiff.classList.add('active');
|
||||
applyDiffToDisplay(pageNum);
|
||||
}
|
||||
|
||||
Replace the existing Pass 2 auto-edit block at the bottom of showOCRPage():
|
||||
javascript
|
||||
|
||||
if (currentRole === 'pass2') {
|
||||
if (!state.editMode) enterEditMode();
|
||||
updatePass2NavButtons();
|
||||
}
|
||||
|
||||
With:
|
||||
javascript
|
||||
|
||||
if (currentRole === 'pass2') {
|
||||
updatePass2NavButtons();
|
||||
}
|
||||
|
||||
Note: Pass 2 no longer auto-enters edit mode on page load. The editor opens edit mode manually via the Edit button. This is intentional — the senior editor reviews the diff first, then decides to edit.
|
||||
|
||||
3j — Update enterEditMode()
|
||||
|
||||
At the end of enterEditMode(), after textEditor.focus(), add:
|
||||
javascript
|
||||
|
||||
applyDiffToReference(pageNum);
|
||||
|
||||
3k — Update exitEditMode()
|
||||
|
||||
At the start of exitEditMode(), before any existing logic, add:
|
||||
javascript
|
||||
|
||||
diffReference.style.display = 'none';
|
||||
|
||||
After the existing line textOutput.style.display = 'block';, add:
|
||||
javascript
|
||||
|
||||
if (state.diffMode) applyDiffToDisplay(pageNum);
|
||||
|
||||
3l — Update goToPage()
|
||||
|
||||
At the start of goToPage(), before if (state.editMode) exitEditMode();, add:
|
||||
javascript
|
||||
|
||||
state.diffMode = false;
|
||||
btnDiff.classList.remove('active');
|
||||
diffToolbar.style.display = 'none';
|
||||
diffReference.style.display = 'none';
|
||||
|
||||
This ensures diff state is always reset on navigation — prevents any possibility of page-bleed.
|
||||
|
||||
VERIFICATION CHECKLIST:
|
||||
|
||||
Pass 2 lands on a page with Pass 1 corrections: diff toolbar appears automatically, ⇄ Diff button is active, inline highlights visible
|
||||
Pass 2 lands on a page with no Pass 1 corrections: diff toolbar hidden, ⇄ Diff button inactive, clicking it shows status message
|
||||
Clicking ⇄ Diff toggles highlights on/off without changing page or losing position
|
||||
Clicking Edit while diff is on: reference pane appears above textarea showing highlighted diff, editor opens below
|
||||
Clicking Edit while diff is off: reference pane hidden, editor fills full height
|
||||
Toggling diff while in edit mode: reference pane appears/disappears above the open textarea
|
||||
Navigating to any new page: diff mode resets to off, no highlights carry over from previous page
|
||||
Pass 1 role: ⇄ Diff button present but only activates on pages with Pass 1 corrections
|
||||
Reference in New Issue
Block a user