YouTube click-to-play and watch-next
Document OCR review UI and docs
Line 1,472: Line 1,472:
if (!path) {
if (!path) {
return '';
return '';
}
var jobMatch = String(path).match(/\/api\/jobs\/([a-fA-F0-9]+)\/preview(?:\?|$)/);
var pageMatch = String(path).match(/\/api\/jobs\/([a-fA-F0-9]+)\/page\/(\d+)\/preview/);
var params;
if (pageMatch) {
params = {
ritchieocr_preview: '1',
job_id: pageMatch[1],
page: pageMatch[2]
};
return mw.util.wikiScript() + '?' + $.param(params);
}
if (jobMatch) {
params = {
ritchieocr_preview: '1',
job_id: jobMatch[1]
};
return mw.util.wikiScript() + '?' + $.param(params);
}
/* Legacy fallback if a full absolute URL was stored */
if (/^https?:\/\//i.test(path)) {
return path;
}
}
var base = (window.__ocrPublicUrl || '').replace(/\/$/, '');
var base = (window.__ocrPublicUrl || '').replace(/\/$/, '');
Line 1,482: Line 1,504:
function publicPreviewUrl(job) {
function publicPreviewUrl(job) {
return publicAssetUrl(job && job.preview_url);
return publicAssetUrl(job && job.preview_url);
}
function timeAgo(ts) {
var sec = Math.max(0, Math.floor(Date.now() / 1000 - Number(ts || 0)));
if (!ts || sec < 60) {
return 'just now';
}
if (sec < 3600) {
return Math.floor(sec / 60) + 'm ago';
}
if (sec < 86400) {
return Math.floor(sec / 3600) + 'h ago';
}
return Math.floor(sec / 86400) + 'd ago';
}
}


Line 1,570: Line 1,606:
}
}
if (fact.kind === 'note') {
if (fact.kind === 'note') {
return (fact.label || 'Note') + ': ' + (val.length > 90 ? val.slice(0, 90) + '…' : val);
return 'Add research note — ' + (fact.label || 'OCR note') + ': ' +
(val.length > 90 ? val.slice(0, 90) + '…' : val);
}
}
if (label.indexOf('team') !== -1 || label.indexOf('ipw') !== -1) {
if (label.indexOf('team') !== -1 || label.indexOf('ipw') !== -1) {
Line 1,619: Line 1,656:
}
}


function isWritableFact(fact) {
function isWritableFact(fact, applyNotes) {
return fact.kind === 'field' || fact.kind === 'bio';
if (!fact) {
return false;
}
if (fact.kind === 'field' || fact.kind === 'bio') {
return true;
}
return !!(applyNotes && fact.kind === 'note');
}
}


Line 2,169: Line 2,212:
$id.append(document.createTextNode(job.identity_warning));
$id.append(document.createTextNode(job.identity_warning));
$panel.append($id);
$panel.append($id);
}
if (mwApiBool(job.pdf_truncated)) {
$panel.append($('<div>', { 'class': 'ocr-identity-banner ocr-identity-warn' }).append(
$('<strong>').text('PDF truncated: '),
document.createTextNode(
'Only the first ' + (job.page_count || 8) + ' of ' +
(job.pdf_total_pages || '?') +
' pages were OCR’d. Split the PDF or re-upload the remaining pages.'
)
));
}
if (job.error) {
$panel.append($('<div>', { 'class': 'ocr-identity-banner ocr-identity-warn' }).text(job.error));
}
}


Line 2,214: Line 2,272:


function refreshPreview() {
function refreshPreview() {
var applyNotesOn = !!$panel.find('.ocr-apply-notes input').prop('checked');
var props = collectProposed();
var props = collectProposed();
var byPage = {};
var byPage = {};
props.forEach(function (p) {
props.forEach(function (p) {
if (!p.selected || !isWritableFact(p) || factAlreadyOnPage(p)) {
if (!p.selected || !isWritableFact(p, applyNotesOn) || factAlreadyOnPage(p)) {
return;
return;
}
}
Line 2,316: Line 2,375:
' Add document to Gallery on Apply' + (isMultiPersonJob(job) ? ' (off by default for multi-person)' : '')
' Add document to Gallery on Apply' + (isMultiPersonJob(job) ? ' (off by default for multi-person)' : '')
);
);
var $applyNotes = $('<label>', { 'class': 'ocr-apply-notes' }).append(
$('<input>', { type: 'checkbox', checked: mwApiBool(job.apply_notes) }),
' Also write selected notes into Research notes'
);
var $forceMulti = $('<label>', { 'class': 'ocr-force-multi' }).append(
$('<input>', { type: 'checkbox', checked: mwApiBool(job.force_multi) }),
' Treat as multi-person roster (rebuild required)'
);
if (isMultiPersonJob(job) && !job.bound_title) {
$forceMulti.hide();
}
var $forceId = $('<label>', { 'class': 'ocr-force-identity' }).append(
var $forceId = $('<label>', { 'class': 'ocr-force-identity' }).append(
$('<input>', { type: 'checkbox', checked: false }),
$('<input>', { type: 'checkbox', checked: false }),
Line 2,323: Line 2,393:
$forceId.hide();
$forceId.hide();
}
}
$advBody.append($overwrite, $('<br>'), $gallery, $('<br>'), $forceId);
$applyNotes.find('input').on('change', refreshPreview);
$advBody.append(
$overwrite, $('<br>'),
$gallery, $('<br>'),
$applyNotes, $('<br>'),
$forceMulti, $('<br>'),
$forceId
);


var $save = $('<button>', { type: 'button', text: 'Save draft' });
var $save = $('<button>', { type: 'button', text: 'Save draft' });
var $rebuild = $('<button>', { type: 'button', text: 'Rebuild from pages' });
var $rebuild = $('<button>', { type: 'button', text: 'Rebuild from pages / roster mode' });
if (pages.length < 2) {
if (pages.length < 2 && !job.bound_title) {
$rebuild.hide();
$rebuild.hide();
}
}
Line 2,355: Line 2,432:


function collectProposed() {
function collectProposed() {
var applyNotesOn = !!$applyNotes.find('input').prop('checked');
var out = [];
var out = [];
$list.find('.ocr-fact-row').each(function () {
$list.find('.ocr-fact-row').each(function () {
Line 2,370: Line 2,448:
var $cb = $row.find('input[type="checkbox"]').first();
var $cb = $row.find('input[type="checkbox"]').first();
base.selected = $cb.length ? !!$cb.prop('checked') : false;
base.selected = $cb.length ? !!$cb.prop('checked') : false;
if ($row.hasClass('ocr-fact-is-note') || ($row.hasClass('ocr-fact-note') && !factAlreadyOnPage(base) && base.kind === 'note')) {
if (base.kind === 'note' && !applyNotesOn) {
base.selected = false;
base.selected = false;
}
}
Line 2,408: Line 2,486:
doc_type: $docSelect.val(),
doc_type: $docSelect.val(),
gallery_caption: $.trim($caption.val() || ''),
gallery_caption: $.trim($caption.val() || ''),
force_identity: $forceId.find('input').prop('checked')
force_identity: $forceId.find('input').prop('checked'),
force_multi: $forceMulti.find('input').prop('checked'),
apply_notes: $applyNotes.find('input').prop('checked')
};
};
}
}
Line 2,469: Line 2,549:
}
}
var selected = collectProposed().filter(function (p) {
var selected = collectProposed().filter(function (p) {
return p.selected && isWritableFact(p) && (p.target_title || '').trim();
return p.selected && isWritableFact(p, $applyNotes.find('input').prop('checked')) && (p.target_title || '').trim();
});
});
if (!selected.length) {
if (!selected.length) {
$status.text('Select at least one change with a matched soldier.');
$status.text('Select at least one change with a matched soldier.');
return;
}
var nPages = {};
selected.forEach(function (p) { nPages[p.target_title] = 1; });
var pageCount = Object.keys(nPages).length;
if (pageCount > 5 && !window.confirm('Apply changes to ' + pageCount + ' soldier pages?')) {
return;
return;
}
}
Line 2,549: Line 2,635:
}
}


function startUpload($fileInput, boundTitle, $host, cfg) {
function startUpload($fileInput, boundTitle, $host, cfg, opts) {
opts = opts || {};
var file = $fileInput[0].files[0];
var file = $fileInput[0].files[0];
if (!file) {
if (!file) {
Line 2,560: Line 2,647:
op: 'upload',
op: 'upload',
bound_title: boundTitle || '',
bound_title: boundTitle || '',
force_multi: opts.forceMulti ? true : false,
file: file,
file: file,
format: 'json'
format: 'json'
Line 2,616: Line 2,704:
'class': 'soldier-upload-file'
'class': 'soldier-upload-file'
});
});
var $rosterMode = $('<label>', { 'class': 'ocr-force-multi-upload' }).append(
$('<input>', { type: 'checkbox' }),
' This is a multi-person roster (not only this soldier)'
);
var $btn = $('<button>', { type: 'button', text: 'Upload & OCR' });
var $btn = $('<button>', { type: 'button', text: 'Upload & OCR' });
var $host = $('<div>', { 'class': 'ocr-review-host' });
var $host = $('<div>', { 'class': 'ocr-review-host' });
$section.append($file, $btn, $host);
$section.append($file, $rosterMode, $btn, $host);
$btn.on('click', function () {
$btn.on('click', function () {
startUpload($file, pageTitle, $host, cfg);
startUpload($file, pageTitle, $host, cfg, {
forceMulti: $rosterMode.find('input').prop('checked')
});
});
});
var $slot = $('.soldier-ocr-slot').first();
var $slot = $('.soldier-ocr-slot').first();
Line 2,658: Line 2,752:
'Resume ready jobs, or reopen a profile upload that finished after you left the page.'
'Resume ready jobs, or reopen a profile upload that finished after you left the page.'
));
));
var me = mw.config.get('wgUserName') || '';
var $filters = $('<div>', { 'class': 'ocr-queue-filters' });
var $filterMine = $('<label>').append(
$('<input>', { type: 'checkbox', checked: false, 'class': 'ocr-queue-mine' }),
' Only mine'
);
var $filterReady = $('<label>').append(
$('<input>', { type: 'checkbox', checked: true, 'class': 'ocr-queue-ready' }),
' Ready'
);
var $filterProc = $('<label>').append(
$('<input>', { type: 'checkbox', checked: true, 'class': 'ocr-queue-processing' }),
' In progress'
);
$filters.append($filterMine, ' ', $filterReady, ' ', $filterProc);
var $ul = $('<ul>', { 'class': 'ocr-queue-list' });
var $ul = $('<ul>', { 'class': 'ocr-queue-list' });
$queue.append($ul);
$queue.append($filters, $ul);
$slot.append($queue);
$slot.append($queue);


Line 2,672: Line 2,781:
}).then(function (bags) {
}).then(function (bags) {
$ul.empty();
$ul.empty();
var jobs = [].concat(bags.processing || [], bags.ready || []);
var showMine = $filterMine.find('input').prop('checked');
var showReady = $filterReady.find('input').prop('checked');
var showProc = $filterProc.find('input').prop('checked');
var jobs = [];
if (showProc) {
jobs = jobs.concat(bags.processing || []);
}
if (showReady) {
jobs = jobs.concat(bags.ready || []);
}
if (showMine) {
jobs = jobs.filter(function (j) {
return (j.uploader || '') === me;
});
}
if (!jobs.length) {
if (!jobs.length) {
$ul.append($('<li>').text('No ready or in-progress jobs.'));
$ul.append($('<li>').text('No jobs match these filters.'));
return;
return;
}
}
Line 2,693: Line 2,816:
? ((ps.detected || 0) + ' people · ' + (ps.matched || 0) + ' matched · ' + (ps.unresolved || 0) + ' unmatched')
? ((ps.detected || 0) + ' people · ' + (ps.matched || 0) + ' matched · ' + (ps.unresolved || 0) + ' unmatched')
: (job.bound_title ? job.bound_title : 'single document');
: (job.bound_title ? job.bound_title : 'single document');
var mineBit = (job.uploader || '') === me ? ' · you' : '';
$meta.append($('<div>').append(
$meta.append($('<div>').append(
$('<strong>').text(job.filename || job.id),
$('<strong>').text(job.filename || job.id),
Line 2,700: Line 2,824:
peopleBit + ' · ' + (job.status || '?') +
peopleBit + ' · ' + (job.status || '?') +
(job.status === 'processing' ? (' ' + (job.progress || 0) + '%') : '') +
(job.status === 'processing' ? (' ' + (job.progress || 0) + '%') : '') +
' · by ' + (job.uploader || '?')
' · by ' + (job.uploader || '?') + mineBit +
' · ' + timeAgo(job.created_at)
));
));
if (job.apply_progress && job.apply_progress.length) {
$meta.append($('<div>', { 'class': 'ocr-fact-meta' }).text(
'Partial apply: ' + job.apply_progress.length + ' page(s) already saved — Open and Apply to resume'
));
}
var $open = $('<button>', { type: 'button', text: 'Open' });
var $open = $('<button>', { type: 'button', text: 'Open' });
$meta.append($open);
$meta.append($open);
Line 2,721: Line 2,851:
});
});
}
}
$filters.find('input').on('change', loadQueue);
loadQueue();
loadQueue();
}
}