@uniqbit/mate-core 0.18.1-canary.4 → 0.18.1
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/package.json +1 -1
- package/src/cli/commands/report/delivery.ts +50 -0
- package/src/cli/commands/report/index.ts +22 -4
- package/src/cli/commands/studio/access.ts +6 -0
- package/src/cli/commands/studio/actions.ts +87 -0
- package/src/cli/commands/studio/reports.ts +176 -0
- package/src/cli/commands/studio/routes.ts +12 -0
- package/src/cli/commands/studio/selection.ts +2 -1
- package/src/cli/commands/studio/server.ts +276 -4
- package/src/cli/commands/studio/terminal-launch.ts +38 -3
- package/src/cli/commands/studio/terminal.ts +48 -4
- package/src/cli/commands/studio/vault.ts +55 -8
- package/src/cli/commands/studio/views/client.ts +263 -27
- package/src/cli/commands/studio/views/document.tsx +168 -20
- package/src/cli/commands/studio/views/model.ts +2 -0
- package/src/cli/commands/studio/views/reports/index.tsx +49 -0
- package/src/cli/commands/studio/views/specs/index.tsx +65 -9
- package/src/cli/commands/studio/views/styles.ts +26 -6
- package/src/cli/commands/studio/views/terminal.tsx +62 -4
- package/src/cli/commands/studio/views/vault/state.ts +1 -0
- package/src/templates/mate-skills/agents/mate-show-me/SKILL.md +11 -5
- package/src/templates/mate-skills/claude/mate-show-me/SKILL.md +11 -5
- package/src/tools/setup/__snapshots__/runtime-surface-golden.test.ts.snap +12 -12
- package/src/tools/setup/mate.ts +1 -1
|
@@ -44,6 +44,8 @@ export interface VaultWatchEvent {
|
|
|
44
44
|
|
|
45
45
|
export interface VaultTreeResult {
|
|
46
46
|
tree: VaultTreeNode[];
|
|
47
|
+
/** Counts watch invalidations seen when this listing was taken; a larger one makes it stale. */
|
|
48
|
+
generation: number;
|
|
47
49
|
watching: boolean;
|
|
48
50
|
warning: string | null;
|
|
49
51
|
}
|
|
@@ -379,6 +381,11 @@ interface SaveRecord {
|
|
|
379
381
|
expires: ReturnType<typeof setTimeout>;
|
|
380
382
|
}
|
|
381
383
|
|
|
384
|
+
interface ListedTree {
|
|
385
|
+
tree: VaultTreeNode[];
|
|
386
|
+
generation: number;
|
|
387
|
+
}
|
|
388
|
+
|
|
382
389
|
interface Subscriber {
|
|
383
390
|
path: string;
|
|
384
391
|
notify: (event: VaultWatchEvent) => void;
|
|
@@ -400,6 +407,8 @@ export interface VaultManager {
|
|
|
400
407
|
requestedPath: string,
|
|
401
408
|
notify: (event: VaultWatchEvent) => void,
|
|
402
409
|
): () => void;
|
|
410
|
+
/** Reports the listing generation now and on every later invalidation; carries no paths or content. */
|
|
411
|
+
subscribeTree(companionRoot: string, notify: (generation: number) => void): () => void;
|
|
403
412
|
refresh(companionRoot: string): Promise<VaultTreeResult>;
|
|
404
413
|
deactivate(): void;
|
|
405
414
|
stop(): void;
|
|
@@ -412,7 +421,8 @@ export function createVaultManager(deps: VaultDeps = {}): VaultManager {
|
|
|
412
421
|
const git = deps.git ?? runGit;
|
|
413
422
|
const trees = new Map<string, VaultTreeNode[]>();
|
|
414
423
|
const generations = new Map<string, number>();
|
|
415
|
-
const listings = new Map<string, Promise<
|
|
424
|
+
const listings = new Map<string, Promise<ListedTree>>();
|
|
425
|
+
const treeSubscribers = new Map<string, Set<(generation: number) => void>>();
|
|
416
426
|
const listed = new Map<string, string[]>();
|
|
417
427
|
const warnings = new Map<string, string | null>();
|
|
418
428
|
const watchers = new Map<string, Map<string, VaultWatcher>>();
|
|
@@ -430,7 +440,9 @@ export function createVaultManager(deps: VaultDeps = {}): VaultManager {
|
|
|
430
440
|
|
|
431
441
|
const invalidate = (root: string) => {
|
|
432
442
|
trees.delete(root);
|
|
433
|
-
|
|
443
|
+
const generation = (generations.get(root) ?? 0) + 1;
|
|
444
|
+
generations.set(root, generation);
|
|
445
|
+
for (const notifyTree of treeSubscribers.get(root) ?? []) notifyTree(generation);
|
|
434
446
|
};
|
|
435
447
|
|
|
436
448
|
const notify = async (
|
|
@@ -624,7 +636,7 @@ export function createVaultManager(deps: VaultDeps = {}): VaultManager {
|
|
|
624
636
|
};
|
|
625
637
|
|
|
626
638
|
/** A change seen while listing leaves the cache empty, so the next request lists again. */
|
|
627
|
-
const listTree = (root: string): Promise<
|
|
639
|
+
const listTree = (root: string): Promise<ListedTree> => {
|
|
628
640
|
const inFlight = listings.get(root);
|
|
629
641
|
if (inFlight) return inFlight;
|
|
630
642
|
const generation = generations.get(root) ?? 0;
|
|
@@ -636,7 +648,7 @@ export function createVaultManager(deps: VaultDeps = {}): VaultManager {
|
|
|
636
648
|
scheduleReconcile(root, files);
|
|
637
649
|
const tree = asTree(files);
|
|
638
650
|
if ((generations.get(root) ?? 0) === generation) trees.set(root, tree);
|
|
639
|
-
return tree;
|
|
651
|
+
return { tree, generation };
|
|
640
652
|
} finally {
|
|
641
653
|
listings.delete(root);
|
|
642
654
|
}
|
|
@@ -761,11 +773,13 @@ export function createVaultManager(deps: VaultDeps = {}): VaultManager {
|
|
|
761
773
|
return {
|
|
762
774
|
async tree(companionRoot, refresh = false) {
|
|
763
775
|
const root = await rootKey(companionRoot);
|
|
764
|
-
const
|
|
765
|
-
const result =
|
|
776
|
+
const cached = !refresh && trees.get(root);
|
|
777
|
+
const result: ListedTree = cached
|
|
778
|
+
? { tree: cached, generation: generations.get(root) ?? 0 }
|
|
779
|
+
: await listTree(root);
|
|
766
780
|
startWatching(root);
|
|
767
781
|
return {
|
|
768
|
-
|
|
782
|
+
...result,
|
|
769
783
|
watching: watchers.has(root),
|
|
770
784
|
warning: warnings.get(root) ?? null,
|
|
771
785
|
};
|
|
@@ -775,7 +789,14 @@ export function createVaultManager(deps: VaultDeps = {}): VaultManager {
|
|
|
775
789
|
if (refresh) invalidate(root);
|
|
776
790
|
startWatching(root);
|
|
777
791
|
const tree = trees.get(root);
|
|
778
|
-
if (tree)
|
|
792
|
+
if (tree) {
|
|
793
|
+
return {
|
|
794
|
+
tree,
|
|
795
|
+
generation: generations.get(root) ?? 0,
|
|
796
|
+
watching: watchers.has(root),
|
|
797
|
+
warning: warnings.get(root) ?? null,
|
|
798
|
+
};
|
|
799
|
+
}
|
|
779
800
|
void listTree(root).catch(() => {});
|
|
780
801
|
return null;
|
|
781
802
|
},
|
|
@@ -818,6 +839,30 @@ export function createVaultManager(deps: VaultDeps = {}): VaultManager {
|
|
|
818
839
|
if (currentSubscribers.size === 0) subscribers.delete(key);
|
|
819
840
|
};
|
|
820
841
|
},
|
|
842
|
+
subscribeTree(companionRoot, notifyTree) {
|
|
843
|
+
let active = true;
|
|
844
|
+
let subscribedRoot: string | null = null;
|
|
845
|
+
void rootKey(companionRoot).then(
|
|
846
|
+
(root) => {
|
|
847
|
+
if (!active) return;
|
|
848
|
+
subscribedRoot = root;
|
|
849
|
+
const set = treeSubscribers.get(root) ?? new Set();
|
|
850
|
+
set.add(notifyTree);
|
|
851
|
+
treeSubscribers.set(root, set);
|
|
852
|
+
startWatching(root);
|
|
853
|
+
if (!trees.has(root)) void listTree(root).catch(() => {});
|
|
854
|
+
notifyTree(generations.get(root) ?? 0);
|
|
855
|
+
},
|
|
856
|
+
() => {},
|
|
857
|
+
);
|
|
858
|
+
return () => {
|
|
859
|
+
active = false;
|
|
860
|
+
if (!subscribedRoot) return;
|
|
861
|
+
const set = treeSubscribers.get(subscribedRoot);
|
|
862
|
+
set?.delete(notifyTree);
|
|
863
|
+
if (set?.size === 0) treeSubscribers.delete(subscribedRoot);
|
|
864
|
+
};
|
|
865
|
+
},
|
|
821
866
|
async refresh(companionRoot) {
|
|
822
867
|
return this.tree(companionRoot, true);
|
|
823
868
|
},
|
|
@@ -828,6 +873,7 @@ export function createVaultManager(deps: VaultDeps = {}): VaultManager {
|
|
|
828
873
|
}
|
|
829
874
|
activeRoot = null;
|
|
830
875
|
subscribers.clear();
|
|
876
|
+
treeSubscribers.clear();
|
|
831
877
|
},
|
|
832
878
|
stop() {
|
|
833
879
|
for (const root of watchers.keys()) closeWatchers(root);
|
|
@@ -838,6 +884,7 @@ export function createVaultManager(deps: VaultDeps = {}): VaultManager {
|
|
|
838
884
|
pendingEvents.clear();
|
|
839
885
|
recentSaves.clear();
|
|
840
886
|
subscribers.clear();
|
|
887
|
+
treeSubscribers.clear();
|
|
841
888
|
},
|
|
842
889
|
getRecovery(companionRoot, requestedPath) {
|
|
843
890
|
const root = path.resolve(companionRoot);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { REPORT_VIEW_PREFIX } from "../routes";
|
|
1
2
|
import { COMPANION_DIGEST_PATTERN, COMPANION_PARAM } from "../selection";
|
|
2
3
|
|
|
3
4
|
export const THEME_STORAGE_KEY = "mate-studio-theme";
|
|
@@ -203,6 +204,35 @@ export const STUDIO_CLIENT_SCRIPT = `(function () {
|
|
|
203
204
|
});
|
|
204
205
|
});
|
|
205
206
|
|
|
207
|
+
/* Delegated: a spec row's controls need no per-node wiring. */
|
|
208
|
+
var AGENT_ORDER = ["claude", "opencode"];
|
|
209
|
+
function copyPromptFor(node) {
|
|
210
|
+
var viewed = document.documentElement.getAttribute("data-agent-viewed");
|
|
211
|
+
var order = viewed ? [viewed].concat(AGENT_ORDER) : AGENT_ORDER;
|
|
212
|
+
for (var index = 0; index < order.length; index += 1) {
|
|
213
|
+
var text = node.getAttribute("data-prompt-" + order[index]);
|
|
214
|
+
if (text) return text;
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
if (typeof document.addEventListener === "function") document.addEventListener("click", function (event) {
|
|
219
|
+
var target = event.target;
|
|
220
|
+
if (!target || typeof target.closest !== "function") return;
|
|
221
|
+
var run = target.closest("[data-studio-action]");
|
|
222
|
+
if (run) {
|
|
223
|
+
var agents = (run.getAttribute("data-studio-agents") || "").split(" ").filter(Boolean);
|
|
224
|
+
document.dispatchEvent(new CustomEvent("studio:terminal-action", {
|
|
225
|
+
detail: { action: run.getAttribute("data-studio-action"), subject: run.getAttribute("data-studio-subject"), agents: agents },
|
|
226
|
+
}));
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
var prompt = target.closest("[data-studio-copy-prompt]");
|
|
230
|
+
if (prompt) {
|
|
231
|
+
var text = copyPromptFor(prompt);
|
|
232
|
+
if (text) copy(text, "prompt");
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
206
236
|
var savePromise = null;
|
|
207
237
|
function editor() { return document.getElementById("vault-editor"); }
|
|
208
238
|
function status(message) {
|
|
@@ -253,7 +283,7 @@ export const STUDIO_CLIENT_SCRIPT = `(function () {
|
|
|
253
283
|
return target.toString();
|
|
254
284
|
}
|
|
255
285
|
function wireNavigation(scope) {
|
|
256
|
-
(scope || document).querySelectorAll('form[method="get"]').forEach(function (form) {
|
|
286
|
+
(scope || document).querySelectorAll('form[method="get"]:not([data-vault-refresh])').forEach(function (form) {
|
|
257
287
|
form.addEventListener("submit", function (event) {
|
|
258
288
|
event.preventDefault();
|
|
259
289
|
navigate(navigationUrl(form, event.submitter));
|
|
@@ -335,22 +365,6 @@ export const STUDIO_CLIENT_SCRIPT = `(function () {
|
|
|
335
365
|
event.preventDefault();
|
|
336
366
|
event.returnValue = "";
|
|
337
367
|
});
|
|
338
|
-
function filterTree(query) {
|
|
339
|
-
var needle = query.trim().toLowerCase();
|
|
340
|
-
document.querySelectorAll("[data-vault-entry]").forEach(function (entry) {
|
|
341
|
-
entry.hidden = !!needle && (entry.getAttribute("data-vault-entry") || "").toLowerCase().indexOf(needle) === -1;
|
|
342
|
-
});
|
|
343
|
-
document.querySelectorAll("[data-vault-dir]").forEach(function (folder) {
|
|
344
|
-
if (!needle) {
|
|
345
|
-
folder.hidden = false;
|
|
346
|
-
folder.open = folder.hasAttribute("data-vault-expanded");
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
var match = folder.querySelector("[data-vault-entry]:not([hidden])");
|
|
350
|
-
folder.hidden = !match;
|
|
351
|
-
if (match) folder.open = true;
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
368
|
function showTree(shown) {
|
|
355
369
|
var root = document.documentElement;
|
|
356
370
|
if (shown) root.removeAttribute("data-vault-tree");
|
|
@@ -366,18 +380,67 @@ export const STUDIO_CLIENT_SCRIPT = `(function () {
|
|
|
366
380
|
/* a blocked web store only costs the preference, never the page */
|
|
367
381
|
}
|
|
368
382
|
}
|
|
369
|
-
|
|
383
|
+
var displayedGeneration = null;
|
|
384
|
+
var latestGeneration = null;
|
|
385
|
+
var refreshSequence = 0;
|
|
386
|
+
var filterSequence = 0;
|
|
387
|
+
var filterTimer = null;
|
|
388
|
+
var filterRequest = null;
|
|
389
|
+
var FILTER_DELAY = 150;
|
|
390
|
+
|
|
391
|
+
function failure(error) {
|
|
392
|
+
return error && error.message ? error.message : String(error);
|
|
393
|
+
}
|
|
394
|
+
function fetchMarkup(url, signal) {
|
|
395
|
+
return fetch(url, { headers: { accept: "text/html" }, signal: signal }).then(function (response) {
|
|
396
|
+
if (!response.ok) throw new Error("status " + response.status);
|
|
397
|
+
return response.text();
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
/** Moves server-rendered nodes into a container; builds no markup of its own. */
|
|
401
|
+
function moveInto(container, markup) {
|
|
402
|
+
var parsed = new DOMParser().parseFromString(markup, "text/html");
|
|
403
|
+
container.replaceChildren.apply(
|
|
404
|
+
container,
|
|
405
|
+
Array.prototype.map.call(parsed.body.childNodes, function (child) { return document.importNode(child, true); }),
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
function readGeneration(slot) {
|
|
409
|
+
var value = slot ? slot.getAttribute("data-vault-generation") : null;
|
|
410
|
+
return value === null || value === "" ? null : Number(value);
|
|
411
|
+
}
|
|
412
|
+
/** The indicator clears only once the displayed listing is as new as the newest change seen. */
|
|
413
|
+
function showStale() {
|
|
414
|
+
var note = document.getElementById("vault-stale");
|
|
415
|
+
if (!note) return;
|
|
416
|
+
note.hidden = !(latestGeneration !== null && displayedGeneration !== null && latestGeneration > displayedGeneration);
|
|
417
|
+
}
|
|
418
|
+
function openedByViewer() {
|
|
419
|
+
var keys = [];
|
|
420
|
+
document.querySelectorAll("details[data-vault-children-url][open]").forEach(function (folder) {
|
|
421
|
+
keys.push(folder.getAttribute("data-vault-dir"));
|
|
422
|
+
});
|
|
423
|
+
return keys;
|
|
424
|
+
}
|
|
425
|
+
/** Swaps server-rendered parts into place; a refreshed tree drops loaded folders and reopens the viewer's. */
|
|
370
426
|
function swapVaultSlots(markup) {
|
|
427
|
+
var reopen = openedByViewer();
|
|
371
428
|
var parsed = new DOMParser().parseFromString(markup, "text/html");
|
|
372
429
|
parsed.querySelectorAll("[data-vault-slot]").forEach(function (slot) {
|
|
373
|
-
var
|
|
430
|
+
var name = slot.getAttribute("data-vault-slot");
|
|
431
|
+
var target = document.querySelector('[data-vault-slot="' + name + '"]');
|
|
374
432
|
if (!target) return;
|
|
375
433
|
var node = document.importNode(slot, true);
|
|
376
434
|
target.replaceWith(node);
|
|
377
435
|
wireNavigation(node);
|
|
436
|
+
if (name !== "tree") return;
|
|
437
|
+
displayedGeneration = readGeneration(node);
|
|
438
|
+
node.querySelectorAll("details[data-vault-children-url]").forEach(function (folder) {
|
|
439
|
+
if (reopen.indexOf(folder.getAttribute("data-vault-dir")) !== -1) folder.open = true;
|
|
440
|
+
});
|
|
378
441
|
});
|
|
379
|
-
|
|
380
|
-
|
|
442
|
+
showStale();
|
|
443
|
+
runFilter();
|
|
381
444
|
}
|
|
382
445
|
function failVaultSlots(message) {
|
|
383
446
|
document.querySelectorAll("[data-vault-slot][aria-busy]").forEach(function (slot) {
|
|
@@ -385,13 +448,109 @@ export const STUDIO_CLIENT_SCRIPT = `(function () {
|
|
|
385
448
|
slot.textContent = message;
|
|
386
449
|
});
|
|
387
450
|
}
|
|
451
|
+
function loadChildren(folder) {
|
|
452
|
+
var url = folder.getAttribute("data-vault-children-url");
|
|
453
|
+
var container = folder.querySelector(".vault-children");
|
|
454
|
+
if (!url || !container || folder.hasAttribute("data-vault-loaded") || folder.hasAttribute("data-vault-loading")) return;
|
|
455
|
+
folder.setAttribute("data-vault-loading", "");
|
|
456
|
+
fetchMarkup(url).then(function (markup) {
|
|
457
|
+
moveInto(container, markup);
|
|
458
|
+
folder.setAttribute("data-vault-loaded", "");
|
|
459
|
+
}).catch(function (error) {
|
|
460
|
+
container.textContent = "This folder could not be loaded: " + failure(error);
|
|
461
|
+
}).finally(function () { folder.removeAttribute("data-vault-loading"); });
|
|
462
|
+
}
|
|
463
|
+
function runFilter() {
|
|
464
|
+
var filter = document.getElementById("vault-filter");
|
|
465
|
+
var results = document.getElementById("vault-filter-results");
|
|
466
|
+
var panel = document.getElementById("vault-tree-panel");
|
|
467
|
+
var layout = document.querySelector("[data-vault-layout]");
|
|
468
|
+
if (!filter || !results || !panel || !layout) return;
|
|
469
|
+
clearTimeout(filterTimer);
|
|
470
|
+
if (filterRequest) filterRequest.abort();
|
|
471
|
+
var needle = filter.value.trim();
|
|
472
|
+
filterSequence += 1;
|
|
473
|
+
if (!needle) {
|
|
474
|
+
results.hidden = true;
|
|
475
|
+
results.replaceChildren();
|
|
476
|
+
panel.removeAttribute("data-vault-filtering");
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
var url = new URL(layout.getAttribute("data-vault-filter-url") || "", location.href);
|
|
480
|
+
url.searchParams.set("q", needle);
|
|
481
|
+
var sequence = filterSequence;
|
|
482
|
+
filterRequest = typeof AbortController !== "undefined" ? new AbortController() : null;
|
|
483
|
+
fetchMarkup(url.toString(), filterRequest ? filterRequest.signal : undefined).then(function (markup) {
|
|
484
|
+
if (sequence !== filterSequence) return;
|
|
485
|
+
moveInto(results, markup);
|
|
486
|
+
results.hidden = false;
|
|
487
|
+
panel.setAttribute("data-vault-filtering", "");
|
|
488
|
+
}).catch(function (error) {
|
|
489
|
+
if (sequence !== filterSequence || (error && error.name === "AbortError")) return;
|
|
490
|
+
results.textContent = "The filter could not run: " + failure(error);
|
|
491
|
+
results.hidden = false;
|
|
492
|
+
panel.setAttribute("data-vault-filtering", "");
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
/** Replaces the tree, listing and filter results in place; the editor and terminal are left alone. */
|
|
496
|
+
function refreshTree() {
|
|
497
|
+
var layout = document.querySelector("[data-vault-layout]");
|
|
498
|
+
var status = document.getElementById("vault-refresh-status");
|
|
499
|
+
var url = layout ? layout.getAttribute("data-vault-refresh-url") : null;
|
|
500
|
+
if (!url) return;
|
|
501
|
+
refreshSequence += 1;
|
|
502
|
+
var sequence = refreshSequence;
|
|
503
|
+
if (status) status.hidden = true;
|
|
504
|
+
fetchMarkup(url).then(function (markup) {
|
|
505
|
+
if (sequence === refreshSequence) swapVaultSlots(markup);
|
|
506
|
+
}).catch(function (error) {
|
|
507
|
+
if (sequence !== refreshSequence || !status) return;
|
|
508
|
+
status.textContent = "The tree could not be refreshed: " + failure(error) + ". Select Refresh tree to retry.";
|
|
509
|
+
status.hidden = false;
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
function watchTreeChanges(layout) {
|
|
513
|
+
var url = layout.getAttribute("data-vault-changes-url");
|
|
514
|
+
if (!url || typeof EventSource === "undefined") return;
|
|
515
|
+
var changes = new EventSource(url);
|
|
516
|
+
changes.onmessage = function (message) {
|
|
517
|
+
var event;
|
|
518
|
+
try { event = JSON.parse(message.data); } catch (error) { return; }
|
|
519
|
+
if (!event || event.type !== "vault-tree-changed" || typeof event.generation !== "number") return;
|
|
520
|
+
if (latestGeneration === null || event.generation > latestGeneration) latestGeneration = event.generation;
|
|
521
|
+
showStale();
|
|
522
|
+
};
|
|
523
|
+
}
|
|
388
524
|
function wireVaultBrowser() {
|
|
389
525
|
var layout = document.querySelector("[data-vault-layout]");
|
|
390
526
|
if (!layout) return;
|
|
527
|
+
var panel = document.getElementById("vault-tree-panel");
|
|
528
|
+
displayedGeneration = readGeneration(document.querySelector('[data-vault-slot="tree"]'));
|
|
529
|
+
if (panel) {
|
|
530
|
+
panel.addEventListener("click", function (event) {
|
|
531
|
+
if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
532
|
+
var link = event.target && event.target.closest ? event.target.closest("a[data-vault-entry]") : null;
|
|
533
|
+
if (!link) return;
|
|
534
|
+
event.preventDefault();
|
|
535
|
+
navigate(link.href);
|
|
536
|
+
});
|
|
537
|
+
panel.addEventListener("toggle", function (event) {
|
|
538
|
+
var folder = event.target;
|
|
539
|
+
if (folder && folder.open && folder.matches && folder.matches("details[data-vault-children-url]")) loadChildren(folder);
|
|
540
|
+
}, true);
|
|
541
|
+
}
|
|
542
|
+
var refresh = document.querySelector("form[data-vault-refresh]");
|
|
543
|
+
if (refresh) refresh.addEventListener("submit", function (event) {
|
|
544
|
+
event.preventDefault();
|
|
545
|
+
refreshTree();
|
|
546
|
+
});
|
|
391
547
|
var filter = document.getElementById("vault-filter");
|
|
392
548
|
if (filter) {
|
|
393
549
|
filter.hidden = false;
|
|
394
|
-
filter.addEventListener("input", function () {
|
|
550
|
+
filter.addEventListener("input", function () {
|
|
551
|
+
clearTimeout(filterTimer);
|
|
552
|
+
filterTimer = setTimeout(runFilter, FILTER_DELAY);
|
|
553
|
+
});
|
|
395
554
|
}
|
|
396
555
|
var toggle = document.getElementById("vault-tree-toggle");
|
|
397
556
|
if (toggle) {
|
|
@@ -403,16 +562,93 @@ export const STUDIO_CLIENT_SCRIPT = `(function () {
|
|
|
403
562
|
showTree(document.documentElement.getAttribute("data-vault-tree") === "hidden");
|
|
404
563
|
});
|
|
405
564
|
}
|
|
565
|
+
watchTreeChanges(layout);
|
|
406
566
|
var deferred = layout.getAttribute("data-vault-view");
|
|
407
567
|
if (!deferred) return;
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
return response.text();
|
|
411
|
-
}).then(swapVaultSlots).catch(function (error) {
|
|
412
|
-
failVaultSlots("The files could not be listed: " + (error && error.message ? error.message : String(error)));
|
|
568
|
+
fetchMarkup(deferred).then(swapVaultSlots).catch(function (error) {
|
|
569
|
+
failVaultSlots("The files could not be listed: " + failure(error));
|
|
413
570
|
});
|
|
414
571
|
}
|
|
572
|
+
/** Hosted reports: the list, the sandboxed frame, and the toast and badge for a new arrival. */
|
|
573
|
+
function wireReports() {
|
|
574
|
+
var shell = document.querySelector("[data-reports-events-url]");
|
|
575
|
+
if (!shell || typeof EventSource === "undefined") return;
|
|
576
|
+
var current = shell.getAttribute("data-companion");
|
|
577
|
+
var listUrl = shell.getAttribute("data-reports-url");
|
|
578
|
+
var viewUrl = shell.getAttribute("data-reports-view-url");
|
|
579
|
+
var list = document.getElementById("reports-list");
|
|
580
|
+
var frame = document.getElementById("reports-frame");
|
|
581
|
+
var open = document.getElementById("reports-open");
|
|
582
|
+
var empty = document.getElementById("reports-empty");
|
|
583
|
+
var template = document.getElementById("reports-item-template");
|
|
584
|
+
var badge = document.getElementById("reports-badge");
|
|
585
|
+
var toast = document.getElementById("reports-toast");
|
|
586
|
+
var toastTitle = document.getElementById("reports-toast-title");
|
|
587
|
+
var toastOpen = document.getElementById("reports-toast-open");
|
|
588
|
+
var shown = null;
|
|
589
|
+
var toastTimeout;
|
|
590
|
+
function reportHash() {
|
|
591
|
+
var match = /(?:^|[#&])report=([0-9a-f]{32})/.exec(location.hash || "");
|
|
592
|
+
return match ? match[1] : null;
|
|
593
|
+
}
|
|
594
|
+
function show(id) {
|
|
595
|
+
if (!frame) return;
|
|
596
|
+
shown = id;
|
|
597
|
+
var src = ${JSON.stringify(REPORT_VIEW_PREFIX)} + id;
|
|
598
|
+
frame.src = src;
|
|
599
|
+
frame.hidden = false;
|
|
600
|
+
if (open) { open.href = src; open.hidden = false; }
|
|
601
|
+
if (list) list.querySelectorAll("button[data-report-id]").forEach(function (button) {
|
|
602
|
+
button.setAttribute("aria-pressed", button.getAttribute("data-report-id") === id ? "true" : "false");
|
|
603
|
+
});
|
|
604
|
+
try { history.replaceState(null, "", "#report=" + id); } catch (error) { /* the address is a convenience */ }
|
|
605
|
+
}
|
|
606
|
+
function render(reports) {
|
|
607
|
+
if (!list || !template || !template.content.firstElementChild) return;
|
|
608
|
+
list.replaceChildren();
|
|
609
|
+
if (empty) empty.hidden = reports.length > 0;
|
|
610
|
+
reports.forEach(function (report) {
|
|
611
|
+
var item = template.content.firstElementChild.cloneNode(true);
|
|
612
|
+
var button = item.querySelector("button");
|
|
613
|
+
button.setAttribute("data-report-id", report.id);
|
|
614
|
+
button.setAttribute("aria-pressed", report.id === shown ? "true" : "false");
|
|
615
|
+
item.querySelector("[data-report-title]").textContent = report.title;
|
|
616
|
+
item.querySelector("[data-report-time]").textContent = new Date(report.createdAt).toLocaleString();
|
|
617
|
+
button.addEventListener("click", function () { show(report.id); });
|
|
618
|
+
list.append(item);
|
|
619
|
+
});
|
|
620
|
+
if (reports.length === 0 || reports.some(function (report) { return report.id === shown; })) return;
|
|
621
|
+
var named = reportHash();
|
|
622
|
+
show(reports.some(function (report) { return report.id === named; }) ? named : reports[0].id);
|
|
623
|
+
}
|
|
624
|
+
function refreshList() {
|
|
625
|
+
if (!list || !listUrl) return;
|
|
626
|
+
fetch(listUrl, { headers: { accept: "application/json" } }).then(function (response) {
|
|
627
|
+
if (!response.ok) throw new Error("status " + response.status);
|
|
628
|
+
return response.json();
|
|
629
|
+
}).then(function (body) { render(body.reports || []); }).catch(function () { /* the next event or reconnect retries */ });
|
|
630
|
+
}
|
|
631
|
+
function announceReport(event) {
|
|
632
|
+
if (badge) badge.hidden = false;
|
|
633
|
+
if (!toast || !toastTitle) return;
|
|
634
|
+
toastTitle.textContent = "New report: " + event.title;
|
|
635
|
+
if (toastOpen && viewUrl) toastOpen.href = viewUrl + "#report=" + event.id;
|
|
636
|
+
toast.setAttribute("data-shown", "true");
|
|
637
|
+
clearTimeout(toastTimeout);
|
|
638
|
+
toastTimeout = setTimeout(function () { toast.setAttribute("data-shown", "false"); }, 10000);
|
|
639
|
+
}
|
|
640
|
+
var events = new EventSource(shell.getAttribute("data-reports-events-url"));
|
|
641
|
+
events.onopen = refreshList;
|
|
642
|
+
events.onmessage = function (message) {
|
|
643
|
+
var event;
|
|
644
|
+
try { event = JSON.parse(message.data); } catch (error) { return; }
|
|
645
|
+
if (!event || event.type !== "report-published" || event.companion !== current) return;
|
|
646
|
+
if (list) refreshList();
|
|
647
|
+
else announceReport(event);
|
|
648
|
+
};
|
|
649
|
+
}
|
|
415
650
|
wireNavigation();
|
|
416
651
|
wireVault();
|
|
417
652
|
wireVaultBrowser();
|
|
653
|
+
wireReports();
|
|
418
654
|
})();`;
|