@gavin-hu/my-pi-agent 0.16.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/README.md +14 -8
- package/README.zh-CN.md +14 -9
- package/extensions/bytefly/README.md +174 -101
- package/extensions/bytefly/footer/collect/entries.ts +1 -6
- package/extensions/bytefly/footer/collect/snapshot.ts +23 -9
- package/extensions/bytefly/footer/price/price.ts +12 -8
- package/extensions/bytefly/footer/register.ts +13 -1
- package/extensions/bytefly/footer/types.ts +11 -0
- package/extensions/bytefly/footer/unpriced.ts +36 -0
- package/extensions/bytefly/header/component.ts +15 -29
- package/extensions/bytefly/header/identity.ts +71 -0
- package/extensions/bytefly/header/register.ts +9 -3
- package/extensions/bytefly/index.ts +13 -5
- package/extensions/bytefly/message/format.ts +20 -13
- package/extensions/bytefly/message/register.ts +8 -7
- package/extensions/bytefly/panel/commands.ts +46 -25
- package/extensions/bytefly/panel/format.ts +49 -14
- package/extensions/bytefly/panel/join.ts +35 -9
- package/extensions/bytefly/panel/settings.ts +10 -27
- package/extensions/bytefly/panel/skills.ts +51 -0
- package/extensions/bytefly/panel/tui/extensions-view.ts +7 -61
- package/extensions/bytefly/panel/tui/frame.ts +9 -1
- package/extensions/bytefly/panel/tui/list-view.ts +72 -0
- package/extensions/bytefly/panel/tui/nav.ts +71 -3
- package/extensions/bytefly/panel/tui/settings-section.ts +250 -0
- package/extensions/bytefly/panel/tui/skills-view.ts +50 -0
- package/extensions/bytefly/panel/tui.ts +223 -331
- package/extensions/bytefly/panel/types.ts +26 -8
- package/extensions/bytefly/picker/discovery.ts +32 -27
- package/extensions/bytefly/picker/resources.ts +16 -8
- package/extensions/bytefly/picker/runtime.ts +62 -34
- package/extensions/bytefly/picker/toggle.ts +16 -10
- package/extensions/bytefly/picker/types.ts +31 -13
- package/extensions/bytefly/update/format.ts +66 -8
- package/extensions/bytefly/update/patch.ts +66 -0
- package/extensions/bytefly/update/register.ts +4 -11
- package/extensions/bytefly/update/release.ts +65 -0
- package/extensions/bytefly/update/state.ts +14 -0
- package/extensions/bytefly/update/suppress.ts +47 -29
- package/extensions/bytefly/update/types.ts +15 -5
- package/extensions/check/tui.ts +2 -0
- package/extensions/context/README.md +21 -11
- package/extensions/context/format.ts +10 -8
- package/extensions/context/report.ts +21 -8
- package/extensions/context/tui.ts +26 -10
- package/extensions/context/types.ts +5 -3
- package/extensions/file-browse/README.md +29 -1
- package/extensions/file-browse/client.ts +16 -463
- package/extensions/file-browse/html.ts +7 -5
- package/extensions/file-browse/palette.ts +224 -0
- package/extensions/file-browse/router.ts +2 -2
- package/extensions/file-browse/styles.ts +8 -3
- package/extensions/file-browse/tree.ts +381 -0
- package/extensions/file-read/README.md +4 -1
- package/extensions/file-read/extract/odt.ts +7 -5
- package/extensions/goal/tools.ts +6 -11
- package/extensions/job/index.ts +20 -45
- package/extensions/job/tools.ts +51 -77
- package/extensions/memory/tools.ts +4 -6
- package/extensions/memory/tui.ts +5 -1
- package/extensions/plan/README.md +88 -33
- package/extensions/plan/commands.ts +19 -4
- package/extensions/plan/config.ts +27 -0
- package/extensions/plan/plan-index.ts +3 -1
- package/extensions/plan/plans.ts +31 -13
- package/extensions/plan/review.ts +34 -14
- package/extensions/plan/runtime.ts +3 -1
- package/extensions/plan/steps.ts +43 -14
- package/extensions/plan/tools/cancel.ts +79 -0
- package/extensions/plan/tools/enter.ts +10 -10
- package/extensions/plan/tools/exit.ts +54 -43
- package/extensions/plan/tools/steps.ts +7 -19
- package/extensions/plan/tools/write.ts +22 -29
- package/extensions/plan/tools.ts +5 -2
- package/extensions/plan/tui.ts +9 -7
- package/extensions/plan/types.ts +8 -0
- package/extensions/plan/web-chrome.ts +101 -0
- package/extensions/plan/web-client.ts +98 -311
- package/extensions/plan/web-markdown.ts +222 -0
- package/extensions/plan/web-page.ts +28 -2
- package/extensions/plan/web-router.ts +2 -2
- package/extensions/rewind/checklist.ts +4 -0
- package/extensions/rewind/review-tui.ts +3 -1
- package/extensions/rewind/rewind-tui.ts +4 -0
- package/extensions/subagent/README.md +25 -12
- package/extensions/subagent/format.ts +72 -25
- package/extensions/subagent/index.ts +21 -47
- package/extensions/subagent/orchestrate.ts +18 -41
- package/extensions/subagent/render.ts +29 -42
- package/extensions/subagent/stream.ts +24 -18
- package/extensions/subagent/types.ts +7 -0
- package/extensions/todo/tools.ts +5 -6
- package/extensions/todo/tui.ts +5 -1
- package/extensions/watch/index.ts +10 -35
- package/extensions/watch/tools.ts +31 -59
- package/extensions/wechat/README.md +40 -18
- package/extensions/wechat/bridge.ts +38 -40
- package/extensions/wechat/client.ts +1 -4
- package/extensions/wechat/commands.ts +1 -1
- package/extensions/wechat/interaction.ts +25 -4
- package/extensions/wechat/login-screen.ts +2 -3
- package/extensions/wechat/notices.ts +7 -5
- package/extensions/wechat/prompt.ts +19 -4
- package/extensions/wechat/qrcode.ts +9 -9
- package/extensions/wechat/slash.ts +14 -0
- package/extensions/wechat/status-screen.ts +2 -2
- package/extensions/wechat/tools.ts +3 -11
- package/extensions/worktree/README.md +48 -12
- package/extensions/worktree/commands.ts +109 -65
- package/extensions/worktree/enter.ts +1 -1
- package/extensions/worktree/exit.ts +11 -1
- package/extensions/worktree/lifecycle.ts +9 -1
- package/extensions/worktree/screen.ts +263 -0
- package/extensions/worktree/status.ts +84 -38
- package/lib/README.md +7 -6
- package/lib/config.ts +12 -0
- package/lib/format.ts +6 -2
- package/lib/interaction.ts +18 -1
- package/lib/report.ts +75 -2
- package/lib/tool-result.ts +54 -4
- package/lib/tui/README.md +16 -8
- package/lib/tui/chrome.ts +27 -6
- package/lib/tui/dock-screen.ts +6 -2
- package/lib/tui/gauge.ts +9 -5
- package/lib/usage.ts +26 -0
- package/lib/web/README.md +131 -0
- package/lib/web/base.ts +19 -2
- package/lib/web/components.ts +10 -6
- package/lib/web/layout.ts +2 -0
- package/lib/web/markup.ts +2 -1
- package/lib/web/runtime.ts +103 -25
- package/lib/web/tokens.ts +23 -7
- package/package.json +3 -1
- package/skills/changelog/SKILL.md +57 -45
- package/skills/next/SKILL.md +184 -0
|
@@ -2,245 +2,25 @@
|
|
|
2
2
|
* The first-party browser script, served as `/app.js`.
|
|
3
3
|
*
|
|
4
4
|
* It is intentionally tiny and dependency-free: it only progressively enhances
|
|
5
|
-
* what the server already rendered
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* what the server already rendered. The sidebar (filter, fold, lazy tree,
|
|
6
|
+
* keyboard) is `tree.ts` and the quick-open palette is `palette.ts`; both are
|
|
7
|
+
* fragments of this same IIFE, interpolated below. What stays here is the shell:
|
|
8
|
+
* sorting, wrapping, copying, the diff's hunk navigation, the reading progress,
|
|
9
|
+
* and the page's keymap. Navigation works without any of it.
|
|
9
10
|
*
|
|
10
11
|
* Kept as a string constant so the extension is a single self-contained module
|
|
11
12
|
* and `bun build --no-bundle` needs no separate asset.
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
|
-
import { keysDialogScript,
|
|
15
|
+
import { keysDialogScript, THEME_TOGGLE_JS } from "../../lib/web/index.ts";
|
|
15
16
|
import { LIVE_CLIENT_JS } from "./live-client.ts";
|
|
17
|
+
import { PALETTE_CLIENT_JS } from "./palette.ts";
|
|
18
|
+
import { TREE_CLIENT_JS } from "./tree.ts";
|
|
16
19
|
|
|
17
20
|
export const CLIENT_JS = `(function () {
|
|
18
21
|
"use strict";
|
|
19
|
-
var tree = document.querySelector("nav.bf-tree");
|
|
20
|
-
var filter = document.getElementById("filter");
|
|
21
|
-
var foldAllButton = document.getElementById("fold-all");
|
|
22
22
|
var rows = Array.prototype.slice.call(document.querySelectorAll("table.bf-table tbody tr"));
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
function matches(text) {
|
|
26
|
-
return !query || text.toLowerCase().indexOf(query) !== -1;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
30
|
-
|
|
31
|
-
function svgIcon(entry) {
|
|
32
|
-
var svg = document.createElementNS(SVG_NS, "svg");
|
|
33
|
-
svg.setAttribute("class", "browse-cat " + (entry.colorClass || ""));
|
|
34
|
-
svg.setAttribute("aria-hidden", "true");
|
|
35
|
-
svg.setAttribute("title", entry.label || "");
|
|
36
|
-
var use = document.createElementNS(SVG_NS, "use");
|
|
37
|
-
use.setAttribute("href", entry.iconHref);
|
|
38
|
-
svg.appendChild(use);
|
|
39
|
-
return svg;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function buildNode(entry) {
|
|
43
|
-
var li = document.createElement("li");
|
|
44
|
-
var row = document.createElement("div");
|
|
45
|
-
row.className = "bf-tree__row";
|
|
46
|
-
row.setAttribute("data-path", entry.path || "");
|
|
47
|
-
var twisty = document.createElement(entry.isDir ? "button" : "span");
|
|
48
|
-
twisty.className = "bf-tree__tw";
|
|
49
|
-
row.appendChild(twisty);
|
|
50
|
-
row.appendChild(svgIcon(entry));
|
|
51
|
-
if (entry.isDir) {
|
|
52
|
-
li.dataset.open = "false";
|
|
53
|
-
twisty.classList.add("bf-tree__tw--toggle");
|
|
54
|
-
twisty.type = "button";
|
|
55
|
-
twisty.textContent = "\\u25B8";
|
|
56
|
-
twisty.setAttribute("aria-expanded", "false");
|
|
57
|
-
twisty.setAttribute("aria-label", entry.name);
|
|
58
|
-
var dir = document.createElement("a");
|
|
59
|
-
dir.href = entry.href;
|
|
60
|
-
dir.textContent = entry.name + "/";
|
|
61
|
-
row.appendChild(dir);
|
|
62
|
-
} else if (entry.broken) {
|
|
63
|
-
twisty.className = "bf-tree__tw bf-tree__tw--spacer";
|
|
64
|
-
twisty.setAttribute("aria-hidden", "true");
|
|
65
|
-
var broken = document.createElement("span");
|
|
66
|
-
broken.className = "browse-broken";
|
|
67
|
-
broken.title = "broken symbolic link";
|
|
68
|
-
broken.textContent = entry.name;
|
|
69
|
-
row.appendChild(broken);
|
|
70
|
-
var mark = document.createElement("span");
|
|
71
|
-
mark.className = "browse-link-mark";
|
|
72
|
-
mark.textContent = "\\u21A9";
|
|
73
|
-
row.appendChild(mark);
|
|
74
|
-
} else {
|
|
75
|
-
twisty.className = "bf-tree__tw bf-tree__tw--spacer";
|
|
76
|
-
twisty.setAttribute("aria-hidden", "true");
|
|
77
|
-
var file = document.createElement("a");
|
|
78
|
-
file.href = entry.href;
|
|
79
|
-
file.textContent = entry.name;
|
|
80
|
-
row.appendChild(file);
|
|
81
|
-
}
|
|
82
|
-
li.appendChild(row);
|
|
83
|
-
return li;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function filterNode(li) {
|
|
87
|
-
var row = li.querySelector(":scope > .bf-tree__row");
|
|
88
|
-
var own = matches(row ? row.getAttribute("data-path") || row.textContent : "");
|
|
89
|
-
var childMatch = false;
|
|
90
|
-
Array.prototype.forEach.call(li.querySelectorAll(":scope > ul > li"), function (child) {
|
|
91
|
-
if (filterNode(child)) childMatch = true;
|
|
92
|
-
});
|
|
93
|
-
var visible = own || childMatch;
|
|
94
|
-
li.hidden = !visible;
|
|
95
|
-
return visible;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function applyFilter() {
|
|
99
|
-
var empty = document.getElementById("tree-empty");
|
|
100
|
-
var anyTree = false;
|
|
101
|
-
if (tree) {
|
|
102
|
-
Array.prototype.forEach.call(tree.querySelectorAll(":scope > ul > li"), function (li) {
|
|
103
|
-
if (filterNode(li)) anyTree = true;
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
if (empty) empty.hidden = !tree || anyTree;
|
|
107
|
-
rows.forEach(function (row) {
|
|
108
|
-
var path = row.getAttribute("data-path") || row.getAttribute("data-name") || "";
|
|
109
|
-
row.hidden = !matches(path);
|
|
110
|
-
});
|
|
111
|
-
// Lists are repainted too: while a query is active every list is shown, and
|
|
112
|
-
// clearing the query brings the folds back.
|
|
113
|
-
if (tree) Array.prototype.forEach.call(tree.querySelectorAll("li[data-open]"), paint);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (filter) {
|
|
117
|
-
filter.addEventListener("input", function () {
|
|
118
|
-
query = filter.value.trim().toLowerCase();
|
|
119
|
-
applyFilter();
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Visibility is derived: the intent in data-open, opened up while a filter is
|
|
124
|
-
// active so a fold cannot hide a match.
|
|
125
|
-
function isOpen(li) {
|
|
126
|
-
return query !== "" || li.dataset.open === "true";
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function paint(li) {
|
|
130
|
-
var row = li.querySelector(":scope > .bf-tree__row");
|
|
131
|
-
var twisty = row ? row.querySelector(":scope > .bf-tree__tw") : null;
|
|
132
|
-
if (!twisty || twisty.tagName !== "BUTTON") return;
|
|
133
|
-
var open = isOpen(li);
|
|
134
|
-
// A pending load owns the glyph until it lands or fails.
|
|
135
|
-
if (!li.dataset.loading && !li.dataset.error) {
|
|
136
|
-
twisty.textContent = open ? "\\u25BE" : "\\u25B8";
|
|
137
|
-
twisty.setAttribute("aria-expanded", open ? "true" : "false");
|
|
138
|
-
}
|
|
139
|
-
var list = li.querySelector(":scope > ul");
|
|
140
|
-
if (list) list.hidden = !open;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function setOpen(li, open) {
|
|
144
|
-
li.dataset.open = open ? "true" : "false";
|
|
145
|
-
paint(li);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Every dir with rendered children: what the fold-all control acts on.
|
|
149
|
-
function foldableDirs() {
|
|
150
|
-
var dirs = [];
|
|
151
|
-
if (!tree) return dirs;
|
|
152
|
-
Array.prototype.forEach.call(tree.querySelectorAll("li"), function (li) {
|
|
153
|
-
if (li.querySelector(":scope > ul")) dirs.push(li);
|
|
154
|
-
});
|
|
155
|
-
return dirs;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function syncFoldButton() {
|
|
159
|
-
if (!foldAllButton) return;
|
|
160
|
-
var dirs = foldableDirs();
|
|
161
|
-
var open = 0;
|
|
162
|
-
dirs.forEach(function (li) {
|
|
163
|
-
if (li.dataset.open === "true") open++;
|
|
164
|
-
});
|
|
165
|
-
foldAllButton.hidden = dirs.length === 0;
|
|
166
|
-
foldAllButton.textContent = open > 0 ? "fold all" : "expand all";
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function setAll(open) {
|
|
170
|
-
foldableDirs().forEach(function (li) {
|
|
171
|
-
setOpen(li, open);
|
|
172
|
-
});
|
|
173
|
-
syncFoldButton();
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function loadChildren(li, twisty, path) {
|
|
177
|
-
var name = twisty.getAttribute("aria-label") || "";
|
|
178
|
-
li.dataset.loading = "true";
|
|
179
|
-
twisty.textContent = "\\u2026";
|
|
180
|
-
twisty.setAttribute("aria-busy", "true");
|
|
181
|
-
twisty.setAttribute("aria-label", "Loading " + name);
|
|
182
|
-
fetch("/api/tree?path=" + encodeURIComponent(path))
|
|
183
|
-
.then(function (response) {
|
|
184
|
-
if (!response.ok) throw new Error("load failed");
|
|
185
|
-
return response.json();
|
|
186
|
-
})
|
|
187
|
-
.then(function (data) {
|
|
188
|
-
var built = document.createElement("ul");
|
|
189
|
-
(data.entries || []).forEach(function (entry) {
|
|
190
|
-
built.appendChild(buildNode(entry));
|
|
191
|
-
});
|
|
192
|
-
li.appendChild(built);
|
|
193
|
-
delete li.dataset.loading;
|
|
194
|
-
delete li.dataset.error;
|
|
195
|
-
setOpen(li, true);
|
|
196
|
-
twisty.setAttribute("aria-busy", "false");
|
|
197
|
-
twisty.setAttribute("aria-label", name);
|
|
198
|
-
applyFilter();
|
|
199
|
-
})
|
|
200
|
-
.catch(function () {
|
|
201
|
-
delete li.dataset.loading;
|
|
202
|
-
li.dataset.error = "true";
|
|
203
|
-
li.dataset.open = "false";
|
|
204
|
-
twisty.textContent = "\\u26A0";
|
|
205
|
-
twisty.setAttribute("aria-busy", "false");
|
|
206
|
-
twisty.setAttribute("aria-expanded", "false");
|
|
207
|
-
twisty.setAttribute("aria-label", "Could not load " + name + ". Press to retry.");
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (tree) {
|
|
212
|
-
tree.addEventListener("click", function (event) {
|
|
213
|
-
var twisty = event.target.closest(".bf-tree__tw");
|
|
214
|
-
if (!twisty || twisty.tagName !== "BUTTON" || !tree.contains(twisty)) return;
|
|
215
|
-
event.preventDefault();
|
|
216
|
-
var li = twisty.closest("li");
|
|
217
|
-
if (!li) return;
|
|
218
|
-
var list = li.querySelector(":scope > ul");
|
|
219
|
-
if (li.dataset.open === "true") {
|
|
220
|
-
setOpen(li, false);
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
if (list) {
|
|
224
|
-
setOpen(li, true);
|
|
225
|
-
applyFilter();
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
var row = li.querySelector(":scope > .bf-tree__row");
|
|
229
|
-
var path = row ? row.getAttribute("data-path") : null;
|
|
230
|
-
if (!path) return;
|
|
231
|
-
loadChildren(li, twisty, path);
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
var panel = document.querySelector("details.bf-app__sidebar");
|
|
236
|
-
if (panel && window.matchMedia) {
|
|
237
|
-
var mq = window.matchMedia("(max-width: ${SIDEBAR_COLLAPSE_MAX}px)");
|
|
238
|
-
var syncSide = function () { panel.open = !mq.matches; };
|
|
239
|
-
syncSide();
|
|
240
|
-
if (mq.addEventListener) mq.addEventListener("change", syncSide);
|
|
241
|
-
else if (mq.addListener) mq.addListener(syncSide);
|
|
242
|
-
}
|
|
243
|
-
|
|
23
|
+
${TREE_CLIENT_JS}
|
|
244
24
|
Array.prototype.forEach.call(document.querySelectorAll("img.bf-table__thumb"), function (img) {
|
|
245
25
|
img.addEventListener("error", function () {
|
|
246
26
|
img.hidden = true;
|
|
@@ -366,214 +146,7 @@ export const CLIENT_JS = `(function () {
|
|
|
366
146
|
});
|
|
367
147
|
});
|
|
368
148
|
|
|
369
|
-
|
|
370
|
-
var quickOpenButton = document.getElementById("quick-open");
|
|
371
|
-
var paletteBackdrop = null;
|
|
372
|
-
var paletteInput = null;
|
|
373
|
-
var paletteList = null;
|
|
374
|
-
var paletteStatus = null;
|
|
375
|
-
var paletteResults = [];
|
|
376
|
-
var paletteActive = -1;
|
|
377
|
-
var paletteTimer = null;
|
|
378
|
-
|
|
379
|
-
function paletteOpen() {
|
|
380
|
-
return paletteBackdrop !== null && paletteBackdrop.hidden === false;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
function encodeSegments(path) {
|
|
384
|
-
return path.split("/").map(encodeURIComponent).join("/");
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
function closestRole(node, role) {
|
|
388
|
-
while (node && node !== paletteList) {
|
|
389
|
-
if (node.getAttribute && node.getAttribute("role") === role) return node;
|
|
390
|
-
node = node.parentNode;
|
|
391
|
-
}
|
|
392
|
-
return null;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
function buildPalette() {
|
|
396
|
-
if (paletteBackdrop) return;
|
|
397
|
-
paletteBackdrop = document.createElement("div");
|
|
398
|
-
paletteBackdrop.className = "bf-dialog";
|
|
399
|
-
paletteBackdrop.hidden = true;
|
|
400
|
-
var dialog = document.createElement("div");
|
|
401
|
-
dialog.className = "bf-dialog__panel";
|
|
402
|
-
dialog.setAttribute("role", "dialog");
|
|
403
|
-
dialog.setAttribute("aria-modal", "true");
|
|
404
|
-
dialog.setAttribute("aria-label", "Quick open a file");
|
|
405
|
-
paletteInput = document.createElement("input");
|
|
406
|
-
paletteInput.type = "text";
|
|
407
|
-
paletteInput.className = "bf-dialog__input";
|
|
408
|
-
paletteInput.id = "palette-input";
|
|
409
|
-
paletteInput.setAttribute("role", "combobox");
|
|
410
|
-
paletteInput.setAttribute("aria-expanded", "true");
|
|
411
|
-
paletteInput.setAttribute("aria-controls", "palette-list");
|
|
412
|
-
paletteInput.setAttribute("aria-autocomplete", "list");
|
|
413
|
-
paletteInput.setAttribute("autocomplete", "off");
|
|
414
|
-
paletteInput.setAttribute("placeholder", "Type a file path…");
|
|
415
|
-
paletteInput.setAttribute("aria-label", "Quick open a file");
|
|
416
|
-
paletteList = document.createElement("ul");
|
|
417
|
-
paletteList.className = "bf-dialog__list";
|
|
418
|
-
paletteList.id = "palette-list";
|
|
419
|
-
paletteList.setAttribute("role", "listbox");
|
|
420
|
-
paletteStatus = document.createElement("p");
|
|
421
|
-
paletteStatus.className = "bf-dialog__status";
|
|
422
|
-
paletteStatus.setAttribute("role", "status");
|
|
423
|
-
dialog.appendChild(paletteInput);
|
|
424
|
-
dialog.appendChild(paletteList);
|
|
425
|
-
dialog.appendChild(paletteStatus);
|
|
426
|
-
paletteBackdrop.appendChild(dialog);
|
|
427
|
-
document.body.appendChild(paletteBackdrop);
|
|
428
|
-
|
|
429
|
-
paletteBackdrop.addEventListener("mousedown", function (event) {
|
|
430
|
-
if (event.target === paletteBackdrop) closePalette();
|
|
431
|
-
});
|
|
432
|
-
paletteInput.addEventListener("input", function () {
|
|
433
|
-
if (paletteTimer) window.clearTimeout(paletteTimer);
|
|
434
|
-
paletteTimer = window.setTimeout(runSearch, 80);
|
|
435
|
-
});
|
|
436
|
-
paletteInput.addEventListener("keydown", function (event) {
|
|
437
|
-
if (event.key === "Tab") {
|
|
438
|
-
event.preventDefault();
|
|
439
|
-
} else if (event.key === "ArrowDown" || (event.ctrlKey && event.key === "n")) {
|
|
440
|
-
event.preventDefault();
|
|
441
|
-
moveActive(1);
|
|
442
|
-
} else if (event.key === "ArrowUp" || (event.ctrlKey && event.key === "p")) {
|
|
443
|
-
event.preventDefault();
|
|
444
|
-
moveActive(-1);
|
|
445
|
-
} else if (event.key === "Enter") {
|
|
446
|
-
event.preventDefault();
|
|
447
|
-
openActive();
|
|
448
|
-
} else if (event.key === "Escape") {
|
|
449
|
-
event.preventDefault();
|
|
450
|
-
closePalette();
|
|
451
|
-
}
|
|
452
|
-
});
|
|
453
|
-
paletteList.addEventListener("mousemove", function (event) {
|
|
454
|
-
var option = closestRole(event.target, "option");
|
|
455
|
-
if (option) setActive(Number(option.getAttribute("data-index")));
|
|
456
|
-
});
|
|
457
|
-
paletteList.addEventListener("click", function (event) {
|
|
458
|
-
var option = closestRole(event.target, "option");
|
|
459
|
-
if (option) {
|
|
460
|
-
setActive(Number(option.getAttribute("data-index")));
|
|
461
|
-
openActive();
|
|
462
|
-
}
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
function openPalette() {
|
|
467
|
-
buildPalette();
|
|
468
|
-
paletteBackdrop.hidden = false;
|
|
469
|
-
document.body.classList.add("palette-open");
|
|
470
|
-
paletteInput.value = "";
|
|
471
|
-
renderResults([], "Searching…");
|
|
472
|
-
paletteInput.focus();
|
|
473
|
-
runSearch();
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
function closePalette() {
|
|
477
|
-
if (!paletteBackdrop) return;
|
|
478
|
-
paletteBackdrop.hidden = true;
|
|
479
|
-
document.body.classList.remove("palette-open");
|
|
480
|
-
if (quickOpenButton) quickOpenButton.focus();
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
function runSearch() {
|
|
484
|
-
var query = paletteInput.value.trim();
|
|
485
|
-
fetch("/api/files?q=" + encodeURIComponent(query))
|
|
486
|
-
.then(function (response) {
|
|
487
|
-
if (!response.ok) throw new Error("search failed");
|
|
488
|
-
return response.json();
|
|
489
|
-
})
|
|
490
|
-
.then(function (data) {
|
|
491
|
-
if (paletteInput.value.trim() !== query) return;
|
|
492
|
-
if (query === "") {
|
|
493
|
-
renderResults([], data.total + " files indexed" + (data.truncated ? " (truncated)" : ""));
|
|
494
|
-
} else {
|
|
495
|
-
renderResults(data.results || [], null);
|
|
496
|
-
}
|
|
497
|
-
})
|
|
498
|
-
.catch(function () {
|
|
499
|
-
renderResults([], "Search failed");
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
function appendHighlighted(target, path, positions) {
|
|
504
|
-
var marks = {};
|
|
505
|
-
(positions || []).forEach(function (index) { marks[index] = true; });
|
|
506
|
-
var buffer = "";
|
|
507
|
-
var marked = false;
|
|
508
|
-
function flush() {
|
|
509
|
-
if (buffer === "") return;
|
|
510
|
-
if (marked) {
|
|
511
|
-
var mark = document.createElement("mark");
|
|
512
|
-
mark.textContent = buffer;
|
|
513
|
-
target.appendChild(mark);
|
|
514
|
-
} else {
|
|
515
|
-
target.appendChild(document.createTextNode(buffer));
|
|
516
|
-
}
|
|
517
|
-
buffer = "";
|
|
518
|
-
}
|
|
519
|
-
for (var i = 0; i < path.length; i++) {
|
|
520
|
-
var isMatch = marks[i] === true;
|
|
521
|
-
if (isMatch !== marked) { flush(); marked = isMatch; }
|
|
522
|
-
buffer += path.charAt(i);
|
|
523
|
-
}
|
|
524
|
-
flush();
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
function renderResults(results, hint) {
|
|
528
|
-
paletteResults = results;
|
|
529
|
-
paletteActive = results.length > 0 ? 0 : -1;
|
|
530
|
-
paletteList.textContent = "";
|
|
531
|
-
results.forEach(function (result, index) {
|
|
532
|
-
var option = document.createElement("li");
|
|
533
|
-
option.className = "bf-dialog__option";
|
|
534
|
-
option.id = "palette-option-" + index;
|
|
535
|
-
option.setAttribute("role", "option");
|
|
536
|
-
option.setAttribute("data-index", String(index));
|
|
537
|
-
appendHighlighted(option, result.path, result.positions);
|
|
538
|
-
paletteList.appendChild(option);
|
|
539
|
-
});
|
|
540
|
-
if (hint) paletteStatus.textContent = hint;
|
|
541
|
-
else if (results.length === 0) paletteStatus.textContent = "No matches";
|
|
542
|
-
else paletteStatus.textContent = results.length + (results.length === 1 ? " match" : " matches");
|
|
543
|
-
updateActive();
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
function updateActive() {
|
|
547
|
-
Array.prototype.forEach.call(paletteList.children, function (option, index) {
|
|
548
|
-
option.setAttribute("aria-selected", index === paletteActive ? "true" : "false");
|
|
549
|
-
});
|
|
550
|
-
var active = paletteList.children[paletteActive];
|
|
551
|
-
if (paletteActive >= 0 && active) {
|
|
552
|
-
paletteInput.setAttribute("aria-activedescendant", "palette-option-" + paletteActive);
|
|
553
|
-
if (active.scrollIntoView) active.scrollIntoView({ block: "nearest" });
|
|
554
|
-
} else {
|
|
555
|
-
paletteInput.removeAttribute("aria-activedescendant");
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
function setActive(index) {
|
|
560
|
-
if (index < 0 || index >= paletteResults.length) return;
|
|
561
|
-
paletteActive = index;
|
|
562
|
-
updateActive();
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
function moveActive(delta) {
|
|
566
|
-
if (paletteResults.length === 0) return;
|
|
567
|
-
var next = paletteActive + delta;
|
|
568
|
-
if (next < 0) next = paletteResults.length - 1;
|
|
569
|
-
if (next >= paletteResults.length) next = 0;
|
|
570
|
-
setActive(next);
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
function openActive() {
|
|
574
|
-
var result = paletteResults[paletteActive];
|
|
575
|
-
if (result) window.location.assign("/view/" + encodeSegments(result.path));
|
|
576
|
-
}
|
|
149
|
+
${PALETTE_CLIENT_JS}
|
|
577
150
|
|
|
578
151
|
// Keyboard: the shared shortcut sheet, the per-page actions the server
|
|
579
152
|
// rendered, and hunk-to-hunk navigation through a diff.
|
|
@@ -657,15 +230,10 @@ export const CLIENT_JS = `(function () {
|
|
|
657
230
|
markTableOverflow();
|
|
658
231
|
|
|
659
232
|
document.addEventListener("keydown", function (event) {
|
|
660
|
-
if (
|
|
661
|
-
event.preventDefault();
|
|
662
|
-
window.bfKeys.close();
|
|
663
|
-
return;
|
|
664
|
-
}
|
|
665
|
-
if (paletteOpen()) {
|
|
233
|
+
if (window.bfPalette && window.bfPalette.isOpen()) {
|
|
666
234
|
if (event.key === "Escape") {
|
|
667
235
|
event.preventDefault();
|
|
668
|
-
|
|
236
|
+
window.bfPalette.close();
|
|
669
237
|
}
|
|
670
238
|
return;
|
|
671
239
|
}
|
|
@@ -675,13 +243,13 @@ export const CLIENT_JS = `(function () {
|
|
|
675
243
|
if (editable) return;
|
|
676
244
|
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "k") {
|
|
677
245
|
event.preventDefault();
|
|
678
|
-
|
|
246
|
+
window.bfPalette.open();
|
|
679
247
|
return;
|
|
680
248
|
}
|
|
681
249
|
if (event.metaKey || event.ctrlKey || event.altKey) return;
|
|
682
250
|
if (event.key === "/") {
|
|
683
251
|
event.preventDefault();
|
|
684
|
-
|
|
252
|
+
window.bfPalette.open();
|
|
685
253
|
} else if (event.key === "?") {
|
|
686
254
|
event.preventDefault();
|
|
687
255
|
window.bfKeys.toggle();
|
|
@@ -697,26 +265,11 @@ export const CLIENT_JS = `(function () {
|
|
|
697
265
|
} else if (event.key === "p" && hunkAnchors.length > 0) {
|
|
698
266
|
event.preventDefault();
|
|
699
267
|
moveHunk(-1);
|
|
700
|
-
} else if (event.key === "-" &&
|
|
268
|
+
} else if (event.key === "-" && document.getElementById("fold-all")) {
|
|
701
269
|
event.preventDefault();
|
|
702
|
-
|
|
270
|
+
document.getElementById("fold-all").click();
|
|
703
271
|
}
|
|
704
272
|
});
|
|
705
273
|
|
|
706
|
-
if (quickOpenButton) quickOpenButton.addEventListener("click", openPalette);
|
|
707
|
-
if (foldAllButton) {
|
|
708
|
-
foldAllButton.addEventListener("click", function () {
|
|
709
|
-
// The control folds when anything is open, and expands when nothing is.
|
|
710
|
-
var open = false;
|
|
711
|
-
foldableDirs().forEach(function (li) {
|
|
712
|
-
if (li.dataset.open === "true") open = true;
|
|
713
|
-
});
|
|
714
|
-
setAll(!open);
|
|
715
|
-
});
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
// The control starts in the state the rendered tree implies.
|
|
719
|
-
syncFoldButton();
|
|
720
|
-
|
|
721
274
|
${LIVE_CLIENT_JS}})();
|
|
722
275
|
`;
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { GLYPHS } from "../../lib/tui/glyphs.ts";
|
|
12
|
-
import { brandLockup, docHead, type KeyHint, keysSheet } from "../../lib/web/index.ts";
|
|
12
|
+
import { brandLockup, docHead, type KeyHint, keysSheet, themeToggle } from "../../lib/web/index.ts";
|
|
13
13
|
import { escapeHtml } from "../../lib/escape.ts";
|
|
14
14
|
import { formatSize, type DirEntry, type FileView, type Listing, type TreeNode } from "./files.ts";
|
|
15
15
|
import { DIFF_MODES, statusLetter, type ChangedFile, type DiffLine, type DiffMode } from "./diff.ts";
|
|
@@ -159,7 +159,7 @@ ${SPRITE}
|
|
|
159
159
|
<span class="bf-chip bf-chip--accent">read-only</span>
|
|
160
160
|
${renderLiveChip(layout.live)}
|
|
161
161
|
<button type="button" class="bf-btn bf-btn--icon browse-keys-btn" id="keys-open" aria-label="Keyboard shortcuts" title="Keyboard shortcuts">?</button>
|
|
162
|
-
|
|
162
|
+
${themeToggle()}
|
|
163
163
|
</div><div class="browse-progress" aria-hidden="true"><span class="browse-progress__fill" id="progress-fill"></span></div></header>
|
|
164
164
|
<div class="bf-app__body">
|
|
165
165
|
<details class="bf-app__sidebar browse-sidebar" open>
|
|
@@ -213,7 +213,7 @@ function renderTreeNode(node: TreeNode, currentRel: string, summary?: ChangeSumm
|
|
|
213
213
|
const href = hrefFor(node);
|
|
214
214
|
const current = node.rel === currentRel ? ` aria-current="page"` : "";
|
|
215
215
|
const twisty = isDir
|
|
216
|
-
? `<button type="button" class="bf-tree__tw bf-tree__tw--toggle"
|
|
216
|
+
? `<button type="button" class="bf-tree__tw bf-tree__tw--toggle" tabindex="-1" aria-hidden="true">${expanded ? "▾" : "▸"}</button>`
|
|
217
217
|
: `<span class="bf-tree__tw bf-tree__tw--spacer" aria-hidden="true"></span>`;
|
|
218
218
|
const dataPath = ` data-path="${escapeHtml(node.rel)}"`;
|
|
219
219
|
const name = isDir ? `${escapeHtml(node.name)}/` : escapeHtml(node.name);
|
|
@@ -222,7 +222,9 @@ function renderTreeNode(node: TreeNode, currentRel: string, summary?: ChangeSumm
|
|
|
222
222
|
: "";
|
|
223
223
|
const label = node.broken
|
|
224
224
|
? `<span class="browse-broken" title="broken symbolic link">${name}</span><span class="browse-link-mark">↩</span>`
|
|
225
|
-
:
|
|
225
|
+
: isDir
|
|
226
|
+
? `<a href="${href}" aria-expanded="${expanded}" title="${escapeHtml(node.rel)}">${name}</a>`
|
|
227
|
+
: `<a href="${href}" title="${escapeHtml(node.rel)}">${name}</a>`;
|
|
226
228
|
return `<li data-open="${expanded}"><div class="bf-tree__row"${current}${dataPath}>${twisty}${iconMarkup(spec)}${label}${changeMark(summary, node.rel, isDir)}</div>${children}</li>`;
|
|
227
229
|
}
|
|
228
230
|
|
|
@@ -238,7 +240,7 @@ function renderRow(entry: DirEntry, maxThumbBytes: number, thumbnails: boolean,
|
|
|
238
240
|
.join(" ");
|
|
239
241
|
const link = entry.broken
|
|
240
242
|
? `<span class="browse-broken" title="broken symbolic link">${name}</span>`
|
|
241
|
-
: `<a href="${hrefFor(entry)}">${name}</a>`;
|
|
243
|
+
: `<a href="${hrefFor(entry)}" title="${escapeHtml(entry.rel)}">${name}</a>`;
|
|
242
244
|
const mark = entry.isSymlink
|
|
243
245
|
? `<span class="browse-link-mark" title="${entry.broken ? "broken symbolic link" : "symbolic link"}">↩</span>`
|
|
244
246
|
: "";
|