@lchase/kiln 0.15.0 → 0.17.0
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/assets/serve.css +993 -310
- package/assets/serve.js +485 -60
- package/dist/config.js +3 -1
- package/dist/icons.js +41 -0
- package/dist/schemas.js +19 -0
- package/dist/serve-page.js +121 -45
- package/dist/serve.js +19 -18
- package/dist/settings.js +7 -0
- package/dist/theme.js +35 -0
- package/package.json +1 -1
package/assets/serve.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
var KEY = "kiln-needs-you";
|
|
3
|
+
var NOTIFY_KEY = "kiln-notify";
|
|
3
4
|
var body = document.body;
|
|
4
5
|
var board = document.getElementById("board");
|
|
5
6
|
var count = Number(body.getAttribute("data-needs-you") || "0");
|
|
@@ -12,7 +13,7 @@
|
|
|
12
13
|
if (raw === null) return;
|
|
13
14
|
var prev = Number(raw);
|
|
14
15
|
if (!isFinite(prev) || n <= prev) return;
|
|
15
|
-
if (
|
|
16
|
+
if (notifyMode() !== "on") return;
|
|
16
17
|
var added = n - prev;
|
|
17
18
|
try {
|
|
18
19
|
new Notification("Kiln", { body: added === 1 ? "1 job needs you" : added + " jobs need you" });
|
|
@@ -59,7 +60,7 @@
|
|
|
59
60
|
if (slimCount) slimCount.textContent = String(visible);
|
|
60
61
|
var empty = col.querySelector(".empty");
|
|
61
62
|
if (empty) empty.hidden = visible !== 0;
|
|
62
|
-
tracks.push(slim ? "
|
|
63
|
+
tracks.push(slim ? "var(--col-slim)" : "minmax(var(--col-min), 1fr)");
|
|
63
64
|
}
|
|
64
65
|
board.style.setProperty("--cols", tracks.join(" "));
|
|
65
66
|
}
|
|
@@ -257,26 +258,65 @@
|
|
|
257
258
|
try { localStorage.removeItem("kiln-drawer"); } catch (err) { /* private mode */ }
|
|
258
259
|
}
|
|
259
260
|
|
|
260
|
-
|
|
261
|
+
var notifyAsking = false;
|
|
262
|
+
|
|
263
|
+
function notifyPref() {
|
|
264
|
+
try { return localStorage.getItem(NOTIFY_KEY) === "off" ? "off" : "on"; }
|
|
265
|
+
catch (err) { return "on"; }
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function setNotifyPref(value) {
|
|
269
|
+
try { localStorage.setItem(NOTIFY_KEY, value); } catch (err) { /* private mode */ }
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function notifyMode() {
|
|
273
|
+
if (typeof Notification === "undefined") return "unavailable";
|
|
274
|
+
if (Notification.permission === "denied") return "blocked";
|
|
275
|
+
if (Notification.permission === "granted") return notifyPref() === "off" ? "off" : "on";
|
|
276
|
+
return "ask";
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function notifyCopy(mode) {
|
|
280
|
+
if (mode === "unavailable") return { label: "Notifications unavailable", note: "" };
|
|
281
|
+
if (mode === "blocked") return { label: "Notifications blocked", note: "Allow notifications for this site in the browser." };
|
|
282
|
+
if (mode === "on") return { label: "Notifications on", note: "" };
|
|
283
|
+
return { label: "Notify me", note: "" };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function renderNotify() {
|
|
261
287
|
var button = document.getElementById("notify");
|
|
262
288
|
if (!button) return;
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
289
|
+
var mode = notifyMode();
|
|
290
|
+
var copy = notifyCopy(mode);
|
|
267
291
|
button.hidden = false;
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
292
|
+
button.disabled = mode === "unavailable";
|
|
293
|
+
button.setAttribute("data-state", mode);
|
|
294
|
+
button.setAttribute("aria-pressed", mode === "on" ? "true" : "false");
|
|
295
|
+
button.setAttribute("aria-label", copy.label);
|
|
296
|
+
if (copy.note) button.setAttribute("aria-describedby", "notify-note");
|
|
297
|
+
else button.removeAttribute("aria-describedby");
|
|
298
|
+
var label = document.getElementById("notify-label");
|
|
299
|
+
if (label) label.textContent = copy.label;
|
|
300
|
+
var note = document.getElementById("notify-note");
|
|
301
|
+
if (note) {
|
|
302
|
+
note.hidden = !copy.note;
|
|
303
|
+
note.textContent = copy.note;
|
|
277
304
|
}
|
|
278
305
|
}
|
|
279
306
|
|
|
307
|
+
function askNotify() {
|
|
308
|
+
if (notifyAsking || typeof Notification === "undefined" || !Notification.requestPermission) return;
|
|
309
|
+
notifyAsking = true;
|
|
310
|
+
var pending = Notification.requestPermission();
|
|
311
|
+
var done = function () {
|
|
312
|
+
notifyAsking = false;
|
|
313
|
+
if (typeof Notification !== "undefined" && Notification.permission === "granted") setNotifyPref("on");
|
|
314
|
+
renderNotify();
|
|
315
|
+
};
|
|
316
|
+
if (pending && typeof pending.then === "function") pending.then(done, done);
|
|
317
|
+
else done();
|
|
318
|
+
}
|
|
319
|
+
|
|
280
320
|
var titleDirty = false;
|
|
281
321
|
var issueDirty = false;
|
|
282
322
|
var sourceKind = "prompt";
|
|
@@ -284,16 +324,204 @@
|
|
|
284
324
|
var ticketsDir = "";
|
|
285
325
|
var fsPath = "";
|
|
286
326
|
var sending = false;
|
|
327
|
+
var intakeTouched = {};
|
|
328
|
+
var intakeSubmitTried = false;
|
|
287
329
|
var FS_KEY = "kiln-fs-dir";
|
|
288
330
|
|
|
331
|
+
function pickLimit() {
|
|
332
|
+
var n = Number(body.getAttribute("data-pick-limit") || "5");
|
|
333
|
+
return n > 0 ? n : 5;
|
|
334
|
+
}
|
|
335
|
+
|
|
289
336
|
function selectedRepo() {
|
|
290
|
-
var
|
|
291
|
-
return
|
|
337
|
+
var box = document.getElementById("repo-pick");
|
|
338
|
+
return box ? box.getAttribute("data-value") || "" : "";
|
|
292
339
|
}
|
|
293
340
|
|
|
294
341
|
function intakeRepoPath() {
|
|
295
|
-
var
|
|
296
|
-
return
|
|
342
|
+
var box = document.getElementById("repo-pick");
|
|
343
|
+
return box ? box.getAttribute("data-path") || "" : "";
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function selectedWorkflow() {
|
|
347
|
+
var select = document.getElementById("workflow");
|
|
348
|
+
if (select) return select.value || "";
|
|
349
|
+
var host = document.getElementById("workflow-pick");
|
|
350
|
+
return host ? host.getAttribute("data-workflow") || "" : "";
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function setFieldError(id, message, reveal) {
|
|
354
|
+
var el = document.getElementById(id);
|
|
355
|
+
if (!el) return;
|
|
356
|
+
var on = Boolean(message) && reveal;
|
|
357
|
+
el.hidden = !on;
|
|
358
|
+
el.textContent = on ? message : "";
|
|
359
|
+
var field = el.closest(".field");
|
|
360
|
+
if (!field) return;
|
|
361
|
+
var control = field.querySelector("textarea, input, select, [role='combobox'], [role='group']");
|
|
362
|
+
if (!control) return;
|
|
363
|
+
if (on) control.setAttribute("aria-invalid", "true");
|
|
364
|
+
else control.removeAttribute("aria-invalid");
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function showFieldError(name) {
|
|
368
|
+
return intakeSubmitTried || intakeTouched[name] === true;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function syncValidity() {
|
|
372
|
+
var repo = selectedRepo();
|
|
373
|
+
var workflow = selectedWorkflow();
|
|
374
|
+
var source = sourceBody();
|
|
375
|
+
var prompt = source.kind === "prompt" && !String(source.text || "").trim() ? "Prompt is required." : "";
|
|
376
|
+
var spec = source.kind === "spec" && !source.path ? "Choose a spec file." : "";
|
|
377
|
+
var tickets = source.kind === "tickets" && !source.dir ? "Choose a tickets folder." : "";
|
|
378
|
+
var jira = source.kind === "jira" && !source.key ? "Enter a Jira key." : "";
|
|
379
|
+
setFieldError("repo-error", repo ? "" : "Choose a repo.", showFieldError("repo"));
|
|
380
|
+
setFieldError("workflow-error", workflow ? "" : "Choose a workflow.", showFieldError("workflow"));
|
|
381
|
+
setFieldError("prompt-error", prompt, showFieldError("prompt"));
|
|
382
|
+
setFieldError("source-error", spec || tickets, showFieldError("source"));
|
|
383
|
+
setFieldError("jira-error", jira, showFieldError("jira"));
|
|
384
|
+
var blocked = !repo || !workflow || Boolean(prompt || spec || tickets || jira);
|
|
385
|
+
var submit = document.getElementById("add-submit");
|
|
386
|
+
if (submit) submit.disabled = blocked || sending;
|
|
387
|
+
var preview = document.getElementById("preview-job");
|
|
388
|
+
if (preview) preview.disabled = blocked || sending;
|
|
389
|
+
return !blocked;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function repoListKey(repos) {
|
|
393
|
+
var parts = [];
|
|
394
|
+
for (var i = 0; i < repos.length; i++) {
|
|
395
|
+
var repo = repos[i];
|
|
396
|
+
if (!repo || !repo.name) continue;
|
|
397
|
+
parts.push(repo.name + "\t" + (repo.path || ""));
|
|
398
|
+
}
|
|
399
|
+
return parts.join("\n");
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function paintRepos(repos) {
|
|
403
|
+
var box = document.getElementById("repo-pick");
|
|
404
|
+
if (!box) return;
|
|
405
|
+
var items = [];
|
|
406
|
+
for (var i = 0; i < repos.length; i++) if (repos[i] && repos[i].name) items.push(repos[i]);
|
|
407
|
+
var key = repoListKey(items);
|
|
408
|
+
if (box.getAttribute("data-list") === key) return;
|
|
409
|
+
var current = box.getAttribute("data-value") || "";
|
|
410
|
+
var selected = null;
|
|
411
|
+
for (var s = 0; s < items.length; s++) if (items[s].name === current) selected = items[s];
|
|
412
|
+
if (!selected && items.length) selected = items[0];
|
|
413
|
+
var value = selected ? selected.name : "";
|
|
414
|
+
var path = selected ? (selected.path || "") : "";
|
|
415
|
+
var mode = !items.length ? "empty" : items.length > pickLimit() ? "select" : "chips";
|
|
416
|
+
var html = "";
|
|
417
|
+
if (mode === "empty") html = '<p class="empty">No repos are registered.</p>';
|
|
418
|
+
if (mode === "chips") {
|
|
419
|
+
for (var c = 0; c < items.length; c++) {
|
|
420
|
+
var chip = items[c];
|
|
421
|
+
var pressed = chip.name === value;
|
|
422
|
+
html += '<button type="button" class="pick" data-repo="' + esc(chip.name) + '" data-path="' + esc(chip.path || "") + '" aria-pressed="' + (pressed ? "true" : "false") + '">' + esc(chip.name) + "</button>";
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
if (mode === "select") {
|
|
426
|
+
html = '<input id="repo-pick-query" class="combo-input" type="text" role="combobox" aria-autocomplete="list" aria-expanded="false" aria-controls="repo-pick-list" aria-labelledby="repo-label" placeholder="Search repos" value="' + esc(value) + '" autocomplete="off" spellcheck="false">';
|
|
427
|
+
html += '<ul class="combo-list" id="repo-pick-list" role="listbox" hidden>';
|
|
428
|
+
for (var o = 0; o < items.length; o++) {
|
|
429
|
+
var opt = items[o];
|
|
430
|
+
html += '<li role="option" id="repo-opt-' + o + '" data-value="' + esc(opt.name) + '" data-path="' + esc(opt.path || "") + '" aria-selected="' + (opt.name === value ? "true" : "false") + '">' + esc(opt.name) + "</li>";
|
|
431
|
+
}
|
|
432
|
+
html += '<li class="empty" role="presentation" hidden>No matching repos.</li></ul>';
|
|
433
|
+
}
|
|
434
|
+
box.className = mode === "select" ? "combo" : "picks";
|
|
435
|
+
box.setAttribute("data-mode", mode);
|
|
436
|
+
box.setAttribute("data-value", value);
|
|
437
|
+
box.setAttribute("data-path", path);
|
|
438
|
+
box.setAttribute("data-list", key);
|
|
439
|
+
if (mode === "select") {
|
|
440
|
+
box.removeAttribute("role");
|
|
441
|
+
box.removeAttribute("aria-labelledby");
|
|
442
|
+
} else {
|
|
443
|
+
box.setAttribute("role", "group");
|
|
444
|
+
box.setAttribute("aria-labelledby", "repo-label");
|
|
445
|
+
}
|
|
446
|
+
box.innerHTML = html;
|
|
447
|
+
syncValidity();
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function visibleOptions(box) {
|
|
451
|
+
var all = box.querySelectorAll("[role='option']");
|
|
452
|
+
var out = [];
|
|
453
|
+
for (var i = 0; i < all.length; i++) if (!all[i].hidden) out.push(all[i]);
|
|
454
|
+
return out;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function applyComboFilter(box, query) {
|
|
458
|
+
var q = String(query || "").trim().toLowerCase();
|
|
459
|
+
var options = box.querySelectorAll("[role='option']");
|
|
460
|
+
var shown = 0;
|
|
461
|
+
for (var i = 0; i < options.length; i++) {
|
|
462
|
+
var name = (options[i].getAttribute("data-value") || options[i].textContent || "").toLowerCase();
|
|
463
|
+
var hide = q.length > 0 && name.indexOf(q) === -1;
|
|
464
|
+
options[i].hidden = hide;
|
|
465
|
+
if (!hide) shown += 1;
|
|
466
|
+
}
|
|
467
|
+
var empty = box.querySelector(".combo-list .empty");
|
|
468
|
+
if (empty) empty.hidden = shown !== 0;
|
|
469
|
+
setComboActive(box, 0);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function setComboActive(box, index) {
|
|
473
|
+
var options = visibleOptions(box);
|
|
474
|
+
var input = box.querySelector("[role='combobox']");
|
|
475
|
+
if (!options.length) {
|
|
476
|
+
box.removeAttribute("data-active");
|
|
477
|
+
if (input) input.removeAttribute("aria-activedescendant");
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
if (index < 0) index = options.length - 1;
|
|
481
|
+
if (index >= options.length) index = 0;
|
|
482
|
+
box.setAttribute("data-active", String(index));
|
|
483
|
+
for (var i = 0; i < options.length; i++) options[i].classList.toggle("active", i === index);
|
|
484
|
+
if (input && options[index].id) input.setAttribute("aria-activedescendant", options[index].id);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function openCombo(box, filter) {
|
|
488
|
+
if (!box) return;
|
|
489
|
+
var list = box.querySelector("[role='listbox']");
|
|
490
|
+
var input = box.querySelector("[role='combobox']");
|
|
491
|
+
if (!list || !input) return;
|
|
492
|
+
list.hidden = false;
|
|
493
|
+
input.setAttribute("aria-expanded", "true");
|
|
494
|
+
applyComboFilter(box, filter ? input.value : "");
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function closeCombo(box) {
|
|
498
|
+
var list = box.querySelector("[role='listbox']");
|
|
499
|
+
var input = box.querySelector("[role='combobox']");
|
|
500
|
+
if (list) list.hidden = true;
|
|
501
|
+
if (input) {
|
|
502
|
+
input.setAttribute("aria-expanded", "false");
|
|
503
|
+
input.removeAttribute("aria-activedescendant");
|
|
504
|
+
input.value = box.getAttribute("data-value") || "";
|
|
505
|
+
}
|
|
506
|
+
box.removeAttribute("data-active");
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function closeCombos(except) {
|
|
510
|
+
var boxes = document.querySelectorAll(".combo");
|
|
511
|
+
for (var i = 0; i < boxes.length; i++) if (boxes[i] !== except) closeCombo(boxes[i]);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function chooseComboOption(box, option) {
|
|
515
|
+
if (!box || !option) return;
|
|
516
|
+
var value = option.getAttribute("data-value") || "";
|
|
517
|
+
box.setAttribute("data-value", value);
|
|
518
|
+
if (option.hasAttribute("data-path")) box.setAttribute("data-path", option.getAttribute("data-path") || "");
|
|
519
|
+
var options = box.querySelectorAll("[role='option']");
|
|
520
|
+
for (var i = 0; i < options.length; i++) {
|
|
521
|
+
options[i].setAttribute("aria-selected", options[i] === option ? "true" : "false");
|
|
522
|
+
}
|
|
523
|
+
closeCombo(box);
|
|
524
|
+
syncValidity();
|
|
297
525
|
}
|
|
298
526
|
|
|
299
527
|
function showIntakeError(message) {
|
|
@@ -346,6 +574,7 @@
|
|
|
346
574
|
if (sourceKind === "tickets") html += '<button type="button" class="fs-use" data-path="' + esc(fsPath) + '">Use this folder</button>';
|
|
347
575
|
host.innerHTML = html;
|
|
348
576
|
if (sourceKind === "tickets") setPicked(fsPath);
|
|
577
|
+
syncValidity();
|
|
349
578
|
}
|
|
350
579
|
|
|
351
580
|
function chooseSpec(data) {
|
|
@@ -356,6 +585,7 @@
|
|
|
356
585
|
var input = document.getElementById("job-title");
|
|
357
586
|
if (input && heading) input.value = heading;
|
|
358
587
|
}
|
|
588
|
+
syncValidity();
|
|
359
589
|
}
|
|
360
590
|
|
|
361
591
|
function loadFs(path, fallback) {
|
|
@@ -417,6 +647,7 @@
|
|
|
417
647
|
}
|
|
418
648
|
syncIssueDefault();
|
|
419
649
|
if (sourceKind === "spec" || sourceKind === "tickets") openBrowser();
|
|
650
|
+
syncValidity();
|
|
420
651
|
}
|
|
421
652
|
|
|
422
653
|
function syncIssueDefault() {
|
|
@@ -451,15 +682,12 @@
|
|
|
451
682
|
|
|
452
683
|
function submitIntake(dryRun) {
|
|
453
684
|
if (sending) return;
|
|
685
|
+
intakeSubmitTried = true;
|
|
686
|
+
if (!syncValidity()) return;
|
|
454
687
|
var repo = selectedRepo();
|
|
455
|
-
var workflow =
|
|
688
|
+
var workflow = selectedWorkflow();
|
|
456
689
|
var source = sourceBody();
|
|
457
|
-
|
|
458
|
-
if (source.kind === "prompt" && !String(source.text || "").trim()) { showIntakeError("Prompt is empty."); return; }
|
|
459
|
-
if (source.kind === "spec" && !source.path) { showIntakeError("Choose a spec file."); return; }
|
|
460
|
-
if (source.kind === "tickets" && !source.dir) { showIntakeError("Choose a tickets folder."); return; }
|
|
461
|
-
if (source.kind === "jira" && !source.key) { showIntakeError("Enter a Jira key."); return; }
|
|
462
|
-
var payload = { repo: repo, workflow: workflow ? workflow.value : "", source: source };
|
|
690
|
+
var payload = { repo: repo, workflow: workflow, source: source };
|
|
463
691
|
if (dryRun) payload.dryRun = true;
|
|
464
692
|
if (sourceKind !== "tickets") {
|
|
465
693
|
var issue = document.getElementById("job-issue");
|
|
@@ -473,6 +701,7 @@
|
|
|
473
701
|
}
|
|
474
702
|
}
|
|
475
703
|
sending = true;
|
|
704
|
+
syncValidity();
|
|
476
705
|
showIntakeError("");
|
|
477
706
|
fetch("/api/jobs", {
|
|
478
707
|
method: "POST",
|
|
@@ -488,6 +717,7 @@
|
|
|
488
717
|
})
|
|
489
718
|
.then(function (result) {
|
|
490
719
|
sending = false;
|
|
720
|
+
syncValidity();
|
|
491
721
|
if (!result.ok) {
|
|
492
722
|
showIntakeError((result.data && result.data.error) || "Could not add the job.");
|
|
493
723
|
return;
|
|
@@ -507,6 +737,7 @@
|
|
|
507
737
|
})
|
|
508
738
|
.catch(function () {
|
|
509
739
|
sending = false;
|
|
740
|
+
syncValidity();
|
|
510
741
|
showIntakeError("Could not add the job.");
|
|
511
742
|
});
|
|
512
743
|
}
|
|
@@ -523,28 +754,7 @@
|
|
|
523
754
|
}
|
|
524
755
|
}
|
|
525
756
|
if (!data || !Array.isArray(data.repos)) return;
|
|
526
|
-
|
|
527
|
-
if (!box) return;
|
|
528
|
-
var current = selectedRepo();
|
|
529
|
-
var still = false;
|
|
530
|
-
var html = "";
|
|
531
|
-
if (!data.repos.length) html = '<p class="empty">No repos are registered.</p>';
|
|
532
|
-
for (var i = 0; i < data.repos.length; i++) {
|
|
533
|
-
var repo = data.repos[i];
|
|
534
|
-
if (!repo || !repo.name) continue;
|
|
535
|
-
if (repo.name === current) still = true;
|
|
536
|
-
}
|
|
537
|
-
if (data.repos.length) {
|
|
538
|
-
html = "";
|
|
539
|
-
for (var j = 0; j < data.repos.length; j++) {
|
|
540
|
-
var item = data.repos[j];
|
|
541
|
-
var pressed = still ? item.name === current : j === 0;
|
|
542
|
-
html += '<button type="button" class="pick" data-repo="' + esc(item.name) + '" data-path="' + esc(item.path || "") + '" aria-pressed="' + (pressed ? "true" : "false") + '">' + esc(item.name) + "</button>";
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
if (box.getAttribute("data-repos") === html) return;
|
|
546
|
-
box.setAttribute("data-repos", html);
|
|
547
|
-
box.innerHTML = html;
|
|
757
|
+
paintRepos(data.repos);
|
|
548
758
|
}
|
|
549
759
|
|
|
550
760
|
function showFresh() {
|
|
@@ -598,17 +808,31 @@
|
|
|
598
808
|
if (message && slot.scrollIntoView) slot.scrollIntoView({ block: "nearest" });
|
|
599
809
|
}
|
|
600
810
|
|
|
811
|
+
var settingsEpoch = 0;
|
|
812
|
+
var themeApplied = document.documentElement.getAttribute("data-theme") || "kiln";
|
|
813
|
+
var themeQueued = null;
|
|
814
|
+
var themeSaving = false;
|
|
815
|
+
|
|
601
816
|
function setSettingsTab(tab) {
|
|
602
817
|
var repos = document.getElementById("settings-repos");
|
|
603
818
|
var globalPane = document.getElementById("settings-global");
|
|
819
|
+
var appearance = document.getElementById("settings-appearance");
|
|
604
820
|
if (repos) repos.hidden = tab !== "repos";
|
|
605
821
|
if (globalPane) globalPane.hidden = tab !== "global";
|
|
822
|
+
if (appearance) appearance.hidden = tab !== "appearance";
|
|
606
823
|
var buttons = document.querySelectorAll("#settings-form [data-settings-tab]");
|
|
607
824
|
for (var i = 0; i < buttons.length; i++) {
|
|
608
825
|
buttons[i].setAttribute("aria-pressed", buttons[i].getAttribute("data-settings-tab") === tab ? "true" : "false");
|
|
609
826
|
}
|
|
610
827
|
}
|
|
611
828
|
|
|
829
|
+
function markTheme(id) {
|
|
830
|
+
var buttons = document.querySelectorAll("#theme-pick [data-theme-choice]");
|
|
831
|
+
for (var i = 0; i < buttons.length; i++) {
|
|
832
|
+
buttons[i].setAttribute("aria-pressed", buttons[i].getAttribute("data-theme-choice") === id ? "true" : "false");
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
612
836
|
function setPressed(selector, attr, value) {
|
|
613
837
|
var buttons = document.querySelectorAll(selector);
|
|
614
838
|
for (var i = 0; i < buttons.length; i++) {
|
|
@@ -868,9 +1092,87 @@
|
|
|
868
1092
|
if (mode) sandbox.mode = mode;
|
|
869
1093
|
if (domains.length) sandbox.allowDomains = domains;
|
|
870
1094
|
if (mode || domains.length) body.sandbox = sandbox;
|
|
1095
|
+
var themeButton = document.querySelector('#theme-pick [aria-pressed="true"]');
|
|
1096
|
+
var themeId = themeButton ? themeButton.getAttribute("data-theme-choice") : "";
|
|
1097
|
+
if (themeId) body.board = { theme: themeId };
|
|
1098
|
+
return body;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
function preservedSettings(data) {
|
|
1102
|
+
var body = { revision: data.revision, repos: data.repos || {} };
|
|
1103
|
+
["stallTimeoutMs", "maxAttempts", "maxFixRounds", "maxSameFingerprint", "dailyDollarCap"].forEach(function (key) {
|
|
1104
|
+
if (data[key] !== undefined && data[key] !== null) body[key] = data[key];
|
|
1105
|
+
});
|
|
1106
|
+
if (data.sandbox) body.sandbox = data.sandbox;
|
|
871
1107
|
return body;
|
|
872
1108
|
}
|
|
873
1109
|
|
|
1110
|
+
function chooseTheme(id) {
|
|
1111
|
+
if (!id) return;
|
|
1112
|
+
settingsEpoch += 1;
|
|
1113
|
+
document.documentElement.setAttribute("data-theme", id);
|
|
1114
|
+
markTheme(id);
|
|
1115
|
+
themeQueued = id;
|
|
1116
|
+
if (!themeSaving) pumpTheme();
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
function pumpTheme() {
|
|
1120
|
+
if (!themeQueued) return;
|
|
1121
|
+
var id = themeQueued;
|
|
1122
|
+
themeQueued = null;
|
|
1123
|
+
themeSaving = true;
|
|
1124
|
+
fetch("/api/settings", { cache: "no-store", headers: { Accept: "application/json" } })
|
|
1125
|
+
.then(function (response) { return response.json().then(function (data) { return { ok: response.ok, data: data }; }); })
|
|
1126
|
+
.then(function (result) {
|
|
1127
|
+
if (!result.ok) throw new Error((result.data && result.data.error) || "Could not load settings.");
|
|
1128
|
+
var body = preservedSettings(result.data);
|
|
1129
|
+
body.board = { theme: id };
|
|
1130
|
+
return fetch("/api/settings", {
|
|
1131
|
+
method: "PUT",
|
|
1132
|
+
headers: {
|
|
1133
|
+
Accept: "application/json",
|
|
1134
|
+
"Content-Type": "application/json",
|
|
1135
|
+
"X-Kiln-CSRF": bodyCsrf(),
|
|
1136
|
+
},
|
|
1137
|
+
body: JSON.stringify(body),
|
|
1138
|
+
});
|
|
1139
|
+
})
|
|
1140
|
+
.then(function (response) { return response.json().then(function (data) { return { ok: response.ok, data: data }; }); })
|
|
1141
|
+
.then(function (result) {
|
|
1142
|
+
themeSaving = false;
|
|
1143
|
+
if (!result.ok) {
|
|
1144
|
+
if (!themeQueued) restoreTheme();
|
|
1145
|
+
showSettingsError((result.data && result.data.error) || "Could not save the theme.");
|
|
1146
|
+
var fields = result.data && result.data.fields;
|
|
1147
|
+
if (fields && fields["board.theme"]) showSlot("board.theme", fields["board.theme"]);
|
|
1148
|
+
if (themeQueued) pumpTheme();
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
themeApplied = (result.data.board && result.data.board.theme) || id;
|
|
1152
|
+
if (!settingsDoc) applySettingsDocument(result.data);
|
|
1153
|
+
else if (result.data.revision) {
|
|
1154
|
+
settingsDoc.revision = result.data.revision;
|
|
1155
|
+
settingsDoc.board = result.data.board;
|
|
1156
|
+
}
|
|
1157
|
+
if (themeQueued) pumpTheme();
|
|
1158
|
+
})
|
|
1159
|
+
.catch(function (err) {
|
|
1160
|
+
themeSaving = false;
|
|
1161
|
+
if (!themeQueued) restoreTheme();
|
|
1162
|
+
showSettingsError(err && err.message ? err.message : "Could not save the theme.");
|
|
1163
|
+
if (themeQueued) pumpTheme();
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
function restoreTheme() {
|
|
1168
|
+
document.documentElement.setAttribute("data-theme", themeApplied);
|
|
1169
|
+
markTheme(themeApplied);
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
function bodyCsrf() {
|
|
1173
|
+
return body.getAttribute("data-csrf") || "";
|
|
1174
|
+
}
|
|
1175
|
+
|
|
874
1176
|
function applyFieldErrors(fields) {
|
|
875
1177
|
var keys = Object.keys(fields || {});
|
|
876
1178
|
var lines = [];
|
|
@@ -896,6 +1198,11 @@
|
|
|
896
1198
|
showSlot(key, message);
|
|
897
1199
|
continue;
|
|
898
1200
|
}
|
|
1201
|
+
if (key === "board.theme") {
|
|
1202
|
+
setSettingsTab("appearance");
|
|
1203
|
+
showSlot("board.theme", message);
|
|
1204
|
+
continue;
|
|
1205
|
+
}
|
|
899
1206
|
if (key === "name" || key.indexOf("repos.") === 0) {
|
|
900
1207
|
setSettingsTab("repos");
|
|
901
1208
|
showSlot("name", message);
|
|
@@ -910,6 +1217,12 @@
|
|
|
910
1217
|
var pathEl = document.getElementById("settings-path");
|
|
911
1218
|
if (pathEl) pathEl.textContent = data.path || "";
|
|
912
1219
|
fillGlobal(data);
|
|
1220
|
+
var themeId = data.board && data.board.theme ? data.board.theme : "kiln";
|
|
1221
|
+
if (!themeSaving && !themeQueued) {
|
|
1222
|
+
themeApplied = themeId;
|
|
1223
|
+
document.documentElement.setAttribute("data-theme", themeId);
|
|
1224
|
+
markTheme(themeId);
|
|
1225
|
+
}
|
|
913
1226
|
if (editing && editing.original && Object.prototype.hasOwnProperty.call(repoDraft, editing.original)) {
|
|
914
1227
|
fillEditor(editing.original, repoDraft[editing.original]);
|
|
915
1228
|
} else {
|
|
@@ -922,11 +1235,13 @@
|
|
|
922
1235
|
clearSettingsErrors();
|
|
923
1236
|
var saved = document.getElementById("settings-saved");
|
|
924
1237
|
if (saved) saved.hidden = true;
|
|
1238
|
+
var epoch = settingsEpoch;
|
|
925
1239
|
fetch("/api/settings", { cache: "no-store", headers: { Accept: "application/json" } })
|
|
926
1240
|
.then(function (response) {
|
|
927
1241
|
return response.json().then(function (data) { return { ok: response.ok, status: response.status, data: data }; });
|
|
928
1242
|
})
|
|
929
1243
|
.then(function (result) {
|
|
1244
|
+
if (epoch !== settingsEpoch) return;
|
|
930
1245
|
if (!result.ok) {
|
|
931
1246
|
showSettingsError((result.data && result.data.error) || "Could not load settings.");
|
|
932
1247
|
return;
|
|
@@ -959,11 +1274,8 @@
|
|
|
959
1274
|
if (buttonId === id) on = enabled;
|
|
960
1275
|
toggles.push({ id: buttonId, enabled: on });
|
|
961
1276
|
}
|
|
962
|
-
var body =
|
|
963
|
-
|
|
964
|
-
if (data[key] !== undefined && data[key] !== null) body[key] = data[key];
|
|
965
|
-
});
|
|
966
|
-
if (data.sandbox) body.sandbox = data.sandbox;
|
|
1277
|
+
var body = preservedSettings(data);
|
|
1278
|
+
body.triggers = toggles;
|
|
967
1279
|
return fetch("/api/settings", {
|
|
968
1280
|
method: "PUT",
|
|
969
1281
|
headers: {
|
|
@@ -999,7 +1311,7 @@
|
|
|
999
1311
|
headers: {
|
|
1000
1312
|
Accept: "application/json",
|
|
1001
1313
|
"Content-Type": "application/json",
|
|
1002
|
-
"X-Kiln-CSRF":
|
|
1314
|
+
"X-Kiln-CSRF": bodyCsrf(),
|
|
1003
1315
|
},
|
|
1004
1316
|
body: JSON.stringify(payload),
|
|
1005
1317
|
})
|
|
@@ -1088,6 +1400,17 @@
|
|
|
1088
1400
|
document.addEventListener("click", function (event) {
|
|
1089
1401
|
var target = event.target;
|
|
1090
1402
|
if (!target || !target.closest) return;
|
|
1403
|
+
var option = target.closest(".combo [role='option']");
|
|
1404
|
+
if (option) {
|
|
1405
|
+
chooseComboOption(option.closest(".combo"), option);
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
var query = target.closest(".combo [role='combobox']");
|
|
1409
|
+
if (query) {
|
|
1410
|
+
openCombo(query.closest(".combo"), false);
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
if (!target.closest(".combo")) closeCombos();
|
|
1091
1414
|
var triggerToggle = target.closest("[data-trigger-toggle]");
|
|
1092
1415
|
if (triggerToggle) {
|
|
1093
1416
|
toggleTrigger(triggerToggle);
|
|
@@ -1107,10 +1430,30 @@
|
|
|
1107
1430
|
setSource(sourceBtn.getAttribute("data-source") || "prompt");
|
|
1108
1431
|
return;
|
|
1109
1432
|
}
|
|
1433
|
+
var flowPick = target.closest("#workflow-pick [data-workflow]");
|
|
1434
|
+
if (flowPick) {
|
|
1435
|
+
var flows = document.querySelectorAll("#workflow-pick [data-workflow]");
|
|
1436
|
+
for (var f = 0; f < flows.length; f++) flows[f].setAttribute("aria-pressed", flows[f] === flowPick ? "true" : "false");
|
|
1437
|
+
var flowHost = document.getElementById("workflow-pick");
|
|
1438
|
+
if (flowHost) flowHost.setAttribute("data-workflow", flowPick.getAttribute("data-workflow") || "");
|
|
1439
|
+
syncValidity();
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1110
1442
|
var pick = target.closest("#repo-pick .pick");
|
|
1111
1443
|
if (pick) {
|
|
1112
1444
|
var picks = document.querySelectorAll("#repo-pick .pick");
|
|
1113
1445
|
for (var p = 0; p < picks.length; p++) picks[p].setAttribute("aria-pressed", picks[p] === pick ? "true" : "false");
|
|
1446
|
+
var repoBox = document.getElementById("repo-pick");
|
|
1447
|
+
if (repoBox) {
|
|
1448
|
+
repoBox.setAttribute("data-value", pick.getAttribute("data-repo") || "");
|
|
1449
|
+
repoBox.setAttribute("data-path", pick.getAttribute("data-path") || "");
|
|
1450
|
+
}
|
|
1451
|
+
syncValidity();
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1454
|
+
var themeChoice = target.closest("#theme-pick [data-theme-choice]");
|
|
1455
|
+
if (themeChoice) {
|
|
1456
|
+
chooseTheme(themeChoice.getAttribute("data-theme-choice") || "");
|
|
1114
1457
|
return;
|
|
1115
1458
|
}
|
|
1116
1459
|
var settingsTab = target.closest("#settings-form [data-settings-tab]");
|
|
@@ -1173,6 +1516,7 @@
|
|
|
1173
1516
|
if (fsButton.classList.contains("fs-use")) {
|
|
1174
1517
|
ticketsDir = next;
|
|
1175
1518
|
setPicked(next);
|
|
1519
|
+
syncValidity();
|
|
1176
1520
|
return;
|
|
1177
1521
|
}
|
|
1178
1522
|
loadFs(next, false);
|
|
@@ -1186,8 +1530,19 @@
|
|
|
1186
1530
|
return;
|
|
1187
1531
|
}
|
|
1188
1532
|
if (target.closest("#notify")) {
|
|
1189
|
-
|
|
1190
|
-
|
|
1533
|
+
var mode = notifyMode();
|
|
1534
|
+
if (mode === "unavailable" || mode === "blocked") return;
|
|
1535
|
+
if (mode === "on") {
|
|
1536
|
+
setNotifyPref("off");
|
|
1537
|
+
renderNotify();
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
if (mode === "off") {
|
|
1541
|
+
setNotifyPref("on");
|
|
1542
|
+
renderNotify();
|
|
1543
|
+
return;
|
|
1544
|
+
}
|
|
1545
|
+
askNotify();
|
|
1191
1546
|
return;
|
|
1192
1547
|
}
|
|
1193
1548
|
var act = target.closest("[data-act]");
|
|
@@ -1245,6 +1600,33 @@
|
|
|
1245
1600
|
});
|
|
1246
1601
|
|
|
1247
1602
|
document.addEventListener("keydown", function (event) {
|
|
1603
|
+
var keyTarget = event.target;
|
|
1604
|
+
if (keyTarget && keyTarget.getAttribute && keyTarget.getAttribute("role") === "combobox") {
|
|
1605
|
+
var combo = keyTarget.closest(".combo");
|
|
1606
|
+
var list = combo ? combo.querySelector("[role='listbox']") : null;
|
|
1607
|
+
var comboOpen = list && !list.hidden;
|
|
1608
|
+
if (combo && (event.key === "ArrowDown" || event.key === "ArrowUp")) {
|
|
1609
|
+
event.preventDefault();
|
|
1610
|
+
if (!comboOpen) openCombo(combo, false);
|
|
1611
|
+
else setComboActive(combo, Number(combo.getAttribute("data-active") || "0") + (event.key === "ArrowDown" ? 1 : -1));
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1614
|
+
if (combo && event.key === "Enter") {
|
|
1615
|
+
event.preventDefault();
|
|
1616
|
+
if (comboOpen) {
|
|
1617
|
+
var choices = visibleOptions(combo);
|
|
1618
|
+
var index = Number(combo.getAttribute("data-active") || "0");
|
|
1619
|
+
if (choices[index]) chooseComboOption(combo, choices[index]);
|
|
1620
|
+
}
|
|
1621
|
+
return;
|
|
1622
|
+
}
|
|
1623
|
+
if (combo && event.key === "Escape" && comboOpen) {
|
|
1624
|
+
event.preventDefault();
|
|
1625
|
+
event.stopPropagation();
|
|
1626
|
+
closeCombo(combo);
|
|
1627
|
+
return;
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1248
1630
|
if (event.key === "Escape") closeDrawer();
|
|
1249
1631
|
var el = event.target;
|
|
1250
1632
|
if ((event.key === "Enter" || event.key === " ") && el && el.classList && el.classList.contains("card")) {
|
|
@@ -1284,7 +1666,8 @@
|
|
|
1284
1666
|
var toggle = document.getElementById("pause-toggle");
|
|
1285
1667
|
var form = document.getElementById("pause-form");
|
|
1286
1668
|
if (toggle && form && data && typeof data.paused === "boolean") {
|
|
1287
|
-
|
|
1669
|
+
var pauseLabel = document.getElementById("pause-label");
|
|
1670
|
+
if (pauseLabel) pauseLabel.textContent = data.paused ? "Resume" : "Pause";
|
|
1288
1671
|
toggle.setAttribute("aria-pressed", data.paused ? "true" : "false");
|
|
1289
1672
|
form.setAttribute("action", data.paused ? "/api/resume" : "/api/pause");
|
|
1290
1673
|
var label = document.getElementById("run-label");
|
|
@@ -1320,7 +1703,48 @@
|
|
|
1320
1703
|
var issueInput = document.getElementById("job-issue");
|
|
1321
1704
|
if (issueInput) issueInput.addEventListener("input", function () { issueDirty = true; });
|
|
1322
1705
|
var jiraKey = document.getElementById("jira-key");
|
|
1323
|
-
if (jiraKey) jiraKey.addEventListener("input",
|
|
1706
|
+
if (jiraKey) jiraKey.addEventListener("input", function () {
|
|
1707
|
+
syncIssueDefault();
|
|
1708
|
+
syncValidity();
|
|
1709
|
+
});
|
|
1710
|
+
document.addEventListener("input", function (event) {
|
|
1711
|
+
var field = event.target;
|
|
1712
|
+
if (!field || !field.getAttribute) return;
|
|
1713
|
+
if (field.getAttribute("role") === "combobox") {
|
|
1714
|
+
openCombo(field.closest(".combo"), true);
|
|
1715
|
+
return;
|
|
1716
|
+
}
|
|
1717
|
+
if (field.id === "prompt-text" || field.id === "workflow") syncValidity();
|
|
1718
|
+
});
|
|
1719
|
+
function intakeFieldName(target) {
|
|
1720
|
+
if (!target || !target.closest || !target.closest("#add-form")) return "";
|
|
1721
|
+
if (target.id === "prompt-text") return "prompt";
|
|
1722
|
+
if (target.id === "jira-key") return "jira";
|
|
1723
|
+
if (target.id === "workflow" || target.closest("#workflow-pick")) return "workflow";
|
|
1724
|
+
if (target.id === "repo-pick-query" || target.closest("#repo-pick")) return "repo";
|
|
1725
|
+
if (target.closest("#browser-field")) return "source";
|
|
1726
|
+
return "";
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
document.addEventListener("focusout", function (event) {
|
|
1730
|
+
var name = intakeFieldName(event.target);
|
|
1731
|
+
if (!name) return;
|
|
1732
|
+
var next = event.relatedTarget;
|
|
1733
|
+
var stay = { prompt: "#prompt-text", jira: "#jira-key", workflow: "#workflow-field", repo: "#repo-field", source: "#browser-field" };
|
|
1734
|
+
if (next && next.closest && next.closest(stay[name])) return;
|
|
1735
|
+
intakeTouched[name] = true;
|
|
1736
|
+
syncValidity();
|
|
1737
|
+
});
|
|
1738
|
+
document.addEventListener("focusin", function (event) {
|
|
1739
|
+
var field = event.target;
|
|
1740
|
+
if (!field || !field.getAttribute || field.getAttribute("role") !== "combobox") return;
|
|
1741
|
+
openCombo(field.closest(".combo"), false);
|
|
1742
|
+
if (field.select) field.select();
|
|
1743
|
+
});
|
|
1744
|
+
document.addEventListener("change", function (event) {
|
|
1745
|
+
var field = event.target;
|
|
1746
|
+
if (field && field.id === "workflow") syncValidity();
|
|
1747
|
+
});
|
|
1324
1748
|
var addForm = document.getElementById("add-form");
|
|
1325
1749
|
if (addForm) addForm.addEventListener("submit", function (event) {
|
|
1326
1750
|
event.preventDefault();
|
|
@@ -1396,8 +1820,9 @@
|
|
|
1396
1820
|
}
|
|
1397
1821
|
|
|
1398
1822
|
showFresh();
|
|
1823
|
+
syncValidity();
|
|
1399
1824
|
renderCompact();
|
|
1400
|
-
|
|
1825
|
+
renderNotify();
|
|
1401
1826
|
applyRepo();
|
|
1402
1827
|
stamp(true);
|
|
1403
1828
|
paintUnseen();
|