@bongos/core 1.21.166 → 1.21.168
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.bongos-core.json +153 -73
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +8 -0
- package/clients/bongos-client/index.cjs +8 -0
- package/clients/bongos-client/index.d.ts +13 -0
- package/clients/bongos-client/index.mjs +8 -0
- package/docs/api/openapi.json +306 -3
- package/docs/api-reference.md +7 -3
- package/docs/copy-inventory.md +21 -20
- package/docs/copy-registry.json +34 -25
- package/docs/module-api-changelog.md +4 -0
- package/docs/modules-contract.md +1 -0
- package/docs/page-inventory.json +3 -0
- package/docs/page-readings.json +445 -445
- package/modules/autonomy/governor-docket.js +10 -5
- package/modules/builder-settings/builder-needs.js +29 -3
- package/modules/copy-desk/guarded-lines.js +89 -0
- package/modules/copy-desk/guarded-lines.json +14 -0
- package/modules/copy-desk/module.json +2 -2
- package/modules/copy-desk/page-status.js +77 -7
- package/modules/copy-desk/pages.js +4 -0
- package/modules/copy-desk/pollers/stuck-page-digest.js +34 -0
- package/modules/copy-desk/routes/copy-desk.js +8 -0
- package/modules/copy-desk/stuck-tweaks.js +79 -0
- package/modules/copy-desk/tests/copy_no_cms.mjs +6 -1
- package/modules/copy-desk/tests/copy_page_status.mjs +2 -2
- package/modules/government/asks-db.js +38 -2
- package/modules/government/catalog.js +1 -1
- package/modules/government/docket.js +20 -2
- package/modules/government/migrations/government_032_ask_flags.sql +35 -0
- package/modules/government/routes/government.js +43 -1
- package/modules/hall-ui/public/approval-queue-lib.js +4 -0
- package/modules/hall-ui/public/approval-queue.js +1 -0
- package/modules/hall-ui/public/brand-apply.js +3 -0
- package/modules/hall-ui/public/city-decide.js +642 -0
- package/modules/hall-ui/public/city-draw.js +14 -3
- package/modules/hall-ui/public/city-switchboard.js +141 -0
- package/modules/hall-ui/public/city.css +176 -10
- package/modules/hall-ui/public/city.html +8 -3
- package/modules/hall-ui/public/city.js +185 -38
- package/modules/hall-ui/public/sky-data.js +4 -1
- package/modules/hall-ui/public/tweak-editor-lib.js +21 -2
- package/modules/hall-ui/public/tweak-editor.css +2 -0
- package/modules/hall-ui/public/tweak-editor.js +3 -1
- package/modules/lifecycle/db-claims.js +20 -5
- package/modules/lifecycle/rebase-gate.js +78 -0
- package/modules/lifecycle/routes/claims.js +10 -2
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +28 -0
- package/scripts/gds/ask.js +68 -1
- package/scripts/gds/claim.js +6 -1
- package/scripts/gds/copy-apply.js +2 -0
- package/src/bongos/module-promotion.js +219 -0
- package/src/bongos/route-rank-check.js +7 -0
- package/src/bongos/routes/me.js +11 -0
- package/src/bongos/routes/modules.js +46 -0
- package/src/module-api.js +1 -1
- package/src/platform-server.js +5 -0
- package/tests/claim_gate_ci_unblock.mjs +4 -0
- package/tests/claim_gate_rebase.mjs +56 -3
- package/tests/copy_desk_page_editor.mjs +1 -1
- package/tests/copy_desk_page_submit.mjs +42 -0
- package/tests/copy_guarded_lines.mjs +54 -0
- package/tests/copy_stuck_page_tweaks.mjs +118 -0
- package/tests/government_ask_flags.mjs +80 -0
- package/tests/government_seed.mjs +1 -0
- package/tests/government_treasury.mjs +1 -0
- package/tests/governor_docket.mjs +1 -1
- package/tests/hall_approval_queue.mjs +15 -0
- package/tests/hall_city.mjs +5 -4
- package/tests/hall_city_living.mjs +118 -12
- package/tests/hall_city_rewording.mjs +51 -0
- package/tests/hall_city_switchboard.mjs +63 -0
- package/tests/hall_gov_real_site.mjs +2 -2
- package/tests/hall_sky.mjs +18 -0
- package/tests/hall_street_deck.mjs +6 -4
- package/tests/hall_street_depth.mjs +1 -1
- package/tests/hall_street_desk.mjs +5 -3
- package/tests/hall_tweak_editor.mjs +26 -0
- package/tests/module_promotion.mjs +290 -0
- package/tests/rebase_gate_set_aside.mjs +73 -0
- package/tests/reward_mode_surfacing.mjs +12 -0
|
@@ -0,0 +1,642 @@
|
|
|
1
|
+
// modules/hall-ui/public/city-decide.js — THE CITY'S DECIDE VIEW: every ask
|
|
2
|
+
// that waits, worked in place over the city instead of on the street's deck.
|
|
3
|
+
//
|
|
4
|
+
// Two views of one queue (GovDeck.queueOf, needs-you first, then the oldest):
|
|
5
|
+
// one by one the deck's card (GovDeck.drawAsk) made answerable from its desk
|
|
6
|
+
// row; Tab / ↑ ↓ walk one building's items, ← / → the buildings,
|
|
7
|
+
// 1-9 answer, S skips, the way artist review walks its pages.
|
|
8
|
+
// table one row per ask, its facts in columns, a box per row; the bulk
|
|
9
|
+
// bar answers every ticked row at once with an answer they all
|
|
10
|
+
// offer, or each with its own recommended pick.
|
|
11
|
+
// Every answer goes through GovDeck.sendAnswer, the same request a desk row
|
|
12
|
+
// sends; nothing here learns a route. No innerHTML: every string lands as text.
|
|
13
|
+
(function () {
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
16
|
+
// ---------- every card answerable in place ----------
|
|
17
|
+
// A matter with no stored ask used to say "answer it on its desk". Its desk
|
|
18
|
+
// row (GovDesk.cardFor, cv.card.row) already holds the acts the server handed
|
|
19
|
+
// this viewer, with their chips and typed fields: they become the card's own
|
|
20
|
+
// answers here, each planned exactly as the deck plans a stored option (the
|
|
21
|
+
// row's own card and act key), so GovDeck.sendAnswer sends the desk's request.
|
|
22
|
+
var QUIET = { hold: 1, abstain: 1 };
|
|
23
|
+
function str(v) { return v == null ? '' : String(v); }
|
|
24
|
+
function answerable(cv) {
|
|
25
|
+
if (!cv) return cv;
|
|
26
|
+
var out = {};
|
|
27
|
+
for (var k in cv) out[k] = cv[k];
|
|
28
|
+
// the glow says it is yours: the "only you can answer" line is left off
|
|
29
|
+
out.foot = { you: '', rest: (cv.foot || {}).rest || '' };
|
|
30
|
+
out.fields = [];
|
|
31
|
+
var card = cv.card || null;
|
|
32
|
+
var row = card && card.row;
|
|
33
|
+
if (cv.options && cv.options.length) return out;
|
|
34
|
+
if (!row || !Array.isArray(row.actions) || !row.actions.length) return out;
|
|
35
|
+
var opts = [];
|
|
36
|
+
row.actions.forEach(function (a) {
|
|
37
|
+
if (a.go) return; // a link out is not an answer
|
|
38
|
+
var choice = (row.choices || {})[a.key] || null;
|
|
39
|
+
if (choice && choice.file) return; // a sign-off with a recording stays on its desk
|
|
40
|
+
if (choice && choice.field) {
|
|
41
|
+
var f = choice.field;
|
|
42
|
+
out.fields.push({ key: a.key, label: str(a.label), ask: str(choice.ask), placeholder: f && typeof f === 'object' && f.placeholder ? str(f.placeholder) : str(choice.ask || a.label), value: str(choice.value), card: row.card });
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
var plan = function (input) { return QUIET[a.key] ? { mode: 'note' } : { mode: 'send', key: a.key, input: input || {}, card: row.card }; };
|
|
46
|
+
if (choice && choice.options && choice.options.length) {
|
|
47
|
+
var bare = a.key === 'decide' || a.key === 'answer';
|
|
48
|
+
choice.options.forEach(function (o) {
|
|
49
|
+
opts.push({ key: a.key + ':' + str(o.key), label: bare ? str(o.label) : str(a.label) + ': ' + str(o.label), result: '', act: a.key, rec: false, plan: plan(o.input), off: false, why: '' });
|
|
50
|
+
});
|
|
51
|
+
} else {
|
|
52
|
+
opts.push({ key: a.key, label: str(a.label), result: '', act: a.key, rec: false, plan: plan({}), off: false, why: '' });
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
out.options = opts.map(function (o, i) { o.n = i + 1; return o; });
|
|
56
|
+
if (!opts.length && !out.fields.length) return out; // only a recording answers it: keep its desk link
|
|
57
|
+
// what the desk row says about it, so the answer is made with it in front of you
|
|
58
|
+
var said = [card.asked, card.fact, card.result].map(str).filter(Boolean);
|
|
59
|
+
if (!cv.said && said.length) out.said = said.join(' ');
|
|
60
|
+
out.desk = null;
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
// ---------- open it in Claude (the crab) ----------
|
|
64
|
+
// The matter as a prompt a person can paste into Claude to think it through:
|
|
65
|
+
// copied to the clipboard and Claude opened in a new tab. Nothing rides the
|
|
66
|
+
// URL; the words go only where the person pastes them.
|
|
67
|
+
var CLAUDE_URL = 'https://claude.ai/new';
|
|
68
|
+
function promptOf(cv) {
|
|
69
|
+
var lines = ['I need to make a decision in my project and want help understanding it.', ''];
|
|
70
|
+
lines.push('Where: the ' + str(cv.buildingName) + ' building' + (cv.waits ? ' (' + str(cv.waits) + ')' : '') + '.');
|
|
71
|
+
lines.push('The question: ' + str(cv.question));
|
|
72
|
+
if (cv.said) lines.push('What it says: ' + str(cv.said));
|
|
73
|
+
var card = cv.card || {};
|
|
74
|
+
var more = card.more && card.more.text ? str(card.more.text) : str(card.fact || '');
|
|
75
|
+
if (more && more !== cv.said) lines.push('Detail: ' + more.slice(0, 1500));
|
|
76
|
+
if (cv.options && cv.options.length) {
|
|
77
|
+
lines.push('', 'The answers I can give:');
|
|
78
|
+
cv.options.forEach(function (o) { lines.push('- ' + str(o.label) + (o.result ? ': ' + str(o.result) : '')); });
|
|
79
|
+
}
|
|
80
|
+
var f = cv.foot || {};
|
|
81
|
+
if (f.rest) lines.push('', str(f.rest));
|
|
82
|
+
lines.push('', 'Explain in plain words what this is about, what each answer would lead to, and which you would pick and why.');
|
|
83
|
+
return lines.join('\n');
|
|
84
|
+
}
|
|
85
|
+
var CRAB = [
|
|
86
|
+
['ellipse', { cx: 12, cy: 14, rx: 6, ry: 4 }],
|
|
87
|
+
['circle', { cx: 4, cy: 7, r: 2.6 }],
|
|
88
|
+
['circle', { cx: 20, cy: 7, r: 2.6 }],
|
|
89
|
+
];
|
|
90
|
+
var CRAB_LINES = 'M7 11 L4.5 9 M17 11 L19.5 9 M7 16 L3 18 M6.5 14.5 L2.5 15 M17 16 L21 18 M17.5 14.5 L21.5 15 M10 10.5 L10 8.5 M14 10.5 L14 8.5';
|
|
91
|
+
function crabButton(doc, cv, onSaid) {
|
|
92
|
+
var b = doc.createElement('button');
|
|
93
|
+
b.className = 'ask-crab';
|
|
94
|
+
b.setAttribute('type', 'button');
|
|
95
|
+
b.setAttribute('title', 'Open in Claude: copies this question so you can paste it into Claude and talk it through');
|
|
96
|
+
b.setAttribute('aria-label', 'Open in Claude');
|
|
97
|
+
var NS = 'http://www.w3.org/2000/svg';
|
|
98
|
+
if (doc.createElementNS) {
|
|
99
|
+
var svg = doc.createElementNS(NS, 'svg');
|
|
100
|
+
svg.setAttribute('viewBox', '0 0 24 24');
|
|
101
|
+
svg.setAttribute('width', '18');
|
|
102
|
+
svg.setAttribute('height', '18');
|
|
103
|
+
svg.setAttribute('aria-hidden', 'true');
|
|
104
|
+
CRAB.forEach(function (s) { var n = doc.createElementNS(NS, s[0]); Object.keys(s[1]).forEach(function (k) { n.setAttribute(k, String(s[1][k])); }); n.setAttribute('fill', 'currentColor'); svg.appendChild(n); });
|
|
105
|
+
var p = doc.createElementNS(NS, 'path');
|
|
106
|
+
p.setAttribute('d', CRAB_LINES);
|
|
107
|
+
p.setAttribute('stroke', 'currentColor');
|
|
108
|
+
p.setAttribute('stroke-width', '1.6');
|
|
109
|
+
p.setAttribute('stroke-linecap', 'round');
|
|
110
|
+
p.setAttribute('fill', 'none');
|
|
111
|
+
svg.appendChild(p);
|
|
112
|
+
b.appendChild(svg);
|
|
113
|
+
}
|
|
114
|
+
b.addEventListener('click', function (e) {
|
|
115
|
+
if (e && e.stopPropagation) e.stopPropagation();
|
|
116
|
+
var text = promptOf(cv);
|
|
117
|
+
var w = typeof window !== 'undefined' ? window : null;
|
|
118
|
+
var copied = w && w.navigator && w.navigator.clipboard && w.navigator.clipboard.writeText ? w.navigator.clipboard.writeText(text).then(function () { return true; }, function () { return false; }) : Promise.resolve(false);
|
|
119
|
+
if (w && w.open) w.open(CLAUDE_URL, '_blank', 'noopener');
|
|
120
|
+
copied.then(function (ok) { if (onSaid) onSaid(ok ? 'Copied. Paste it into Claude to talk it through.' : 'Claude is open; copying did not work here, so select the question and copy it yourself.'); });
|
|
121
|
+
});
|
|
122
|
+
return b;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ---------- "?": I don't understand this (the reword list) ----------
|
|
126
|
+
// One press puts the matter on the reword list (POST /government/asks/:id/
|
|
127
|
+
// unclear); an AI session rewrites it into the standard short card, and that
|
|
128
|
+
// closes the flag. Flagged already (the docket's `unclear`, or pressed this
|
|
129
|
+
// visit) shows filled, and pressing again only says so.
|
|
130
|
+
var FLAGGED = {};
|
|
131
|
+
// Every matter waiting on a rewrite: flagged this visit, or flagged before
|
|
132
|
+
// (the docket's `unclear`). They leave the to-do while Claude rewrites them,
|
|
133
|
+
// and are counted apart as "Rewording" (owner, 2026-10-23).
|
|
134
|
+
function rewordingIds(docket) {
|
|
135
|
+
var out = {};
|
|
136
|
+
Object.keys(FLAGGED).forEach(function (id) { out[id] = true; });
|
|
137
|
+
var ms = docket && Array.isArray(docket.matters) ? docket.matters : [];
|
|
138
|
+
ms.forEach(function (m) { if (m && m.unclear) out[m.id] = true; });
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
// How many are being reworded (at one building, or everywhere).
|
|
142
|
+
function rewordingCount(docket, building) {
|
|
143
|
+
var ids = rewordingIds(docket);
|
|
144
|
+
var ms = docket && Array.isArray(docket.matters) ? docket.matters : [];
|
|
145
|
+
var at = {};
|
|
146
|
+
ms.forEach(function (m) { if (m) at[m.id] = m.building; });
|
|
147
|
+
return Object.keys(ids).filter(function (id) { return !building || !at[id] || at[id] === building; }).length;
|
|
148
|
+
}
|
|
149
|
+
// The queue's session with the reworded matters left out, as if answered.
|
|
150
|
+
function withoutReworded(done, docket) {
|
|
151
|
+
var out = {};
|
|
152
|
+
Object.keys(done || {}).forEach(function (k) { out[k] = done[k]; });
|
|
153
|
+
Object.keys(rewordingIds(docket)).forEach(function (k) { out[k] = true; });
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
156
|
+
// The "?": first it says what will happen and asks to confirm, with an
|
|
157
|
+
// optional line on what was confusing; only the confirm sends the flag.
|
|
158
|
+
function unclearButton(doc, cv, on) {
|
|
159
|
+
var b = doc.createElement('button');
|
|
160
|
+
b.className = 'ask-unclear';
|
|
161
|
+
b.setAttribute('type', 'button');
|
|
162
|
+
b.textContent = '?';
|
|
163
|
+
b.setAttribute('aria-expanded', 'false');
|
|
164
|
+
b.setAttribute('title', 'Don\'t understand this? Ask Claude to rewrite it in plain words');
|
|
165
|
+
b.setAttribute('aria-label', 'I don\'t understand this: ask Claude to rewrite it');
|
|
166
|
+
b.addEventListener('click', function (e) {
|
|
167
|
+
if (e && e.stopPropagation) e.stopPropagation();
|
|
168
|
+
var art = b.closest ? b.closest('[data-ask]') : null;
|
|
169
|
+
if (!art) return;
|
|
170
|
+
var open = art.querySelector('.ask-reword');
|
|
171
|
+
if (open) { open.parentNode.removeChild(open); b.setAttribute('aria-expanded', 'false'); return; }
|
|
172
|
+
b.setAttribute('aria-expanded', 'true');
|
|
173
|
+
var box = doc.createElement('div');
|
|
174
|
+
box.className = 'ask-reword';
|
|
175
|
+
box.setAttribute('role', 'group');
|
|
176
|
+
box.setAttribute('aria-label', 'Send this for a rewrite');
|
|
177
|
+
var h = function (tag, cls, text) { var n = doc.createElement(tag); if (cls) n.className = cls; if (text != null) n.textContent = text; return n; };
|
|
178
|
+
box.appendChild(h('p', 'ask-reword__title', 'Send this to Claude to rewrite?'));
|
|
179
|
+
var list = h('ul', 'ask-reword__list');
|
|
180
|
+
['Claude rewrites it as one short question with clear answers.',
|
|
181
|
+
'It leaves your to-do and counts under "Rewording" until the new words are ready.',
|
|
182
|
+
'Nothing is decided or changed; it comes back to you to answer.'].forEach(function (t) { list.appendChild(h('li', null, t)); });
|
|
183
|
+
box.appendChild(list);
|
|
184
|
+
var note = h('input', 'ask-reword__note');
|
|
185
|
+
note.setAttribute('type', 'text');
|
|
186
|
+
note.setAttribute('maxlength', '500');
|
|
187
|
+
note.setAttribute('placeholder', 'What was confusing? (optional)');
|
|
188
|
+
note.setAttribute('aria-label', 'What was confusing (optional)');
|
|
189
|
+
box.appendChild(note);
|
|
190
|
+
var row = h('div', 'ask-reword__acts');
|
|
191
|
+
var go = h('button', 'ask-reword__go', 'Send for rewrite');
|
|
192
|
+
go.setAttribute('type', 'button');
|
|
193
|
+
var no = h('button', 'ask-reword__no', 'Cancel');
|
|
194
|
+
no.setAttribute('type', 'button');
|
|
195
|
+
no.addEventListener('click', function (ev) { if (ev && ev.stopPropagation) ev.stopPropagation(); box.parentNode.removeChild(box); b.setAttribute('aria-expanded', 'false'); if (b.focus) b.focus(); });
|
|
196
|
+
go.addEventListener('click', function (ev) {
|
|
197
|
+
if (ev && ev.stopPropagation) ev.stopPropagation();
|
|
198
|
+
go.setAttribute('disabled', '');
|
|
199
|
+
var text = String(note.value || '').trim();
|
|
200
|
+
on.send({ method: 'POST', url: '/api/bongos/government/asks/' + encodeURIComponent(cv.id) + '/unclear', body: text ? { note: text } : {} }).then(function (res) {
|
|
201
|
+
var preview = res && res.status === 501 && res.body && res.body.error && res.body.error.code === 'preview_readonly';
|
|
202
|
+
if (res && (res.ok || preview)) {
|
|
203
|
+
FLAGGED[cv.id] = true;
|
|
204
|
+
var msg = preview ? 'Sent for a rewrite. This preview stores nothing.' : 'Sent for a rewrite. It is back on your list once the new words are ready.';
|
|
205
|
+
if (on.reworded) on.reworded(msg); else if (on.said) on.said(msg);
|
|
206
|
+
} else {
|
|
207
|
+
go.removeAttribute('disabled');
|
|
208
|
+
var C = window.GovMatterCard;
|
|
209
|
+
if (on.said) on.said(C && C.refusal ? C.refusal(res && res.status, res && res.body) : 'That could not be sent just now.');
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
row.appendChild(go);
|
|
214
|
+
row.appendChild(no);
|
|
215
|
+
box.appendChild(row);
|
|
216
|
+
var line = b.parentNode;
|
|
217
|
+
if (line && line.parentNode && typeof line.parentNode.insertBefore === 'function') line.parentNode.insertBefore(box, line.nextSibling);
|
|
218
|
+
else art.appendChild(box);
|
|
219
|
+
if (go.focus) go.focus();
|
|
220
|
+
});
|
|
221
|
+
return b;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// The deck's card, answerable; a typed answer gets its own box and send.
|
|
225
|
+
function drawCard(doc, K, cv, on, o) {
|
|
226
|
+
var shown = answerable(cv);
|
|
227
|
+
var art = K.drawAsk(doc, shown, on, o);
|
|
228
|
+
// the crab: open this one in Claude to help decide it
|
|
229
|
+
var q = art.querySelector ? art.querySelector('.ask__q') : null;
|
|
230
|
+
if (q && q.parentNode && typeof q.parentNode.insertBefore === 'function') {
|
|
231
|
+
var line = doc.createElement('div');
|
|
232
|
+
line.className = 'ask-qline';
|
|
233
|
+
q.parentNode.insertBefore(line, q);
|
|
234
|
+
line.appendChild(q);
|
|
235
|
+
line.appendChild(crabButton(doc, shown, on.said));
|
|
236
|
+
if (on.send) line.appendChild(unclearButton(doc, shown, on));
|
|
237
|
+
} else {
|
|
238
|
+
art.appendChild(crabButton(doc, shown, on.said));
|
|
239
|
+
if (on.send) art.appendChild(unclearButton(doc, shown, on));
|
|
240
|
+
}
|
|
241
|
+
shown.fields.forEach(function (f) {
|
|
242
|
+
var form = doc.createElement('form');
|
|
243
|
+
form.className = 'ask-field';
|
|
244
|
+
var input = doc.createElement('input');
|
|
245
|
+
input.className = 'ask-field__input';
|
|
246
|
+
input.setAttribute('type', 'text');
|
|
247
|
+
input.setAttribute('aria-label', f.label);
|
|
248
|
+
input.setAttribute('placeholder', f.placeholder);
|
|
249
|
+
if (f.value) input.value = f.value;
|
|
250
|
+
if (o && o.busy) input.setAttribute('disabled', '');
|
|
251
|
+
var go = doc.createElement('button');
|
|
252
|
+
go.className = 'ask-field__send';
|
|
253
|
+
go.setAttribute('type', 'submit');
|
|
254
|
+
go.textContent = f.label;
|
|
255
|
+
if (o && o.busy) go.setAttribute('disabled', '');
|
|
256
|
+
form.appendChild(input);
|
|
257
|
+
form.appendChild(go);
|
|
258
|
+
form.addEventListener('submit', function (e) {
|
|
259
|
+
if (e && e.preventDefault) e.preventDefault();
|
|
260
|
+
var text = str(input.value).trim();
|
|
261
|
+
if (!text || !on.pick) return;
|
|
262
|
+
on.pick({ key: f.key, label: f.label, result: '', plan: { mode: 'send', key: f.key, input: { option: text, label: text, text: text }, card: f.card } });
|
|
263
|
+
});
|
|
264
|
+
var foot = art.querySelector ? art.querySelector('.ask__foot, .ask__more') : null;
|
|
265
|
+
if (foot && typeof art.insertBefore === 'function') art.insertBefore(form, foot); else art.appendChild(form);
|
|
266
|
+
});
|
|
267
|
+
return { node: art, cv: shown };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// The beat a decision lands with: a pulse on the panel and a word over it,
|
|
271
|
+
// gone in under a second (still under reduced motion: city.css).
|
|
272
|
+
function flash(host, text) {
|
|
273
|
+
if (!host || !host.ownerDocument) return;
|
|
274
|
+
var doc = host.ownerDocument;
|
|
275
|
+
host.classList.remove('is-decided');
|
|
276
|
+
void host.offsetWidth;
|
|
277
|
+
host.classList.add('is-decided');
|
|
278
|
+
var tag = doc.createElement('span');
|
|
279
|
+
tag.className = 'decided-tag';
|
|
280
|
+
tag.setAttribute('role', 'status');
|
|
281
|
+
tag.textContent = '✓ ' + text;
|
|
282
|
+
host.appendChild(tag);
|
|
283
|
+
setTimeout(function () { host.classList.remove('is-decided'); if (tag.parentNode) tag.parentNode.removeChild(tag); }, 1100);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// A number key picks first, then confirms (owner, 2026-10-23): the first
|
|
287
|
+
// press moves to that answer and marks it; the same number again sends it.
|
|
288
|
+
// host is the panel that outlives a redraw; returns true when confirmed.
|
|
289
|
+
function armKey(host, cv, n) {
|
|
290
|
+
if (!host || !cv) return false;
|
|
291
|
+
var a = host.__armed;
|
|
292
|
+
if (a && a.id === cv.id && a.n === n) { host.__armed = null; return true; }
|
|
293
|
+
host.__armed = { id: cv.id, n: n };
|
|
294
|
+
paintArmed(host);
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
function paintArmed(host) {
|
|
298
|
+
var a = host && host.__armed;
|
|
299
|
+
if (!a || !host.querySelectorAll) return;
|
|
300
|
+
var opts = host.querySelectorAll('.ask__opt');
|
|
301
|
+
for (var i = 0; i < opts.length; i++) {
|
|
302
|
+
var b = opts[i];
|
|
303
|
+
var hit = b.getAttribute('data-n') === String(a.n) && b.closest && b.closest('[data-ask]') && b.closest('[data-ask]').getAttribute('data-ask') === String(a.id);
|
|
304
|
+
b.classList.toggle('ask__opt--armed', !!hit);
|
|
305
|
+
var old = b.querySelector('.ask__confirm');
|
|
306
|
+
if (old) old.parentNode.removeChild(old);
|
|
307
|
+
if (hit) {
|
|
308
|
+
var t = b.ownerDocument.createElement('span');
|
|
309
|
+
t.className = 'ask__confirm';
|
|
310
|
+
t.textContent = 'press ' + a.n + ' again to confirm';
|
|
311
|
+
b.appendChild(t);
|
|
312
|
+
if (b.focus) b.focus();
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function make(ctx) {
|
|
318
|
+
// ctx { data(), done{}, send(req), changed(), root() }
|
|
319
|
+
var doc = document;
|
|
320
|
+
var st = { open: false, view: 'one', scope: null, at: 0, busy: false, said: '', picked: {}, skipped: [] };
|
|
321
|
+
var el = null;
|
|
322
|
+
|
|
323
|
+
function K() { return window.GovDeck; }
|
|
324
|
+
function node(tag, cls, text) {
|
|
325
|
+
var n = doc.createElement(tag);
|
|
326
|
+
if (cls) n.className = cls;
|
|
327
|
+
if (text != null) n.textContent = String(text);
|
|
328
|
+
return n;
|
|
329
|
+
}
|
|
330
|
+
function button(cls, text, fn, label) {
|
|
331
|
+
var b = node('button', cls, text);
|
|
332
|
+
b.setAttribute('type', 'button');
|
|
333
|
+
if (label) b.setAttribute('aria-label', label);
|
|
334
|
+
b.addEventListener('click', fn);
|
|
335
|
+
return b;
|
|
336
|
+
}
|
|
337
|
+
function queue(scope) {
|
|
338
|
+
var d = ctx.data();
|
|
339
|
+
if (!K() || !d || !d.living) return [];
|
|
340
|
+
// what is out for a rewrite leaves the queue until its new words land
|
|
341
|
+
var q = K().queueOf(d.living, { done: withoutReworded(ctx.done, d.docket), delegated: {}, skipped: st.skipped, extra: [] }, scope === undefined ? st.scope : scope);
|
|
342
|
+
// one building's items together, buildings in the order their first item ranks
|
|
343
|
+
var order = [];
|
|
344
|
+
q.forEach(function (a) { if (order.indexOf(a.building) < 0) order.push(a.building); });
|
|
345
|
+
return q.slice().sort(function (a, b) { return order.indexOf(a.building) - order.indexOf(b.building); });
|
|
346
|
+
}
|
|
347
|
+
function view(ask) {
|
|
348
|
+
var d = ctx.data();
|
|
349
|
+
return K().cardView(ask, { docket: d.docket, room: ctx.room ? ctx.room() : null, constitution: d.raw && d.raw.constitution, goals: (d.goals || []).filter(function (g) { return g && g.status === 'open'; }), now: Date.now(), viewerId: d.docket && d.docket.viewer ? String(d.docket.viewer.id) : null });
|
|
350
|
+
}
|
|
351
|
+
function ageOf(cv) { var k = String(cv.kicker || ''); var i = k.indexOf(' · '); return i < 0 ? '' : k.slice(i + 3); }
|
|
352
|
+
|
|
353
|
+
function open(scope, how) {
|
|
354
|
+
st.open = true;
|
|
355
|
+
st.scope = scope || null;
|
|
356
|
+
st.at = 0;
|
|
357
|
+
st.said = '';
|
|
358
|
+
st.picked = {};
|
|
359
|
+
if (how) st.view = how;
|
|
360
|
+
render();
|
|
361
|
+
var f = el && el.querySelector('.ask__opt, .decide__row-box, .decide__close');
|
|
362
|
+
if (f && f.focus) f.focus();
|
|
363
|
+
}
|
|
364
|
+
function close() {
|
|
365
|
+
st.open = false;
|
|
366
|
+
if (el) { while (el.firstChild) el.removeChild(el.firstChild); el.setAttribute('hidden', ''); }
|
|
367
|
+
var L = window.location; if (L && /^#decide/.test(L.hash || '') && window.history && history.replaceState) history.replaceState(null, '', L.pathname + L.search);
|
|
368
|
+
}
|
|
369
|
+
function mount() {
|
|
370
|
+
if (el) return el;
|
|
371
|
+
el = node('div', 'decide gov-glass');
|
|
372
|
+
el.setAttribute('id', 'cityDecide');
|
|
373
|
+
el.setAttribute('role', 'dialog');
|
|
374
|
+
el.setAttribute('aria-label', 'Decide');
|
|
375
|
+
el.setAttribute('hidden', '');
|
|
376
|
+
if (doc.addEventListener) doc.addEventListener('keydown', key); else el.addEventListener('keydown', key);
|
|
377
|
+
(ctx.root() || doc.body).appendChild(el);
|
|
378
|
+
return el;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function render() {
|
|
382
|
+
mount();
|
|
383
|
+
while (el.firstChild) el.removeChild(el.firstChild);
|
|
384
|
+
if (!st.open) { el.setAttribute('hidden', ''); return; }
|
|
385
|
+
el.removeAttribute('hidden');
|
|
386
|
+
var all = queue(null);
|
|
387
|
+
var q = queue();
|
|
388
|
+
var yours = q.filter(function (a) { return a.needs_you; }).length;
|
|
389
|
+
|
|
390
|
+
var head = node('div', 'decide__head');
|
|
391
|
+
head.appendChild(node('h2', 'decide__title', 'Decide'));
|
|
392
|
+
// the scope: every building, or one
|
|
393
|
+
var pick = node('select', 'decide__scope');
|
|
394
|
+
pick.setAttribute('aria-label', 'Which building');
|
|
395
|
+
var o0 = node('option', null, 'All buildings (' + all.length + ')');
|
|
396
|
+
o0.value = '';
|
|
397
|
+
pick.appendChild(o0);
|
|
398
|
+
(K().scopesOf ? K().scopesOf(ctx.data().living, { done: withoutReworded(ctx.done, ctx.data().docket), delegated: {}, skipped: [], extra: [] }) : []).forEach(function (s) {
|
|
399
|
+
var o = node('option', null, s.label + ' (' + s.n + ')');
|
|
400
|
+
o.value = s.key;
|
|
401
|
+
if (s.key === st.scope) o.selected = true;
|
|
402
|
+
pick.appendChild(o);
|
|
403
|
+
});
|
|
404
|
+
pick.addEventListener('change', function () { st.scope = pick.value || null; st.at = 0; st.picked = {}; render(); });
|
|
405
|
+
head.appendChild(pick);
|
|
406
|
+
// one by one, or the table: the hall's own tabs (OTBKit.makeTabs)
|
|
407
|
+
var tabs = node('div', 'decide__tabs');
|
|
408
|
+
head.appendChild(tabs);
|
|
409
|
+
var Kit = window.OTBKit;
|
|
410
|
+
if (Kit && Kit.makeTabs) {
|
|
411
|
+
Kit.makeTabs({ mount: tabs, tabs: [{ id: 'decide-one', label: 'One by one' }, { id: 'decide-table', label: 'Table' }], initial: st.view === 'table' ? 'decide-table' : 'decide-one',
|
|
412
|
+
onChange: function (id) { var v = id === 'decide-table' ? 'table' : 'one'; if (v === st.view) return; st.view = v; st.said = ''; render(); } });
|
|
413
|
+
}
|
|
414
|
+
head.appendChild(node('span', 'decide__gap'));
|
|
415
|
+
var counts = node('span', 'decide__counts', 'Total ' + q.length + ' / ');
|
|
416
|
+
counts.appendChild(node('b', yours ? 'decide__yours' : null, 'Yours ' + yours));
|
|
417
|
+
var rw = rewordingCount(ctx.data().docket, st.scope);
|
|
418
|
+
if (rw) counts.appendChild(node('span', 'decide__rewording', 'Rewording ' + rw));
|
|
419
|
+
head.appendChild(counts);
|
|
420
|
+
var x = button('decide__close', null, close, 'Close');
|
|
421
|
+
x.setAttribute('aria-keyshortcuts', 'Escape');
|
|
422
|
+
x.appendChild(node('kbd', null, 'esc'));
|
|
423
|
+
head.appendChild(x);
|
|
424
|
+
el.appendChild(head);
|
|
425
|
+
|
|
426
|
+
if (!q.length) { el.appendChild(node('p', 'decide__empty', 'Nothing waits here. Every ask in this scope is answered.')); return; }
|
|
427
|
+
if (st.view === 'table') table(q); else one(q);
|
|
428
|
+
if (st.said) { var s = node('p', 'decide__said', st.said); s.setAttribute('role', 'status'); el.appendChild(s); }
|
|
429
|
+
// a pick waiting on its confirm survives a redraw of the same card, never a move to another
|
|
430
|
+
if (el.__armed && (st.view !== 'one' || !el._cv || el._cv.id !== el.__armed.id)) el.__armed = null;
|
|
431
|
+
else paintArmed(el);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// ---------- one by one ----------
|
|
435
|
+
function one(q) {
|
|
436
|
+
if (st.at >= q.length) st.at = q.length - 1;
|
|
437
|
+
if (st.at < 0) st.at = 0;
|
|
438
|
+
var raw = view(q[st.at]);
|
|
439
|
+
var g = groupOf(q, st.at);
|
|
440
|
+
var row = node('div', 'decide__one');
|
|
441
|
+
var prev = button('decide__arrow', '‹', function () { jump(-1); }, 'Previous building');
|
|
442
|
+
prev.setAttribute('aria-keyshortcuts', 'ArrowLeft');
|
|
443
|
+
if (g.n < 2) prev.setAttribute('disabled', '');
|
|
444
|
+
var next = button('decide__arrow', '›', function () { jump(1); }, 'Next building');
|
|
445
|
+
next.setAttribute('aria-keyshortcuts', 'ArrowRight');
|
|
446
|
+
if (g.n < 2) next.setAttribute('disabled', '');
|
|
447
|
+
var drawn = window.GovCityDecide.drawCard(doc, K(), raw, {
|
|
448
|
+
pick: function (op) { answerOne(raw, op); },
|
|
449
|
+
skip: function () { st.skipped.push(raw.id); st.said = ''; render(); },
|
|
450
|
+
said: function (t) { st.said = t; render(); },
|
|
451
|
+
reworded: function (t) { st.said = t; ctx.changed(); render(); },
|
|
452
|
+
send: ctx.send,
|
|
453
|
+
docket: function () { return ctx.data().docket; },
|
|
454
|
+
}, { where: true, acts: true, busy: st.busy, pos: (g.i + 1) + ' of ' + g.items, cls: 'decide-ask' + (raw.needsYou ? ' decide-ask--you' : '') });
|
|
455
|
+
row.appendChild(prev);
|
|
456
|
+
row.appendChild(drawn.node);
|
|
457
|
+
row.appendChild(next);
|
|
458
|
+
el.appendChild(row);
|
|
459
|
+
// where you are: this building's items, and the buildings
|
|
460
|
+
var pos = node('p', 'decide__pos');
|
|
461
|
+
pos.appendChild(node('b', 'decide__pos-n', raw.buildingName + ' ' + (g.i + 1) + ' of ' + g.items));
|
|
462
|
+
pos.appendChild(node('span', null, ' · building ' + (g.b + 1) + ' of ' + g.n + ' · Tab or ↑ ↓ next item · ← → next building · 1–9 answer · S skip'));
|
|
463
|
+
el.appendChild(pos);
|
|
464
|
+
el._cv = drawn.cv;
|
|
465
|
+
}
|
|
466
|
+
// the item's place: its index in its building, and its building's among the rest
|
|
467
|
+
function groupOf(q, at) {
|
|
468
|
+
var keys = [];
|
|
469
|
+
q.forEach(function (a) { if (keys.indexOf(a.building) < 0) keys.push(a.building); });
|
|
470
|
+
var b = q[at] ? q[at].building : keys[0];
|
|
471
|
+
var mine = [];
|
|
472
|
+
q.forEach(function (a, i) { if (a.building === b) mine.push(i); });
|
|
473
|
+
return { b: keys.indexOf(b), n: keys.length, keys: keys, idx: mine, i: mine.indexOf(at), items: mine.length };
|
|
474
|
+
}
|
|
475
|
+
function focusFirst() { var c = el.querySelector('.ask__opt, .ask-field__input'); if (c && c.focus) c.focus(); }
|
|
476
|
+
// Tab / down: the next item in this building (wrapping); Shift+Tab / up: the one before
|
|
477
|
+
function flip(d) {
|
|
478
|
+
var q = queue(), g = groupOf(q, st.at);
|
|
479
|
+
if (!g.items) return;
|
|
480
|
+
st.at = g.idx[(g.i + d + g.items) % g.items];
|
|
481
|
+
st.said = '';
|
|
482
|
+
render();
|
|
483
|
+
focusFirst();
|
|
484
|
+
}
|
|
485
|
+
// left / right: the first item of the building before or after
|
|
486
|
+
function jump(d) {
|
|
487
|
+
var q = queue(), g = groupOf(q, st.at);
|
|
488
|
+
if (g.n < 2) return;
|
|
489
|
+
var to = g.keys[(g.b + d + g.n) % g.n];
|
|
490
|
+
for (var i = 0; i < q.length; i++) if (q[i].building === to) { st.at = i; break; }
|
|
491
|
+
st.said = '';
|
|
492
|
+
render();
|
|
493
|
+
focusFirst();
|
|
494
|
+
}
|
|
495
|
+
function answerOne(cv, op) {
|
|
496
|
+
if (st.busy || !op) return;
|
|
497
|
+
if (op.plan && op.plan.mode === 'record') { st.said = 'This one is signed off with a recording: answer it on its desk.'; render(); return; }
|
|
498
|
+
st.busy = true;
|
|
499
|
+
render();
|
|
500
|
+
K().sendAnswer(op, ctx.send).then(function (res) {
|
|
501
|
+
st.busy = false;
|
|
502
|
+
st.said = res.toast;
|
|
503
|
+
if (res.ok) { ctx.done[cv.id] = true; ctx.changed(); }
|
|
504
|
+
render();
|
|
505
|
+
if (res.ok) flash(el.querySelector('.decide-ask') || el, 'Decided');
|
|
506
|
+
var c = el.querySelector('.ask__opt');
|
|
507
|
+
if (c && c.focus) c.focus();
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// ---------- table ----------
|
|
512
|
+
function table(q) {
|
|
513
|
+
var cvs = q.map(function (a) { return window.GovCityDecide.answerable(view(a)); });
|
|
514
|
+
var ticked = cvs.filter(function (cv) { return st.picked[cv.id]; });
|
|
515
|
+
// the bulk bar: answers every ticked row offers, and "my pick" for each
|
|
516
|
+
var bar = node('div', 'decide__bulk');
|
|
517
|
+
bar.appendChild(node('span', 'decide__bulk-n', ticked.length ? ticked.length + ' selected' : 'Tick rows to answer them together'));
|
|
518
|
+
if (ticked.length) {
|
|
519
|
+
var common = null;
|
|
520
|
+
ticked.forEach(function (cv) {
|
|
521
|
+
var labels = cv.options.filter(function (o) { return !o.off && !(o.plan && o.plan.mode === 'record'); }).map(function (o) { return o.label; });
|
|
522
|
+
common = common === null ? labels : common.filter(function (l) { return labels.indexOf(l) >= 0; });
|
|
523
|
+
});
|
|
524
|
+
(common || []).forEach(function (label) {
|
|
525
|
+
bar.appendChild(button('decide__bulk-btn', label, function () { bulk(ticked, function (cv) { return cv.options.filter(function (o) { return o.label === label; })[0]; }); }));
|
|
526
|
+
});
|
|
527
|
+
if (ticked.some(function (cv) { return cv.rec; })) {
|
|
528
|
+
bar.appendChild(button('decide__bulk-btn decide__bulk-btn--rec', 'Use my pick for each', function () { bulk(ticked, function (cv) { return cv.rec ? cv.options.filter(function (o) { return o.label === cv.rec.label; })[0] : null; }); }));
|
|
529
|
+
}
|
|
530
|
+
bar.appendChild(button('decide__bulk-clear', 'clear', function () { st.picked = {}; render(); }));
|
|
531
|
+
}
|
|
532
|
+
el.appendChild(bar);
|
|
533
|
+
|
|
534
|
+
var wrap = node('div', 'decide__table-wrap');
|
|
535
|
+
var t = node('table', 'decide__table');
|
|
536
|
+
var thead = node('thead');
|
|
537
|
+
var hr = node('tr');
|
|
538
|
+
var allBox = node('input', 'decide__row-box');
|
|
539
|
+
allBox.type = 'checkbox';
|
|
540
|
+
allBox.setAttribute('aria-label', 'Select all');
|
|
541
|
+
allBox.checked = ticked.length === cvs.length;
|
|
542
|
+
allBox.addEventListener('change', function () { st.picked = {}; if (allBox.checked) cvs.forEach(function (cv) { st.picked[cv.id] = true; }); render(); });
|
|
543
|
+
var th0 = node('th');
|
|
544
|
+
th0.appendChild(allBox);
|
|
545
|
+
hr.appendChild(th0);
|
|
546
|
+
['Building', 'Question', 'Waits on', 'Age', 'My pick', 'Answer'].forEach(function (h) { hr.appendChild(node('th', null, h)); });
|
|
547
|
+
thead.appendChild(hr);
|
|
548
|
+
t.appendChild(thead);
|
|
549
|
+
var tb = node('tbody');
|
|
550
|
+
cvs.forEach(function (cv) {
|
|
551
|
+
var tr = node('tr', cv.needsYou ? 'decide__tr--you' : null);
|
|
552
|
+
var td0 = node('td');
|
|
553
|
+
var box = node('input', 'decide__row-box');
|
|
554
|
+
box.type = 'checkbox';
|
|
555
|
+
box.checked = !!st.picked[cv.id];
|
|
556
|
+
box.setAttribute('aria-label', 'Select: ' + cv.question);
|
|
557
|
+
box.addEventListener('change', function () { if (box.checked) st.picked[cv.id] = true; else delete st.picked[cv.id]; render(); });
|
|
558
|
+
td0.appendChild(box);
|
|
559
|
+
tr.appendChild(td0);
|
|
560
|
+
tr.appendChild(node('td', 'decide__bld', cv.buildingName));
|
|
561
|
+
tr.appendChild(node('td', 'decide__q', cv.question));
|
|
562
|
+
tr.appendChild(node('td', null, cv.waits));
|
|
563
|
+
tr.appendChild(node('td', 'decide__age', ageOf(cv)));
|
|
564
|
+
tr.appendChild(node('td', null, cv.rec ? cv.rec.label : '—'));
|
|
565
|
+
var acts = node('td', 'decide__acts');
|
|
566
|
+
cv.fields.forEach(function (fl) {
|
|
567
|
+
var box = node('input', 'decide__text');
|
|
568
|
+
box.setAttribute('type', 'text');
|
|
569
|
+
box.setAttribute('placeholder', fl.placeholder);
|
|
570
|
+
box.setAttribute('aria-label', fl.label);
|
|
571
|
+
acts.appendChild(box);
|
|
572
|
+
acts.appendChild(button('decide__opt', fl.label, function () {
|
|
573
|
+
var t = String(box.value || '').trim();
|
|
574
|
+
if (t) bulk([cv], function () { return { label: fl.label, plan: { mode: 'send', key: fl.key, input: { option: t, label: t, text: t }, card: fl.card } }; });
|
|
575
|
+
}));
|
|
576
|
+
});
|
|
577
|
+
if (!cv.options.length && !cv.fields.length && cv.desk) {
|
|
578
|
+
var a = node('a', 'decide__desk', 'on its desk');
|
|
579
|
+
a.setAttribute('href', cv.desk);
|
|
580
|
+
acts.appendChild(a);
|
|
581
|
+
}
|
|
582
|
+
cv.options.forEach(function (op) {
|
|
583
|
+
var b = button('decide__opt' + (op.rec ? ' decide__opt--rec' : ''), op.label, function () { bulk([cv], function () { return op; }); });
|
|
584
|
+
b.setAttribute('title', op.off ? op.why : op.result);
|
|
585
|
+
if (op.off || st.busy) b.setAttribute('disabled', '');
|
|
586
|
+
acts.appendChild(b);
|
|
587
|
+
});
|
|
588
|
+
tr.appendChild(acts);
|
|
589
|
+
tb.appendChild(tr);
|
|
590
|
+
});
|
|
591
|
+
t.appendChild(tb);
|
|
592
|
+
wrap.appendChild(t);
|
|
593
|
+
el.appendChild(wrap);
|
|
594
|
+
}
|
|
595
|
+
// Answer rows one after another; say how many went and what refused.
|
|
596
|
+
function bulk(cvs, choose) {
|
|
597
|
+
if (st.busy) return;
|
|
598
|
+
var jobs = cvs.map(function (cv) { return { cv: cv, op: choose(cv) }; });
|
|
599
|
+
var skipped = jobs.filter(function (j) { return !j.op || j.op.off || (j.op.plan && j.op.plan.mode === 'record'); }).length;
|
|
600
|
+
jobs = jobs.filter(function (j) { return j.op && !j.op.off && !(j.op.plan && j.op.plan.mode === 'record'); });
|
|
601
|
+
st.busy = true;
|
|
602
|
+
st.said = 'Sending ' + jobs.length + '…';
|
|
603
|
+
render();
|
|
604
|
+
var ok = 0, refused = [];
|
|
605
|
+
jobs.reduce(function (p, j) {
|
|
606
|
+
return p.then(function () {
|
|
607
|
+
return K().sendAnswer(j.op, ctx.send).then(function (res) {
|
|
608
|
+
if (res.ok) { ok++; ctx.done[j.cv.id] = true; delete st.picked[j.cv.id]; } else refused.push(res.toast);
|
|
609
|
+
});
|
|
610
|
+
});
|
|
611
|
+
}, Promise.resolve()).then(function () {
|
|
612
|
+
st.busy = false;
|
|
613
|
+
st.said = (jobs.length === 1 && cvs.length === 1 && !refused.length ? 'Answered.' : 'Answered ' + ok + ' of ' + cvs.length + '.')
|
|
614
|
+
+ (skipped ? ' ' + skipped + ' had no such answer from here.' : '')
|
|
615
|
+
+ (refused.length ? ' Refused: ' + refused[0] + (refused.length > 1 ? ' (and ' + (refused.length - 1) + ' more)' : '') : '');
|
|
616
|
+
if (ok) ctx.changed();
|
|
617
|
+
render();
|
|
618
|
+
if (ok) flash(el, 'Decided ' + ok);
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function key(e) {
|
|
623
|
+
if (!st.open || e.ctrlKey || e.metaKey || e.altKey) return;
|
|
624
|
+
var tag = e.target && e.target.tagName;
|
|
625
|
+
if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); close(); return; }
|
|
626
|
+
if (st.view !== 'one' || tag === 'SELECT') return;
|
|
627
|
+
if (tag === 'INPUT' || tag === 'TEXTAREA') return;
|
|
628
|
+
if (e.key === 'Tab') { e.preventDefault(); flip(e.shiftKey ? -1 : 1); return; }
|
|
629
|
+
if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') { e.preventDefault(); jump(e.key === 'ArrowLeft' ? -1 : 1); return; }
|
|
630
|
+
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { e.preventDefault(); flip(e.key === 'ArrowUp' ? -1 : 1); return; }
|
|
631
|
+
if (/^[sS]$/.test(e.key) && el._cv) { e.preventDefault(); st.skipped.push(el._cv.id); render(); return; }
|
|
632
|
+
if (/^[1-9]$/.test(e.key) && el._cv) {
|
|
633
|
+
var op = el._cv.options[Number(e.key) - 1];
|
|
634
|
+
if (op) { e.preventDefault(); if (armKey(el, el._cv, Number(e.key))) answerOne(el._cv, op); }
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
return { open: open, close: close, render: render, isOpen: function () { return st.open; } };
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
window.GovCityDecide = { make: make, answerable: answerable, drawCard: drawCard, flash: flash, armKey: armKey, rewordingIds: rewordingIds, rewordingCount: rewordingCount, withoutReworded: withoutReworded, paintArmed: paintArmed, promptOf: promptOf, CLAUDE_URL: CLAUDE_URL };
|
|
642
|
+
}());
|