rafmacalaba commited on
Commit
a4dc408
·
verified ·
1 Parent(s): f46a411

optional freeform note per ruling

Browse files
Files changed (1) hide show
  1. review.html +18 -1
review.html CHANGED
@@ -102,6 +102,8 @@ footer .sp { flex: 1; }
102
  <button class="drop" id="bDrop" onclick="rule('NON_MENTION')">drop <kbd>d</kbd></button>
103
  <button class="nav" onclick="move(1)">next →</button>
104
  </div>
 
 
105
  <div id="mine"></div>
106
  <footer>
107
  <span><kbd>←</kbd><kbd>→</kbd> prev/next mention · <kbd>k</kbd> keep · <kbd>d</kbd> drop · <kbd>space</kbd> skip · <kbd>[</kbd><kbd>]</kbd> slide · <kbd>-</kbd><kbd>+</kbd> size · <kbd>e</kbd> export</span>
@@ -148,6 +150,8 @@ const USERS = ['paddy', 'aj', 'aivin', 'rafael'];
148
  const LS = 'review_rulings';
149
  let R = JSON.parse(localStorage.getItem(LS) || '{}');
150
  let RQ = JSON.parse(localStorage.getItem('review_queues') || '{}');
 
 
151
  const $ = id => document.getElementById(id);
152
  const esc = s => String(s).replace(/[&<>]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;'}[c]));
153
  const QD = document.documentElement;
@@ -171,6 +175,12 @@ function loadWho() {
171
  const w = who().toLowerCase();
172
  if (USERS.includes(w) && $('ds').value !== w) { $('ds').value = w; loadDs(); }
173
  });
 
 
 
 
 
 
174
  const ht = $('hubtok');
175
  if (ht) { ht.value = localStorage.getItem('review_hubtok') || ''; ht.addEventListener('input', () => localStorage.setItem('review_hubtok', ht.value.trim())); }
176
  if (localStorage.getItem('review_locked') === '1' && who()) setLocked(true);
@@ -233,6 +243,7 @@ function render() {
233
  document.querySelector('#bar > div').style.width = (100 * (i + 1) / ITEMS.length) + '%';
234
  $('key').textContent = sp.key;
235
  $('surface').textContent = '“' + sp.surface + '”';
 
236
  // sliding assessment window around the active span
237
  if (needCenter || wc === null) { wc = (sp.start + sp.end) / 2; needCenter = false; }
238
  const W = WSIZES[wi], L = o.text.length;
@@ -351,6 +362,7 @@ function light() {
351
  $('prog').textContent = `${i + 1}/${ITEMS.length} · span ${s + 1}/${o.spans.length}`;
352
  $('key').textContent = sp.key;
353
  $('surface').textContent = '“' + sp.surface + '”';
 
354
  document.querySelectorAll('#passage mark').forEach(m => {
355
  m.classList.toggle('active', m.dataset.k === sp.key);
356
  m.classList.toggle('done-k', R[m.dataset.k] === 'DATA_MENTION');
@@ -384,7 +396,9 @@ async function rule(v) {
384
  localStorage.setItem('review_queues', JSON.stringify(RQ));
385
  setLocked(true);
386
  paintGate();
387
- const entry = {key: k, ruling: v, queue: $('ds').value, annotator: who()};
 
 
388
  let saved = 'this browser only';
389
  try {
390
  const r = await fetch('/api/rulings', {method: 'POST', headers: {'Content-Type': 'application/json'},
@@ -448,6 +462,7 @@ function exp() {
448
  const rows = Object.entries(R).map(([key, ruling]) => {
449
  const hit = idx[key];
450
  const base = {key, ruling, queue: RQ[key] || $('ds').value, annotator: who(),
 
451
  ts: new Date().toISOString().replace(/\.\d+Z$/, '+00:00')};
452
  if (!hit) return base;
453
  const {o, x} = hit;
@@ -474,11 +489,13 @@ document.getElementById('impFile').addEventListener('change', async e => {
474
  if (!it.key || !['DATA_MENTION', 'NON_MENTION'].includes(it.ruling)) continue;
475
  R[it.key] = it.ruling;
476
  if (it.queue) RQ[it.key] = it.queue;
 
477
  n++;
478
  }
479
  }
480
  localStorage.setItem(LS, JSON.stringify(R));
481
  localStorage.setItem('review_queues', JSON.stringify(RQ));
 
482
  // jump to first unruled span
483
  let done = false;
484
  for (let ni = 0; ni < ITEMS.length && !done; ni++)
 
102
  <button class="drop" id="bDrop" onclick="rule('NON_MENTION')">drop <kbd>d</kbd></button>
103
  <button class="nav" onclick="move(1)">next →</button>
104
  </div>
105
+ <input id="note" placeholder="note (optional, saved with your ruling)" autocomplete="off"
106
+ style="width:100%;margin-top:10px;font:inherit;color:var(--fg);background:var(--bg);border:1px solid var(--line);border-radius:8px;padding:8px 12px">
107
  <div id="mine"></div>
108
  <footer>
109
  <span><kbd>←</kbd><kbd>→</kbd> prev/next mention · <kbd>k</kbd> keep · <kbd>d</kbd> drop · <kbd>space</kbd> skip · <kbd>[</kbd><kbd>]</kbd> slide · <kbd>-</kbd><kbd>+</kbd> size · <kbd>e</kbd> export</span>
 
150
  const LS = 'review_rulings';
151
  let R = JSON.parse(localStorage.getItem(LS) || '{}');
152
  let RQ = JSON.parse(localStorage.getItem('review_queues') || '{}');
153
+ let RN = JSON.parse(localStorage.getItem('review_notes') || '{}');
154
+ function saveNotes() { localStorage.setItem('review_notes', JSON.stringify(RN)); }
155
  const $ = id => document.getElementById(id);
156
  const esc = s => String(s).replace(/[&<>]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;'}[c]));
157
  const QD = document.documentElement;
 
175
  const w = who().toLowerCase();
176
  if (USERS.includes(w) && $('ds').value !== w) { $('ds').value = w; loadDs(); }
177
  });
178
+ $('note').addEventListener('input', () => {
179
+ if (!ITEMS.length) return;
180
+ const v = $('note').value;
181
+ if (v) RN[span().key] = v; else delete RN[span().key];
182
+ saveNotes();
183
+ });
184
  const ht = $('hubtok');
185
  if (ht) { ht.value = localStorage.getItem('review_hubtok') || ''; ht.addEventListener('input', () => localStorage.setItem('review_hubtok', ht.value.trim())); }
186
  if (localStorage.getItem('review_locked') === '1' && who()) setLocked(true);
 
243
  document.querySelector('#bar > div').style.width = (100 * (i + 1) / ITEMS.length) + '%';
244
  $('key').textContent = sp.key;
245
  $('surface').textContent = '“' + sp.surface + '”';
246
+ if (document.activeElement !== $('note')) $('note').value = RN[sp.key] || '';
247
  // sliding assessment window around the active span
248
  if (needCenter || wc === null) { wc = (sp.start + sp.end) / 2; needCenter = false; }
249
  const W = WSIZES[wi], L = o.text.length;
 
362
  $('prog').textContent = `${i + 1}/${ITEMS.length} · span ${s + 1}/${o.spans.length}`;
363
  $('key').textContent = sp.key;
364
  $('surface').textContent = '“' + sp.surface + '”';
365
+ if (document.activeElement !== $('note')) $('note').value = RN[sp.key] || '';
366
  document.querySelectorAll('#passage mark').forEach(m => {
367
  m.classList.toggle('active', m.dataset.k === sp.key);
368
  m.classList.toggle('done-k', R[m.dataset.k] === 'DATA_MENTION');
 
396
  localStorage.setItem('review_queues', JSON.stringify(RQ));
397
  setLocked(true);
398
  paintGate();
399
+ const note = ($('note').value || '').trim();
400
+ if (note) { RN[k] = note; saveNotes(); }
401
+ const entry = {key: k, ruling: v, queue: $('ds').value, annotator: who(), note: note || undefined};
402
  let saved = 'this browser only';
403
  try {
404
  const r = await fetch('/api/rulings', {method: 'POST', headers: {'Content-Type': 'application/json'},
 
462
  const rows = Object.entries(R).map(([key, ruling]) => {
463
  const hit = idx[key];
464
  const base = {key, ruling, queue: RQ[key] || $('ds').value, annotator: who(),
465
+ note: RN[key] || undefined,
466
  ts: new Date().toISOString().replace(/\.\d+Z$/, '+00:00')};
467
  if (!hit) return base;
468
  const {o, x} = hit;
 
489
  if (!it.key || !['DATA_MENTION', 'NON_MENTION'].includes(it.ruling)) continue;
490
  R[it.key] = it.ruling;
491
  if (it.queue) RQ[it.key] = it.queue;
492
+ if (it.note) RN[it.key] = it.note;
493
  n++;
494
  }
495
  }
496
  localStorage.setItem(LS, JSON.stringify(R));
497
  localStorage.setItem('review_queues', JSON.stringify(RQ));
498
+ saveNotes();
499
  // jump to first unruled span
500
  let done = false;
501
  for (let ni = 0; ni < ITEMS.length && !done; ni++)