@lchase/kiln 0.14.0 → 0.16.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 +913 -310
- package/assets/serve.js +414 -41
- package/dist/config.js +3 -1
- package/dist/schemas.js +19 -0
- package/dist/serve-page.js +97 -29
- 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
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
if (slimCount) slimCount.textContent = String(visible);
|
|
60
60
|
var empty = col.querySelector(".empty");
|
|
61
61
|
if (empty) empty.hidden = visible !== 0;
|
|
62
|
-
tracks.push(slim ? "
|
|
62
|
+
tracks.push(slim ? "var(--col-slim)" : "minmax(var(--col-min), 1fr)");
|
|
63
63
|
}
|
|
64
64
|
board.style.setProperty("--cols", tracks.join(" "));
|
|
65
65
|
}
|
|
@@ -284,16 +284,204 @@
|
|
|
284
284
|
var ticketsDir = "";
|
|
285
285
|
var fsPath = "";
|
|
286
286
|
var sending = false;
|
|
287
|
+
var intakeTouched = {};
|
|
288
|
+
var intakeSubmitTried = false;
|
|
287
289
|
var FS_KEY = "kiln-fs-dir";
|
|
288
290
|
|
|
291
|
+
function pickLimit() {
|
|
292
|
+
var n = Number(body.getAttribute("data-pick-limit") || "5");
|
|
293
|
+
return n > 0 ? n : 5;
|
|
294
|
+
}
|
|
295
|
+
|
|
289
296
|
function selectedRepo() {
|
|
290
|
-
var
|
|
291
|
-
return
|
|
297
|
+
var box = document.getElementById("repo-pick");
|
|
298
|
+
return box ? box.getAttribute("data-value") || "" : "";
|
|
292
299
|
}
|
|
293
300
|
|
|
294
301
|
function intakeRepoPath() {
|
|
295
|
-
var
|
|
296
|
-
return
|
|
302
|
+
var box = document.getElementById("repo-pick");
|
|
303
|
+
return box ? box.getAttribute("data-path") || "" : "";
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function selectedWorkflow() {
|
|
307
|
+
var select = document.getElementById("workflow");
|
|
308
|
+
if (select) return select.value || "";
|
|
309
|
+
var host = document.getElementById("workflow-pick");
|
|
310
|
+
return host ? host.getAttribute("data-workflow") || "" : "";
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function setFieldError(id, message, reveal) {
|
|
314
|
+
var el = document.getElementById(id);
|
|
315
|
+
if (!el) return;
|
|
316
|
+
var on = Boolean(message) && reveal;
|
|
317
|
+
el.hidden = !on;
|
|
318
|
+
el.textContent = on ? message : "";
|
|
319
|
+
var field = el.closest(".field");
|
|
320
|
+
if (!field) return;
|
|
321
|
+
var control = field.querySelector("textarea, input, select, [role='combobox'], [role='group']");
|
|
322
|
+
if (!control) return;
|
|
323
|
+
if (on) control.setAttribute("aria-invalid", "true");
|
|
324
|
+
else control.removeAttribute("aria-invalid");
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function showFieldError(name) {
|
|
328
|
+
return intakeSubmitTried || intakeTouched[name] === true;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function syncValidity() {
|
|
332
|
+
var repo = selectedRepo();
|
|
333
|
+
var workflow = selectedWorkflow();
|
|
334
|
+
var source = sourceBody();
|
|
335
|
+
var prompt = source.kind === "prompt" && !String(source.text || "").trim() ? "Prompt is required." : "";
|
|
336
|
+
var spec = source.kind === "spec" && !source.path ? "Choose a spec file." : "";
|
|
337
|
+
var tickets = source.kind === "tickets" && !source.dir ? "Choose a tickets folder." : "";
|
|
338
|
+
var jira = source.kind === "jira" && !source.key ? "Enter a Jira key." : "";
|
|
339
|
+
setFieldError("repo-error", repo ? "" : "Choose a repo.", showFieldError("repo"));
|
|
340
|
+
setFieldError("workflow-error", workflow ? "" : "Choose a workflow.", showFieldError("workflow"));
|
|
341
|
+
setFieldError("prompt-error", prompt, showFieldError("prompt"));
|
|
342
|
+
setFieldError("source-error", spec || tickets, showFieldError("source"));
|
|
343
|
+
setFieldError("jira-error", jira, showFieldError("jira"));
|
|
344
|
+
var blocked = !repo || !workflow || Boolean(prompt || spec || tickets || jira);
|
|
345
|
+
var submit = document.getElementById("add-submit");
|
|
346
|
+
if (submit) submit.disabled = blocked || sending;
|
|
347
|
+
var preview = document.getElementById("preview-job");
|
|
348
|
+
if (preview) preview.disabled = blocked || sending;
|
|
349
|
+
return !blocked;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function repoListKey(repos) {
|
|
353
|
+
var parts = [];
|
|
354
|
+
for (var i = 0; i < repos.length; i++) {
|
|
355
|
+
var repo = repos[i];
|
|
356
|
+
if (!repo || !repo.name) continue;
|
|
357
|
+
parts.push(repo.name + "\t" + (repo.path || ""));
|
|
358
|
+
}
|
|
359
|
+
return parts.join("\n");
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function paintRepos(repos) {
|
|
363
|
+
var box = document.getElementById("repo-pick");
|
|
364
|
+
if (!box) return;
|
|
365
|
+
var items = [];
|
|
366
|
+
for (var i = 0; i < repos.length; i++) if (repos[i] && repos[i].name) items.push(repos[i]);
|
|
367
|
+
var key = repoListKey(items);
|
|
368
|
+
if (box.getAttribute("data-list") === key) return;
|
|
369
|
+
var current = box.getAttribute("data-value") || "";
|
|
370
|
+
var selected = null;
|
|
371
|
+
for (var s = 0; s < items.length; s++) if (items[s].name === current) selected = items[s];
|
|
372
|
+
if (!selected && items.length) selected = items[0];
|
|
373
|
+
var value = selected ? selected.name : "";
|
|
374
|
+
var path = selected ? (selected.path || "") : "";
|
|
375
|
+
var mode = !items.length ? "empty" : items.length > pickLimit() ? "select" : "chips";
|
|
376
|
+
var html = "";
|
|
377
|
+
if (mode === "empty") html = '<p class="empty">No repos are registered.</p>';
|
|
378
|
+
if (mode === "chips") {
|
|
379
|
+
for (var c = 0; c < items.length; c++) {
|
|
380
|
+
var chip = items[c];
|
|
381
|
+
var pressed = chip.name === value;
|
|
382
|
+
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>";
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if (mode === "select") {
|
|
386
|
+
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">';
|
|
387
|
+
html += '<ul class="combo-list" id="repo-pick-list" role="listbox" hidden>';
|
|
388
|
+
for (var o = 0; o < items.length; o++) {
|
|
389
|
+
var opt = items[o];
|
|
390
|
+
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>";
|
|
391
|
+
}
|
|
392
|
+
html += '<li class="empty" role="presentation" hidden>No matching repos.</li></ul>';
|
|
393
|
+
}
|
|
394
|
+
box.className = mode === "select" ? "combo" : "picks";
|
|
395
|
+
box.setAttribute("data-mode", mode);
|
|
396
|
+
box.setAttribute("data-value", value);
|
|
397
|
+
box.setAttribute("data-path", path);
|
|
398
|
+
box.setAttribute("data-list", key);
|
|
399
|
+
if (mode === "select") {
|
|
400
|
+
box.removeAttribute("role");
|
|
401
|
+
box.removeAttribute("aria-labelledby");
|
|
402
|
+
} else {
|
|
403
|
+
box.setAttribute("role", "group");
|
|
404
|
+
box.setAttribute("aria-labelledby", "repo-label");
|
|
405
|
+
}
|
|
406
|
+
box.innerHTML = html;
|
|
407
|
+
syncValidity();
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function visibleOptions(box) {
|
|
411
|
+
var all = box.querySelectorAll("[role='option']");
|
|
412
|
+
var out = [];
|
|
413
|
+
for (var i = 0; i < all.length; i++) if (!all[i].hidden) out.push(all[i]);
|
|
414
|
+
return out;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function applyComboFilter(box, query) {
|
|
418
|
+
var q = String(query || "").trim().toLowerCase();
|
|
419
|
+
var options = box.querySelectorAll("[role='option']");
|
|
420
|
+
var shown = 0;
|
|
421
|
+
for (var i = 0; i < options.length; i++) {
|
|
422
|
+
var name = (options[i].getAttribute("data-value") || options[i].textContent || "").toLowerCase();
|
|
423
|
+
var hide = q.length > 0 && name.indexOf(q) === -1;
|
|
424
|
+
options[i].hidden = hide;
|
|
425
|
+
if (!hide) shown += 1;
|
|
426
|
+
}
|
|
427
|
+
var empty = box.querySelector(".combo-list .empty");
|
|
428
|
+
if (empty) empty.hidden = shown !== 0;
|
|
429
|
+
setComboActive(box, 0);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function setComboActive(box, index) {
|
|
433
|
+
var options = visibleOptions(box);
|
|
434
|
+
var input = box.querySelector("[role='combobox']");
|
|
435
|
+
if (!options.length) {
|
|
436
|
+
box.removeAttribute("data-active");
|
|
437
|
+
if (input) input.removeAttribute("aria-activedescendant");
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
if (index < 0) index = options.length - 1;
|
|
441
|
+
if (index >= options.length) index = 0;
|
|
442
|
+
box.setAttribute("data-active", String(index));
|
|
443
|
+
for (var i = 0; i < options.length; i++) options[i].classList.toggle("active", i === index);
|
|
444
|
+
if (input && options[index].id) input.setAttribute("aria-activedescendant", options[index].id);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function openCombo(box, filter) {
|
|
448
|
+
if (!box) return;
|
|
449
|
+
var list = box.querySelector("[role='listbox']");
|
|
450
|
+
var input = box.querySelector("[role='combobox']");
|
|
451
|
+
if (!list || !input) return;
|
|
452
|
+
list.hidden = false;
|
|
453
|
+
input.setAttribute("aria-expanded", "true");
|
|
454
|
+
applyComboFilter(box, filter ? input.value : "");
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function closeCombo(box) {
|
|
458
|
+
var list = box.querySelector("[role='listbox']");
|
|
459
|
+
var input = box.querySelector("[role='combobox']");
|
|
460
|
+
if (list) list.hidden = true;
|
|
461
|
+
if (input) {
|
|
462
|
+
input.setAttribute("aria-expanded", "false");
|
|
463
|
+
input.removeAttribute("aria-activedescendant");
|
|
464
|
+
input.value = box.getAttribute("data-value") || "";
|
|
465
|
+
}
|
|
466
|
+
box.removeAttribute("data-active");
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function closeCombos(except) {
|
|
470
|
+
var boxes = document.querySelectorAll(".combo");
|
|
471
|
+
for (var i = 0; i < boxes.length; i++) if (boxes[i] !== except) closeCombo(boxes[i]);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function chooseComboOption(box, option) {
|
|
475
|
+
if (!box || !option) return;
|
|
476
|
+
var value = option.getAttribute("data-value") || "";
|
|
477
|
+
box.setAttribute("data-value", value);
|
|
478
|
+
if (option.hasAttribute("data-path")) box.setAttribute("data-path", option.getAttribute("data-path") || "");
|
|
479
|
+
var options = box.querySelectorAll("[role='option']");
|
|
480
|
+
for (var i = 0; i < options.length; i++) {
|
|
481
|
+
options[i].setAttribute("aria-selected", options[i] === option ? "true" : "false");
|
|
482
|
+
}
|
|
483
|
+
closeCombo(box);
|
|
484
|
+
syncValidity();
|
|
297
485
|
}
|
|
298
486
|
|
|
299
487
|
function showIntakeError(message) {
|
|
@@ -346,6 +534,7 @@
|
|
|
346
534
|
if (sourceKind === "tickets") html += '<button type="button" class="fs-use" data-path="' + esc(fsPath) + '">Use this folder</button>';
|
|
347
535
|
host.innerHTML = html;
|
|
348
536
|
if (sourceKind === "tickets") setPicked(fsPath);
|
|
537
|
+
syncValidity();
|
|
349
538
|
}
|
|
350
539
|
|
|
351
540
|
function chooseSpec(data) {
|
|
@@ -356,6 +545,7 @@
|
|
|
356
545
|
var input = document.getElementById("job-title");
|
|
357
546
|
if (input && heading) input.value = heading;
|
|
358
547
|
}
|
|
548
|
+
syncValidity();
|
|
359
549
|
}
|
|
360
550
|
|
|
361
551
|
function loadFs(path, fallback) {
|
|
@@ -417,6 +607,7 @@
|
|
|
417
607
|
}
|
|
418
608
|
syncIssueDefault();
|
|
419
609
|
if (sourceKind === "spec" || sourceKind === "tickets") openBrowser();
|
|
610
|
+
syncValidity();
|
|
420
611
|
}
|
|
421
612
|
|
|
422
613
|
function syncIssueDefault() {
|
|
@@ -451,15 +642,12 @@
|
|
|
451
642
|
|
|
452
643
|
function submitIntake(dryRun) {
|
|
453
644
|
if (sending) return;
|
|
645
|
+
intakeSubmitTried = true;
|
|
646
|
+
if (!syncValidity()) return;
|
|
454
647
|
var repo = selectedRepo();
|
|
455
|
-
var workflow =
|
|
648
|
+
var workflow = selectedWorkflow();
|
|
456
649
|
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 };
|
|
650
|
+
var payload = { repo: repo, workflow: workflow, source: source };
|
|
463
651
|
if (dryRun) payload.dryRun = true;
|
|
464
652
|
if (sourceKind !== "tickets") {
|
|
465
653
|
var issue = document.getElementById("job-issue");
|
|
@@ -473,6 +661,7 @@
|
|
|
473
661
|
}
|
|
474
662
|
}
|
|
475
663
|
sending = true;
|
|
664
|
+
syncValidity();
|
|
476
665
|
showIntakeError("");
|
|
477
666
|
fetch("/api/jobs", {
|
|
478
667
|
method: "POST",
|
|
@@ -488,6 +677,7 @@
|
|
|
488
677
|
})
|
|
489
678
|
.then(function (result) {
|
|
490
679
|
sending = false;
|
|
680
|
+
syncValidity();
|
|
491
681
|
if (!result.ok) {
|
|
492
682
|
showIntakeError((result.data && result.data.error) || "Could not add the job.");
|
|
493
683
|
return;
|
|
@@ -507,6 +697,7 @@
|
|
|
507
697
|
})
|
|
508
698
|
.catch(function () {
|
|
509
699
|
sending = false;
|
|
700
|
+
syncValidity();
|
|
510
701
|
showIntakeError("Could not add the job.");
|
|
511
702
|
});
|
|
512
703
|
}
|
|
@@ -523,28 +714,7 @@
|
|
|
523
714
|
}
|
|
524
715
|
}
|
|
525
716
|
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;
|
|
717
|
+
paintRepos(data.repos);
|
|
548
718
|
}
|
|
549
719
|
|
|
550
720
|
function showFresh() {
|
|
@@ -598,17 +768,31 @@
|
|
|
598
768
|
if (message && slot.scrollIntoView) slot.scrollIntoView({ block: "nearest" });
|
|
599
769
|
}
|
|
600
770
|
|
|
771
|
+
var settingsEpoch = 0;
|
|
772
|
+
var themeApplied = document.documentElement.getAttribute("data-theme") || "kiln";
|
|
773
|
+
var themeQueued = null;
|
|
774
|
+
var themeSaving = false;
|
|
775
|
+
|
|
601
776
|
function setSettingsTab(tab) {
|
|
602
777
|
var repos = document.getElementById("settings-repos");
|
|
603
778
|
var globalPane = document.getElementById("settings-global");
|
|
779
|
+
var appearance = document.getElementById("settings-appearance");
|
|
604
780
|
if (repos) repos.hidden = tab !== "repos";
|
|
605
781
|
if (globalPane) globalPane.hidden = tab !== "global";
|
|
782
|
+
if (appearance) appearance.hidden = tab !== "appearance";
|
|
606
783
|
var buttons = document.querySelectorAll("#settings-form [data-settings-tab]");
|
|
607
784
|
for (var i = 0; i < buttons.length; i++) {
|
|
608
785
|
buttons[i].setAttribute("aria-pressed", buttons[i].getAttribute("data-settings-tab") === tab ? "true" : "false");
|
|
609
786
|
}
|
|
610
787
|
}
|
|
611
788
|
|
|
789
|
+
function markTheme(id) {
|
|
790
|
+
var buttons = document.querySelectorAll("#theme-pick [data-theme-choice]");
|
|
791
|
+
for (var i = 0; i < buttons.length; i++) {
|
|
792
|
+
buttons[i].setAttribute("aria-pressed", buttons[i].getAttribute("data-theme-choice") === id ? "true" : "false");
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
612
796
|
function setPressed(selector, attr, value) {
|
|
613
797
|
var buttons = document.querySelectorAll(selector);
|
|
614
798
|
for (var i = 0; i < buttons.length; i++) {
|
|
@@ -868,9 +1052,87 @@
|
|
|
868
1052
|
if (mode) sandbox.mode = mode;
|
|
869
1053
|
if (domains.length) sandbox.allowDomains = domains;
|
|
870
1054
|
if (mode || domains.length) body.sandbox = sandbox;
|
|
1055
|
+
var themeButton = document.querySelector('#theme-pick [aria-pressed="true"]');
|
|
1056
|
+
var themeId = themeButton ? themeButton.getAttribute("data-theme-choice") : "";
|
|
1057
|
+
if (themeId) body.board = { theme: themeId };
|
|
1058
|
+
return body;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
function preservedSettings(data) {
|
|
1062
|
+
var body = { revision: data.revision, repos: data.repos || {} };
|
|
1063
|
+
["stallTimeoutMs", "maxAttempts", "maxFixRounds", "maxSameFingerprint", "dailyDollarCap"].forEach(function (key) {
|
|
1064
|
+
if (data[key] !== undefined && data[key] !== null) body[key] = data[key];
|
|
1065
|
+
});
|
|
1066
|
+
if (data.sandbox) body.sandbox = data.sandbox;
|
|
871
1067
|
return body;
|
|
872
1068
|
}
|
|
873
1069
|
|
|
1070
|
+
function chooseTheme(id) {
|
|
1071
|
+
if (!id) return;
|
|
1072
|
+
settingsEpoch += 1;
|
|
1073
|
+
document.documentElement.setAttribute("data-theme", id);
|
|
1074
|
+
markTheme(id);
|
|
1075
|
+
themeQueued = id;
|
|
1076
|
+
if (!themeSaving) pumpTheme();
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
function pumpTheme() {
|
|
1080
|
+
if (!themeQueued) return;
|
|
1081
|
+
var id = themeQueued;
|
|
1082
|
+
themeQueued = null;
|
|
1083
|
+
themeSaving = true;
|
|
1084
|
+
fetch("/api/settings", { cache: "no-store", headers: { Accept: "application/json" } })
|
|
1085
|
+
.then(function (response) { return response.json().then(function (data) { return { ok: response.ok, data: data }; }); })
|
|
1086
|
+
.then(function (result) {
|
|
1087
|
+
if (!result.ok) throw new Error((result.data && result.data.error) || "Could not load settings.");
|
|
1088
|
+
var body = preservedSettings(result.data);
|
|
1089
|
+
body.board = { theme: id };
|
|
1090
|
+
return fetch("/api/settings", {
|
|
1091
|
+
method: "PUT",
|
|
1092
|
+
headers: {
|
|
1093
|
+
Accept: "application/json",
|
|
1094
|
+
"Content-Type": "application/json",
|
|
1095
|
+
"X-Kiln-CSRF": bodyCsrf(),
|
|
1096
|
+
},
|
|
1097
|
+
body: JSON.stringify(body),
|
|
1098
|
+
});
|
|
1099
|
+
})
|
|
1100
|
+
.then(function (response) { return response.json().then(function (data) { return { ok: response.ok, data: data }; }); })
|
|
1101
|
+
.then(function (result) {
|
|
1102
|
+
themeSaving = false;
|
|
1103
|
+
if (!result.ok) {
|
|
1104
|
+
if (!themeQueued) restoreTheme();
|
|
1105
|
+
showSettingsError((result.data && result.data.error) || "Could not save the theme.");
|
|
1106
|
+
var fields = result.data && result.data.fields;
|
|
1107
|
+
if (fields && fields["board.theme"]) showSlot("board.theme", fields["board.theme"]);
|
|
1108
|
+
if (themeQueued) pumpTheme();
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
themeApplied = (result.data.board && result.data.board.theme) || id;
|
|
1112
|
+
if (!settingsDoc) applySettingsDocument(result.data);
|
|
1113
|
+
else if (result.data.revision) {
|
|
1114
|
+
settingsDoc.revision = result.data.revision;
|
|
1115
|
+
settingsDoc.board = result.data.board;
|
|
1116
|
+
}
|
|
1117
|
+
if (themeQueued) pumpTheme();
|
|
1118
|
+
})
|
|
1119
|
+
.catch(function (err) {
|
|
1120
|
+
themeSaving = false;
|
|
1121
|
+
if (!themeQueued) restoreTheme();
|
|
1122
|
+
showSettingsError(err && err.message ? err.message : "Could not save the theme.");
|
|
1123
|
+
if (themeQueued) pumpTheme();
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
function restoreTheme() {
|
|
1128
|
+
document.documentElement.setAttribute("data-theme", themeApplied);
|
|
1129
|
+
markTheme(themeApplied);
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
function bodyCsrf() {
|
|
1133
|
+
return body.getAttribute("data-csrf") || "";
|
|
1134
|
+
}
|
|
1135
|
+
|
|
874
1136
|
function applyFieldErrors(fields) {
|
|
875
1137
|
var keys = Object.keys(fields || {});
|
|
876
1138
|
var lines = [];
|
|
@@ -896,6 +1158,11 @@
|
|
|
896
1158
|
showSlot(key, message);
|
|
897
1159
|
continue;
|
|
898
1160
|
}
|
|
1161
|
+
if (key === "board.theme") {
|
|
1162
|
+
setSettingsTab("appearance");
|
|
1163
|
+
showSlot("board.theme", message);
|
|
1164
|
+
continue;
|
|
1165
|
+
}
|
|
899
1166
|
if (key === "name" || key.indexOf("repos.") === 0) {
|
|
900
1167
|
setSettingsTab("repos");
|
|
901
1168
|
showSlot("name", message);
|
|
@@ -910,6 +1177,12 @@
|
|
|
910
1177
|
var pathEl = document.getElementById("settings-path");
|
|
911
1178
|
if (pathEl) pathEl.textContent = data.path || "";
|
|
912
1179
|
fillGlobal(data);
|
|
1180
|
+
var themeId = data.board && data.board.theme ? data.board.theme : "kiln";
|
|
1181
|
+
if (!themeSaving && !themeQueued) {
|
|
1182
|
+
themeApplied = themeId;
|
|
1183
|
+
document.documentElement.setAttribute("data-theme", themeId);
|
|
1184
|
+
markTheme(themeId);
|
|
1185
|
+
}
|
|
913
1186
|
if (editing && editing.original && Object.prototype.hasOwnProperty.call(repoDraft, editing.original)) {
|
|
914
1187
|
fillEditor(editing.original, repoDraft[editing.original]);
|
|
915
1188
|
} else {
|
|
@@ -922,11 +1195,13 @@
|
|
|
922
1195
|
clearSettingsErrors();
|
|
923
1196
|
var saved = document.getElementById("settings-saved");
|
|
924
1197
|
if (saved) saved.hidden = true;
|
|
1198
|
+
var epoch = settingsEpoch;
|
|
925
1199
|
fetch("/api/settings", { cache: "no-store", headers: { Accept: "application/json" } })
|
|
926
1200
|
.then(function (response) {
|
|
927
1201
|
return response.json().then(function (data) { return { ok: response.ok, status: response.status, data: data }; });
|
|
928
1202
|
})
|
|
929
1203
|
.then(function (result) {
|
|
1204
|
+
if (epoch !== settingsEpoch) return;
|
|
930
1205
|
if (!result.ok) {
|
|
931
1206
|
showSettingsError((result.data && result.data.error) || "Could not load settings.");
|
|
932
1207
|
return;
|
|
@@ -959,11 +1234,8 @@
|
|
|
959
1234
|
if (buttonId === id) on = enabled;
|
|
960
1235
|
toggles.push({ id: buttonId, enabled: on });
|
|
961
1236
|
}
|
|
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;
|
|
1237
|
+
var body = preservedSettings(data);
|
|
1238
|
+
body.triggers = toggles;
|
|
967
1239
|
return fetch("/api/settings", {
|
|
968
1240
|
method: "PUT",
|
|
969
1241
|
headers: {
|
|
@@ -999,7 +1271,7 @@
|
|
|
999
1271
|
headers: {
|
|
1000
1272
|
Accept: "application/json",
|
|
1001
1273
|
"Content-Type": "application/json",
|
|
1002
|
-
"X-Kiln-CSRF":
|
|
1274
|
+
"X-Kiln-CSRF": bodyCsrf(),
|
|
1003
1275
|
},
|
|
1004
1276
|
body: JSON.stringify(payload),
|
|
1005
1277
|
})
|
|
@@ -1088,6 +1360,17 @@
|
|
|
1088
1360
|
document.addEventListener("click", function (event) {
|
|
1089
1361
|
var target = event.target;
|
|
1090
1362
|
if (!target || !target.closest) return;
|
|
1363
|
+
var option = target.closest(".combo [role='option']");
|
|
1364
|
+
if (option) {
|
|
1365
|
+
chooseComboOption(option.closest(".combo"), option);
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
var query = target.closest(".combo [role='combobox']");
|
|
1369
|
+
if (query) {
|
|
1370
|
+
openCombo(query.closest(".combo"), false);
|
|
1371
|
+
return;
|
|
1372
|
+
}
|
|
1373
|
+
if (!target.closest(".combo")) closeCombos();
|
|
1091
1374
|
var triggerToggle = target.closest("[data-trigger-toggle]");
|
|
1092
1375
|
if (triggerToggle) {
|
|
1093
1376
|
toggleTrigger(triggerToggle);
|
|
@@ -1107,10 +1390,30 @@
|
|
|
1107
1390
|
setSource(sourceBtn.getAttribute("data-source") || "prompt");
|
|
1108
1391
|
return;
|
|
1109
1392
|
}
|
|
1393
|
+
var flowPick = target.closest("#workflow-pick [data-workflow]");
|
|
1394
|
+
if (flowPick) {
|
|
1395
|
+
var flows = document.querySelectorAll("#workflow-pick [data-workflow]");
|
|
1396
|
+
for (var f = 0; f < flows.length; f++) flows[f].setAttribute("aria-pressed", flows[f] === flowPick ? "true" : "false");
|
|
1397
|
+
var flowHost = document.getElementById("workflow-pick");
|
|
1398
|
+
if (flowHost) flowHost.setAttribute("data-workflow", flowPick.getAttribute("data-workflow") || "");
|
|
1399
|
+
syncValidity();
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1110
1402
|
var pick = target.closest("#repo-pick .pick");
|
|
1111
1403
|
if (pick) {
|
|
1112
1404
|
var picks = document.querySelectorAll("#repo-pick .pick");
|
|
1113
1405
|
for (var p = 0; p < picks.length; p++) picks[p].setAttribute("aria-pressed", picks[p] === pick ? "true" : "false");
|
|
1406
|
+
var repoBox = document.getElementById("repo-pick");
|
|
1407
|
+
if (repoBox) {
|
|
1408
|
+
repoBox.setAttribute("data-value", pick.getAttribute("data-repo") || "");
|
|
1409
|
+
repoBox.setAttribute("data-path", pick.getAttribute("data-path") || "");
|
|
1410
|
+
}
|
|
1411
|
+
syncValidity();
|
|
1412
|
+
return;
|
|
1413
|
+
}
|
|
1414
|
+
var themeChoice = target.closest("#theme-pick [data-theme-choice]");
|
|
1415
|
+
if (themeChoice) {
|
|
1416
|
+
chooseTheme(themeChoice.getAttribute("data-theme-choice") || "");
|
|
1114
1417
|
return;
|
|
1115
1418
|
}
|
|
1116
1419
|
var settingsTab = target.closest("#settings-form [data-settings-tab]");
|
|
@@ -1173,6 +1476,7 @@
|
|
|
1173
1476
|
if (fsButton.classList.contains("fs-use")) {
|
|
1174
1477
|
ticketsDir = next;
|
|
1175
1478
|
setPicked(next);
|
|
1479
|
+
syncValidity();
|
|
1176
1480
|
return;
|
|
1177
1481
|
}
|
|
1178
1482
|
loadFs(next, false);
|
|
@@ -1245,6 +1549,33 @@
|
|
|
1245
1549
|
});
|
|
1246
1550
|
|
|
1247
1551
|
document.addEventListener("keydown", function (event) {
|
|
1552
|
+
var keyTarget = event.target;
|
|
1553
|
+
if (keyTarget && keyTarget.getAttribute && keyTarget.getAttribute("role") === "combobox") {
|
|
1554
|
+
var combo = keyTarget.closest(".combo");
|
|
1555
|
+
var list = combo ? combo.querySelector("[role='listbox']") : null;
|
|
1556
|
+
var comboOpen = list && !list.hidden;
|
|
1557
|
+
if (combo && (event.key === "ArrowDown" || event.key === "ArrowUp")) {
|
|
1558
|
+
event.preventDefault();
|
|
1559
|
+
if (!comboOpen) openCombo(combo, false);
|
|
1560
|
+
else setComboActive(combo, Number(combo.getAttribute("data-active") || "0") + (event.key === "ArrowDown" ? 1 : -1));
|
|
1561
|
+
return;
|
|
1562
|
+
}
|
|
1563
|
+
if (combo && event.key === "Enter") {
|
|
1564
|
+
event.preventDefault();
|
|
1565
|
+
if (comboOpen) {
|
|
1566
|
+
var choices = visibleOptions(combo);
|
|
1567
|
+
var index = Number(combo.getAttribute("data-active") || "0");
|
|
1568
|
+
if (choices[index]) chooseComboOption(combo, choices[index]);
|
|
1569
|
+
}
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1572
|
+
if (combo && event.key === "Escape" && comboOpen) {
|
|
1573
|
+
event.preventDefault();
|
|
1574
|
+
event.stopPropagation();
|
|
1575
|
+
closeCombo(combo);
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1248
1579
|
if (event.key === "Escape") closeDrawer();
|
|
1249
1580
|
var el = event.target;
|
|
1250
1581
|
if ((event.key === "Enter" || event.key === " ") && el && el.classList && el.classList.contains("card")) {
|
|
@@ -1320,7 +1651,48 @@
|
|
|
1320
1651
|
var issueInput = document.getElementById("job-issue");
|
|
1321
1652
|
if (issueInput) issueInput.addEventListener("input", function () { issueDirty = true; });
|
|
1322
1653
|
var jiraKey = document.getElementById("jira-key");
|
|
1323
|
-
if (jiraKey) jiraKey.addEventListener("input",
|
|
1654
|
+
if (jiraKey) jiraKey.addEventListener("input", function () {
|
|
1655
|
+
syncIssueDefault();
|
|
1656
|
+
syncValidity();
|
|
1657
|
+
});
|
|
1658
|
+
document.addEventListener("input", function (event) {
|
|
1659
|
+
var field = event.target;
|
|
1660
|
+
if (!field || !field.getAttribute) return;
|
|
1661
|
+
if (field.getAttribute("role") === "combobox") {
|
|
1662
|
+
openCombo(field.closest(".combo"), true);
|
|
1663
|
+
return;
|
|
1664
|
+
}
|
|
1665
|
+
if (field.id === "prompt-text" || field.id === "workflow") syncValidity();
|
|
1666
|
+
});
|
|
1667
|
+
function intakeFieldName(target) {
|
|
1668
|
+
if (!target || !target.closest || !target.closest("#add-form")) return "";
|
|
1669
|
+
if (target.id === "prompt-text") return "prompt";
|
|
1670
|
+
if (target.id === "jira-key") return "jira";
|
|
1671
|
+
if (target.id === "workflow" || target.closest("#workflow-pick")) return "workflow";
|
|
1672
|
+
if (target.id === "repo-pick-query" || target.closest("#repo-pick")) return "repo";
|
|
1673
|
+
if (target.closest("#browser-field")) return "source";
|
|
1674
|
+
return "";
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
document.addEventListener("focusout", function (event) {
|
|
1678
|
+
var name = intakeFieldName(event.target);
|
|
1679
|
+
if (!name) return;
|
|
1680
|
+
var next = event.relatedTarget;
|
|
1681
|
+
var stay = { prompt: "#prompt-text", jira: "#jira-key", workflow: "#workflow-field", repo: "#repo-field", source: "#browser-field" };
|
|
1682
|
+
if (next && next.closest && next.closest(stay[name])) return;
|
|
1683
|
+
intakeTouched[name] = true;
|
|
1684
|
+
syncValidity();
|
|
1685
|
+
});
|
|
1686
|
+
document.addEventListener("focusin", function (event) {
|
|
1687
|
+
var field = event.target;
|
|
1688
|
+
if (!field || !field.getAttribute || field.getAttribute("role") !== "combobox") return;
|
|
1689
|
+
openCombo(field.closest(".combo"), false);
|
|
1690
|
+
if (field.select) field.select();
|
|
1691
|
+
});
|
|
1692
|
+
document.addEventListener("change", function (event) {
|
|
1693
|
+
var field = event.target;
|
|
1694
|
+
if (field && field.id === "workflow") syncValidity();
|
|
1695
|
+
});
|
|
1324
1696
|
var addForm = document.getElementById("add-form");
|
|
1325
1697
|
if (addForm) addForm.addEventListener("submit", function (event) {
|
|
1326
1698
|
event.preventDefault();
|
|
@@ -1396,6 +1768,7 @@
|
|
|
1396
1768
|
}
|
|
1397
1769
|
|
|
1398
1770
|
showFresh();
|
|
1771
|
+
syncValidity();
|
|
1399
1772
|
renderCompact();
|
|
1400
1773
|
notifyLabel();
|
|
1401
1774
|
applyRepo();
|
package/dist/config.js
CHANGED
|
@@ -3,6 +3,7 @@ import { homedir } from "node:os";
|
|
|
3
3
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
4
4
|
import { KilnConfigError } from "./errors.js";
|
|
5
5
|
import { DEFAULT_MAX_ATTEMPTS, DEFAULT_MAX_FIX_ROUNDS, DEFAULT_MAX_SAME_FINGERPRINT, DEFAULT_STALL_TIMEOUT_MS, KilnConfigFileSchema, REPO_NAME, } from "./schemas.js";
|
|
6
|
+
import { resolveBoardTheme } from "./theme.js";
|
|
6
7
|
const CONFIG_NAME = "kiln.config.json";
|
|
7
8
|
export function loadKilnConfig(configPath, options = {}) {
|
|
8
9
|
const home = options.home ?? homedir();
|
|
@@ -60,6 +61,7 @@ export function loadKilnConfig(configPath, options = {}) {
|
|
|
60
61
|
...(parsed.data.reviewPrompt
|
|
61
62
|
? { reviewPrompt: resolveConfiguredPath(configDir, parsed.data.reviewPrompt, home) }
|
|
62
63
|
: {}),
|
|
64
|
+
boardTheme: resolveBoardTheme(parsed.data.board?.theme),
|
|
63
65
|
};
|
|
64
66
|
}
|
|
65
67
|
/**
|
|
@@ -230,7 +232,7 @@ function assertNoStaleCwdConfig(cwd, homeConfig) {
|
|
|
230
232
|
}
|
|
231
233
|
function readConfigObject(configPath) {
|
|
232
234
|
if (!existsSync(configPath))
|
|
233
|
-
return { repos: {} };
|
|
235
|
+
return { repos: {}, board: { theme: "kiln" } };
|
|
234
236
|
let raw;
|
|
235
237
|
try {
|
|
236
238
|
raw = JSON.parse(readFileSync(configPath, "utf8"));
|