@ondrej-svec/hog 1.1.3 → 1.3.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/dist/cli.js +1825 -942
- package/dist/cli.js.map +1 -1
- package/dist/fetch-worker.js.map +1 -1
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -9,6 +9,166 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
// src/ai.ts
|
|
13
|
+
async function parseHeuristic(input2, today = /* @__PURE__ */ new Date()) {
|
|
14
|
+
let remaining = input2;
|
|
15
|
+
const labelMatches = [...remaining.matchAll(/#([\w:/-]+)/g)];
|
|
16
|
+
const rawLabels = labelMatches.map((m) => (m[1] ?? "").toLowerCase());
|
|
17
|
+
remaining = remaining.replace(/#[\w:/-]+/g, "").trim();
|
|
18
|
+
const assigneeMatches = [...remaining.matchAll(/@([\w-]+)/g)];
|
|
19
|
+
const assignee = assigneeMatches.length > 0 ? assigneeMatches[assigneeMatches.length - 1]?.[1] ?? null : null;
|
|
20
|
+
remaining = remaining.replace(/@[\w-]+/g, "").trim();
|
|
21
|
+
let dueDate = null;
|
|
22
|
+
const dueMatch = remaining.match(/\bdue\s+(.+?)(?:\s+#|\s+@|$)/i);
|
|
23
|
+
if (dueMatch?.[1]) {
|
|
24
|
+
const { parse } = await import("chrono-node");
|
|
25
|
+
const results = parse(dueMatch[1], { instant: today }, { forwardDate: true });
|
|
26
|
+
const first = results[0];
|
|
27
|
+
if (first) {
|
|
28
|
+
let date = first.date();
|
|
29
|
+
if (date < today) {
|
|
30
|
+
date = new Date(date);
|
|
31
|
+
date.setFullYear(date.getFullYear() + 1);
|
|
32
|
+
}
|
|
33
|
+
const yyyy = date.getFullYear();
|
|
34
|
+
const mm = String(date.getMonth() + 1).padStart(2, "0");
|
|
35
|
+
const dd = String(date.getDate()).padStart(2, "0");
|
|
36
|
+
dueDate = `${yyyy}-${mm}-${dd}`;
|
|
37
|
+
}
|
|
38
|
+
remaining = remaining.slice(0, dueMatch.index ?? 0).trim();
|
|
39
|
+
}
|
|
40
|
+
const title = remaining.replace(/\s+/g, " ").trim();
|
|
41
|
+
if (!title) return null;
|
|
42
|
+
return { title, labels: rawLabels, assignee, dueDate };
|
|
43
|
+
}
|
|
44
|
+
function detectProvider() {
|
|
45
|
+
const orKey = process.env["OPENROUTER_API_KEY"];
|
|
46
|
+
if (orKey) return { provider: "openrouter", apiKey: orKey };
|
|
47
|
+
const antKey = process.env["ANTHROPIC_API_KEY"];
|
|
48
|
+
if (antKey) return { provider: "anthropic", apiKey: antKey };
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
async function callLLM(userText, validLabels, today, providerConfig) {
|
|
52
|
+
const { provider, apiKey } = providerConfig;
|
|
53
|
+
const todayStr = today.toISOString().slice(0, 10);
|
|
54
|
+
const systemPrompt = `Extract GitHub issue fields. Today is ${todayStr}. Return JSON with: title (string), labels (string[]), due_date (YYYY-MM-DD or null), assignee (string or null).`;
|
|
55
|
+
const escapedText = userText.replace(/<\/input>/gi, "< /input>");
|
|
56
|
+
const userContent = `<input>${escapedText}</input>
|
|
57
|
+
<valid_labels>${validLabels.join(",")}</valid_labels>`;
|
|
58
|
+
const jsonSchema = {
|
|
59
|
+
name: "issue",
|
|
60
|
+
schema: {
|
|
61
|
+
type: "object",
|
|
62
|
+
properties: {
|
|
63
|
+
title: { type: "string" },
|
|
64
|
+
labels: { type: "array", items: { type: "string" } },
|
|
65
|
+
due_date: { type: ["string", "null"] },
|
|
66
|
+
assignee: { type: ["string", "null"] }
|
|
67
|
+
},
|
|
68
|
+
required: ["title", "labels", "due_date", "assignee"],
|
|
69
|
+
additionalProperties: false
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
try {
|
|
73
|
+
let response;
|
|
74
|
+
if (provider === "openrouter") {
|
|
75
|
+
response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
|
|
76
|
+
method: "POST",
|
|
77
|
+
headers: {
|
|
78
|
+
"Content-Type": "application/json",
|
|
79
|
+
Authorization: `Bearer ${apiKey}`
|
|
80
|
+
},
|
|
81
|
+
body: JSON.stringify({
|
|
82
|
+
model: "google/gemini-2.5-flash",
|
|
83
|
+
messages: [
|
|
84
|
+
{ role: "system", content: systemPrompt },
|
|
85
|
+
{ role: "user", content: userContent }
|
|
86
|
+
],
|
|
87
|
+
response_format: { type: "json_schema", json_schema: jsonSchema },
|
|
88
|
+
max_tokens: 256,
|
|
89
|
+
temperature: 0
|
|
90
|
+
}),
|
|
91
|
+
signal: AbortSignal.timeout(5e3)
|
|
92
|
+
});
|
|
93
|
+
} else {
|
|
94
|
+
response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
95
|
+
method: "POST",
|
|
96
|
+
headers: {
|
|
97
|
+
"Content-Type": "application/json",
|
|
98
|
+
"x-api-key": apiKey,
|
|
99
|
+
"anthropic-version": "2023-06-01"
|
|
100
|
+
},
|
|
101
|
+
body: JSON.stringify({
|
|
102
|
+
model: "claude-haiku-4-5-20251001",
|
|
103
|
+
system: systemPrompt,
|
|
104
|
+
messages: [{ role: "user", content: userContent }],
|
|
105
|
+
max_tokens: 256
|
|
106
|
+
}),
|
|
107
|
+
signal: AbortSignal.timeout(5e3)
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
if (!response.ok) return null;
|
|
111
|
+
const json = await response.json();
|
|
112
|
+
let raw;
|
|
113
|
+
if (provider === "openrouter") {
|
|
114
|
+
const choicesRaw = json["choices"];
|
|
115
|
+
if (!Array.isArray(choicesRaw)) return null;
|
|
116
|
+
const firstChoice = choicesRaw[0];
|
|
117
|
+
const content = firstChoice?.message?.content;
|
|
118
|
+
if (!content) return null;
|
|
119
|
+
raw = JSON.parse(content);
|
|
120
|
+
} else {
|
|
121
|
+
const contentRaw = json["content"];
|
|
122
|
+
if (!Array.isArray(contentRaw)) return null;
|
|
123
|
+
const firstItem = contentRaw[0];
|
|
124
|
+
const text = firstItem?.text;
|
|
125
|
+
if (!text) return null;
|
|
126
|
+
raw = JSON.parse(text);
|
|
127
|
+
}
|
|
128
|
+
if (!raw || typeof raw !== "object") return null;
|
|
129
|
+
const r = raw;
|
|
130
|
+
const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
131
|
+
return {
|
|
132
|
+
title: typeof r["title"] === "string" ? r["title"] : "",
|
|
133
|
+
labels: Array.isArray(r["labels"]) ? r["labels"].filter((l) => typeof l === "string") : [],
|
|
134
|
+
due_date: typeof r["due_date"] === "string" && ISO_DATE_RE.test(r["due_date"]) ? r["due_date"] : null,
|
|
135
|
+
assignee: typeof r["assignee"] === "string" ? r["assignee"] : null
|
|
136
|
+
};
|
|
137
|
+
} catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async function extractIssueFields(input2, options = {}) {
|
|
142
|
+
const today = options.today ?? /* @__PURE__ */ new Date();
|
|
143
|
+
const heuristic = await parseHeuristic(input2, today);
|
|
144
|
+
if (!heuristic) return null;
|
|
145
|
+
const providerConfig = detectProvider();
|
|
146
|
+
if (!providerConfig) return heuristic;
|
|
147
|
+
const llmResult = await callLLM(input2, options.validLabels ?? [], today, providerConfig);
|
|
148
|
+
if (!llmResult) {
|
|
149
|
+
options.onLlmFallback?.("AI parsing unavailable, used keyword matching");
|
|
150
|
+
return heuristic;
|
|
151
|
+
}
|
|
152
|
+
const merged = {
|
|
153
|
+
...llmResult,
|
|
154
|
+
// Heuristic explicit tokens always win
|
|
155
|
+
labels: heuristic.labels.length > 0 ? heuristic.labels : llmResult.labels,
|
|
156
|
+
assignee: heuristic.assignee ?? llmResult.assignee,
|
|
157
|
+
dueDate: heuristic.dueDate ?? llmResult.due_date,
|
|
158
|
+
// LLM title is used only if heuristic left explicit tokens
|
|
159
|
+
title: heuristic.labels.length > 0 || heuristic.assignee || heuristic.dueDate ? llmResult.title || heuristic.title : heuristic.title
|
|
160
|
+
};
|
|
161
|
+
return merged;
|
|
162
|
+
}
|
|
163
|
+
function hasLlmApiKey() {
|
|
164
|
+
return detectProvider() !== null;
|
|
165
|
+
}
|
|
166
|
+
var init_ai = __esm({
|
|
167
|
+
"src/ai.ts"() {
|
|
168
|
+
"use strict";
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
12
172
|
// src/api.ts
|
|
13
173
|
var BASE_URL, TickTickClient;
|
|
14
174
|
var init_api = __esm({
|
|
@@ -164,13 +324,6 @@ function getAuth() {
|
|
|
164
324
|
return null;
|
|
165
325
|
}
|
|
166
326
|
}
|
|
167
|
-
function saveAuth(data) {
|
|
168
|
-
ensureDir();
|
|
169
|
-
writeFileSync(AUTH_FILE, `${JSON.stringify(data, null, 2)}
|
|
170
|
-
`, {
|
|
171
|
-
mode: 384
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
327
|
function getConfig() {
|
|
175
328
|
if (!existsSync(CONFIG_FILE)) return {};
|
|
176
329
|
try {
|
|
@@ -468,6 +621,21 @@ function fetchProjectStatusOptions(repo, projectNumber, _statusFieldId) {
|
|
|
468
621
|
function addLabel(repo, issueNumber, label) {
|
|
469
622
|
runGh(["issue", "edit", String(issueNumber), "--repo", repo, "--add-label", label]);
|
|
470
623
|
}
|
|
624
|
+
async function fetchRepoLabelsAsync(repo) {
|
|
625
|
+
try {
|
|
626
|
+
const result = await runGhJsonAsync([
|
|
627
|
+
"label",
|
|
628
|
+
"list",
|
|
629
|
+
"--repo",
|
|
630
|
+
repo,
|
|
631
|
+
"--json",
|
|
632
|
+
"name,color"
|
|
633
|
+
]);
|
|
634
|
+
return Array.isArray(result) ? result : [];
|
|
635
|
+
} catch {
|
|
636
|
+
return [];
|
|
637
|
+
}
|
|
638
|
+
}
|
|
471
639
|
function updateProjectItemStatus(repo, issueNumber, projectConfig) {
|
|
472
640
|
const [owner, repoName] = repo.split("/");
|
|
473
641
|
const findItemQuery = `
|
|
@@ -685,6 +853,21 @@ var init_sync_state = __esm({
|
|
|
685
853
|
}
|
|
686
854
|
});
|
|
687
855
|
|
|
856
|
+
// src/clipboard.ts
|
|
857
|
+
function getClipboardArgs() {
|
|
858
|
+
if (process.platform === "darwin") return ["pbcopy"];
|
|
859
|
+
if (process.platform === "win32") return ["clip"];
|
|
860
|
+
if (process.env["WSL_DISTRO_NAME"] ?? process.env["WSL_INTEROP"]) return ["clip.exe"];
|
|
861
|
+
if (process.env["WAYLAND_DISPLAY"]) return ["wl-copy"];
|
|
862
|
+
if (process.env["DISPLAY"]) return ["xsel", "--clipboard", "--input"];
|
|
863
|
+
return null;
|
|
864
|
+
}
|
|
865
|
+
var init_clipboard = __esm({
|
|
866
|
+
"src/clipboard.ts"() {
|
|
867
|
+
"use strict";
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
|
|
688
871
|
// src/pick.ts
|
|
689
872
|
var pick_exports = {};
|
|
690
873
|
__export(pick_exports, {
|
|
@@ -1041,6 +1224,26 @@ function useActions({
|
|
|
1041
1224
|
},
|
|
1042
1225
|
[toast, refresh, onOverlayDone]
|
|
1043
1226
|
);
|
|
1227
|
+
const handleLabelChange = useCallback(
|
|
1228
|
+
(addLabels, removeLabels) => {
|
|
1229
|
+
const ctx = findIssueContext(reposRef.current, selectedIdRef.current, configRef.current);
|
|
1230
|
+
if (!(ctx.issue && ctx.repoName)) return;
|
|
1231
|
+
const { issue, repoName } = ctx;
|
|
1232
|
+
const args = ["issue", "edit", String(issue.number), "--repo", repoName];
|
|
1233
|
+
for (const label of addLabels) args.push("--add-label", label);
|
|
1234
|
+
for (const label of removeLabels) args.push("--remove-label", label);
|
|
1235
|
+
const t = toast.loading("Updating labels...");
|
|
1236
|
+
execFileAsync2("gh", args, { encoding: "utf-8", timeout: 3e4 }).then(() => {
|
|
1237
|
+
t.resolve(`Labels updated on #${issue.number}`);
|
|
1238
|
+
refresh();
|
|
1239
|
+
onOverlayDone();
|
|
1240
|
+
}).catch((err) => {
|
|
1241
|
+
t.reject(`Label update failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1242
|
+
onOverlayDone();
|
|
1243
|
+
});
|
|
1244
|
+
},
|
|
1245
|
+
[toast, refresh, onOverlayDone]
|
|
1246
|
+
);
|
|
1044
1247
|
const handleBulkAssign = useCallback(
|
|
1045
1248
|
async (ids) => {
|
|
1046
1249
|
const failed = [];
|
|
@@ -1171,6 +1374,7 @@ function useActions({
|
|
|
1171
1374
|
handleStatusChange,
|
|
1172
1375
|
handleAssign,
|
|
1173
1376
|
handleUnassign,
|
|
1377
|
+
handleLabelChange,
|
|
1174
1378
|
handleCreateIssue,
|
|
1175
1379
|
handleBulkAssign,
|
|
1176
1380
|
handleBulkUnassign,
|
|
@@ -1306,7 +1510,13 @@ function useData(config2, options, refreshIntervalMs) {
|
|
|
1306
1510
|
return { ...prev, data: fn(prev.data) };
|
|
1307
1511
|
});
|
|
1308
1512
|
}, []);
|
|
1309
|
-
|
|
1513
|
+
const pauseAutoRefresh = useCallback2(() => {
|
|
1514
|
+
setState((prev) => ({ ...prev, autoRefreshPaused: true }));
|
|
1515
|
+
}, []);
|
|
1516
|
+
const resumeAutoRefresh = useCallback2(() => {
|
|
1517
|
+
setState((prev) => ({ ...prev, autoRefreshPaused: false }));
|
|
1518
|
+
}, []);
|
|
1519
|
+
return { ...state, refresh, mutateData, pauseAutoRefresh, resumeAutoRefresh };
|
|
1310
1520
|
}
|
|
1311
1521
|
var INITIAL_STATE, STALE_THRESHOLDS, MAX_REFRESH_FAILURES;
|
|
1312
1522
|
var init_use_data = __esm({
|
|
@@ -1332,14 +1542,234 @@ var init_use_data = __esm({
|
|
|
1332
1542
|
}
|
|
1333
1543
|
});
|
|
1334
1544
|
|
|
1545
|
+
// src/board/hooks/use-keyboard.ts
|
|
1546
|
+
import { useInput } from "ink";
|
|
1547
|
+
import { useCallback as useCallback3 } from "react";
|
|
1548
|
+
function isHeaderId(id) {
|
|
1549
|
+
return id != null && (id.startsWith("header:") || id.startsWith("sub:"));
|
|
1550
|
+
}
|
|
1551
|
+
function useKeyboard({
|
|
1552
|
+
ui,
|
|
1553
|
+
nav,
|
|
1554
|
+
multiSelect,
|
|
1555
|
+
selectedIssue,
|
|
1556
|
+
selectedRepoStatusOptionsLength,
|
|
1557
|
+
actions,
|
|
1558
|
+
onSearchEscape
|
|
1559
|
+
}) {
|
|
1560
|
+
const {
|
|
1561
|
+
exit,
|
|
1562
|
+
refresh,
|
|
1563
|
+
handleSlack,
|
|
1564
|
+
handleCopyLink,
|
|
1565
|
+
handleOpen,
|
|
1566
|
+
handleEnterFocus,
|
|
1567
|
+
handlePick,
|
|
1568
|
+
handleAssign,
|
|
1569
|
+
handleUnassign,
|
|
1570
|
+
handleEnterLabel,
|
|
1571
|
+
handleEnterCreateNl,
|
|
1572
|
+
handleErrorAction,
|
|
1573
|
+
toastInfo
|
|
1574
|
+
} = actions;
|
|
1575
|
+
const handleInput = useCallback3(
|
|
1576
|
+
(input2, key) => {
|
|
1577
|
+
if (input2 === "?") {
|
|
1578
|
+
ui.toggleHelp();
|
|
1579
|
+
return;
|
|
1580
|
+
}
|
|
1581
|
+
if (key.escape && ui.state.mode !== "focus") {
|
|
1582
|
+
if (ui.state.mode === "multiSelect") {
|
|
1583
|
+
multiSelect.clear();
|
|
1584
|
+
}
|
|
1585
|
+
ui.exitOverlay();
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1588
|
+
if (ui.canNavigate) {
|
|
1589
|
+
if (input2 === "j" || key.downArrow) {
|
|
1590
|
+
nav.moveDown();
|
|
1591
|
+
return;
|
|
1592
|
+
}
|
|
1593
|
+
if (input2 === "k" || key.upArrow) {
|
|
1594
|
+
nav.moveUp();
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1597
|
+
if (key.tab) {
|
|
1598
|
+
if (ui.state.mode === "multiSelect") {
|
|
1599
|
+
multiSelect.clear();
|
|
1600
|
+
ui.clearMultiSelect();
|
|
1601
|
+
}
|
|
1602
|
+
key.shift ? nav.prevSection() : nav.nextSection();
|
|
1603
|
+
return;
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
if (ui.state.mode === "multiSelect") {
|
|
1607
|
+
if (input2 === " ") {
|
|
1608
|
+
const id = nav.selectedId;
|
|
1609
|
+
if (id && !isHeaderId(id)) {
|
|
1610
|
+
multiSelect.toggle(id);
|
|
1611
|
+
}
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1614
|
+
if (key.return) {
|
|
1615
|
+
if (multiSelect.count > 0) {
|
|
1616
|
+
ui.enterBulkAction();
|
|
1617
|
+
}
|
|
1618
|
+
return;
|
|
1619
|
+
}
|
|
1620
|
+
if (input2 === "m" && multiSelect.count > 0) {
|
|
1621
|
+
ui.enterBulkAction();
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1626
|
+
if (input2 === "d") {
|
|
1627
|
+
if (handleErrorAction("dismiss")) return;
|
|
1628
|
+
}
|
|
1629
|
+
if (input2 === "r" && handleErrorAction("retry")) return;
|
|
1630
|
+
if (ui.canAct) {
|
|
1631
|
+
if (input2 === "/") {
|
|
1632
|
+
multiSelect.clear();
|
|
1633
|
+
ui.enterSearch();
|
|
1634
|
+
return;
|
|
1635
|
+
}
|
|
1636
|
+
if (input2 === "q") {
|
|
1637
|
+
exit();
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
if (input2 === "r" || input2 === "R") {
|
|
1641
|
+
multiSelect.clear();
|
|
1642
|
+
refresh();
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
if (input2 === "s") {
|
|
1646
|
+
handleSlack();
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
if (input2 === "y") {
|
|
1650
|
+
handleCopyLink();
|
|
1651
|
+
return;
|
|
1652
|
+
}
|
|
1653
|
+
if (input2 === "p") {
|
|
1654
|
+
handlePick();
|
|
1655
|
+
return;
|
|
1656
|
+
}
|
|
1657
|
+
if (input2 === "a") {
|
|
1658
|
+
handleAssign();
|
|
1659
|
+
return;
|
|
1660
|
+
}
|
|
1661
|
+
if (input2 === "u") {
|
|
1662
|
+
handleUnassign();
|
|
1663
|
+
return;
|
|
1664
|
+
}
|
|
1665
|
+
if (input2 === "c") {
|
|
1666
|
+
if (selectedIssue) {
|
|
1667
|
+
multiSelect.clear();
|
|
1668
|
+
ui.enterComment();
|
|
1669
|
+
}
|
|
1670
|
+
return;
|
|
1671
|
+
}
|
|
1672
|
+
if (input2 === "m") {
|
|
1673
|
+
if (selectedIssue && selectedRepoStatusOptionsLength > 0) {
|
|
1674
|
+
multiSelect.clear();
|
|
1675
|
+
ui.enterStatus();
|
|
1676
|
+
} else if (selectedIssue) {
|
|
1677
|
+
toastInfo("Issue not in a project board");
|
|
1678
|
+
}
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
if (input2 === "n") {
|
|
1682
|
+
multiSelect.clear();
|
|
1683
|
+
ui.enterCreate();
|
|
1684
|
+
return;
|
|
1685
|
+
}
|
|
1686
|
+
if (input2 === "f") {
|
|
1687
|
+
handleEnterFocus();
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1690
|
+
if (input2 === "C") {
|
|
1691
|
+
nav.collapseAll();
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
if (input2 === "l") {
|
|
1695
|
+
if (selectedIssue) {
|
|
1696
|
+
multiSelect.clear();
|
|
1697
|
+
handleEnterLabel();
|
|
1698
|
+
}
|
|
1699
|
+
return;
|
|
1700
|
+
}
|
|
1701
|
+
if (input2 === "I") {
|
|
1702
|
+
handleEnterCreateNl();
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1705
|
+
if (input2 === " ") {
|
|
1706
|
+
const id = nav.selectedId;
|
|
1707
|
+
if (id && !isHeaderId(id)) {
|
|
1708
|
+
multiSelect.toggle(id);
|
|
1709
|
+
ui.enterMultiSelect();
|
|
1710
|
+
} else if (isHeaderId(nav.selectedId)) {
|
|
1711
|
+
nav.toggleSection();
|
|
1712
|
+
}
|
|
1713
|
+
return;
|
|
1714
|
+
}
|
|
1715
|
+
if (key.return) {
|
|
1716
|
+
if (isHeaderId(nav.selectedId)) {
|
|
1717
|
+
nav.toggleSection();
|
|
1718
|
+
return;
|
|
1719
|
+
}
|
|
1720
|
+
handleOpen();
|
|
1721
|
+
return;
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
},
|
|
1725
|
+
[
|
|
1726
|
+
ui,
|
|
1727
|
+
nav,
|
|
1728
|
+
exit,
|
|
1729
|
+
refresh,
|
|
1730
|
+
handleSlack,
|
|
1731
|
+
handleCopyLink,
|
|
1732
|
+
handleOpen,
|
|
1733
|
+
handlePick,
|
|
1734
|
+
handleAssign,
|
|
1735
|
+
handleUnassign,
|
|
1736
|
+
handleEnterLabel,
|
|
1737
|
+
handleEnterCreateNl,
|
|
1738
|
+
selectedIssue,
|
|
1739
|
+
selectedRepoStatusOptionsLength,
|
|
1740
|
+
toastInfo,
|
|
1741
|
+
nav.selectedId,
|
|
1742
|
+
multiSelect,
|
|
1743
|
+
handleEnterFocus,
|
|
1744
|
+
handleErrorAction
|
|
1745
|
+
]
|
|
1746
|
+
);
|
|
1747
|
+
const inputActive = ui.state.mode === "normal" || ui.state.mode === "multiSelect" || ui.state.mode === "focus";
|
|
1748
|
+
useInput(handleInput, { isActive: inputActive });
|
|
1749
|
+
const handleSearchEscape = useCallback3(
|
|
1750
|
+
(_input, key) => {
|
|
1751
|
+
if (key.escape) {
|
|
1752
|
+
onSearchEscape();
|
|
1753
|
+
}
|
|
1754
|
+
},
|
|
1755
|
+
[onSearchEscape]
|
|
1756
|
+
);
|
|
1757
|
+
useInput(handleSearchEscape, { isActive: ui.state.mode === "search" });
|
|
1758
|
+
}
|
|
1759
|
+
var init_use_keyboard = __esm({
|
|
1760
|
+
"src/board/hooks/use-keyboard.ts"() {
|
|
1761
|
+
"use strict";
|
|
1762
|
+
}
|
|
1763
|
+
});
|
|
1764
|
+
|
|
1335
1765
|
// src/board/hooks/use-multi-select.ts
|
|
1336
|
-
import { useCallback as
|
|
1766
|
+
import { useCallback as useCallback4, useRef as useRef3, useState as useState2 } from "react";
|
|
1337
1767
|
function useMultiSelect(getRepoForId) {
|
|
1338
1768
|
const [selected, setSelected] = useState2(/* @__PURE__ */ new Set());
|
|
1339
1769
|
const repoRef = useRef3(null);
|
|
1340
1770
|
const getRepoRef = useRef3(getRepoForId);
|
|
1341
1771
|
getRepoRef.current = getRepoForId;
|
|
1342
|
-
const toggle =
|
|
1772
|
+
const toggle = useCallback4((id) => {
|
|
1343
1773
|
setSelected((prev) => {
|
|
1344
1774
|
const repo = getRepoRef.current(id);
|
|
1345
1775
|
if (!repo) return prev;
|
|
@@ -1357,11 +1787,11 @@ function useMultiSelect(getRepoForId) {
|
|
|
1357
1787
|
return next;
|
|
1358
1788
|
});
|
|
1359
1789
|
}, []);
|
|
1360
|
-
const clear =
|
|
1790
|
+
const clear = useCallback4(() => {
|
|
1361
1791
|
setSelected(/* @__PURE__ */ new Set());
|
|
1362
1792
|
repoRef.current = null;
|
|
1363
1793
|
}, []);
|
|
1364
|
-
const prune =
|
|
1794
|
+
const prune = useCallback4((validIds) => {
|
|
1365
1795
|
setSelected((prev) => {
|
|
1366
1796
|
const next = /* @__PURE__ */ new Set();
|
|
1367
1797
|
for (const id of prev) {
|
|
@@ -1372,7 +1802,7 @@ function useMultiSelect(getRepoForId) {
|
|
|
1372
1802
|
return next;
|
|
1373
1803
|
});
|
|
1374
1804
|
}, []);
|
|
1375
|
-
const isSelected =
|
|
1805
|
+
const isSelected = useCallback4((id) => selected.has(id), [selected]);
|
|
1376
1806
|
return {
|
|
1377
1807
|
selected,
|
|
1378
1808
|
count: selected.size,
|
|
@@ -1390,7 +1820,7 @@ var init_use_multi_select = __esm({
|
|
|
1390
1820
|
});
|
|
1391
1821
|
|
|
1392
1822
|
// src/board/hooks/use-navigation.ts
|
|
1393
|
-
import { useCallback as
|
|
1823
|
+
import { useCallback as useCallback5, useMemo, useReducer, useRef as useRef4 } from "react";
|
|
1394
1824
|
function arraysEqual(a, b) {
|
|
1395
1825
|
if (a.length !== b.length) return false;
|
|
1396
1826
|
for (let i = 0; i < a.length; i++) {
|
|
@@ -1412,7 +1842,7 @@ function navReducer(state, action) {
|
|
|
1412
1842
|
case "SET_ITEMS": {
|
|
1413
1843
|
const sections = [...new Set(action.items.map((i) => i.section))];
|
|
1414
1844
|
const isFirstLoad = state.sections.length === 0;
|
|
1415
|
-
const collapsedSections = isFirstLoad ? new Set(sections) : state.collapsedSections;
|
|
1845
|
+
const collapsedSections = isFirstLoad ? new Set(sections.filter((s) => s === "activity")) : state.collapsedSections;
|
|
1416
1846
|
const selectionValid = state.selectedId != null && action.items.some((i) => i.id === state.selectedId);
|
|
1417
1847
|
if (!isFirstLoad && selectionValid && arraysEqual(sections, state.sections)) {
|
|
1418
1848
|
return state;
|
|
@@ -1450,6 +1880,9 @@ function navReducer(state, action) {
|
|
|
1450
1880
|
}
|
|
1451
1881
|
return { ...state, collapsedSections: next };
|
|
1452
1882
|
}
|
|
1883
|
+
case "COLLAPSE_ALL": {
|
|
1884
|
+
return { ...state, collapsedSections: new Set(state.sections) };
|
|
1885
|
+
}
|
|
1453
1886
|
default:
|
|
1454
1887
|
return state;
|
|
1455
1888
|
}
|
|
@@ -1484,17 +1917,17 @@ function useNavigation(allItems) {
|
|
|
1484
1917
|
const idx = visibleItems.findIndex((i) => i.id === state.selectedId);
|
|
1485
1918
|
return idx >= 0 ? idx : 0;
|
|
1486
1919
|
}, [state.selectedId, visibleItems]);
|
|
1487
|
-
const moveUp =
|
|
1920
|
+
const moveUp = useCallback5(() => {
|
|
1488
1921
|
const newIdx = Math.max(0, selectedIndex - 1);
|
|
1489
1922
|
const item = visibleItems[newIdx];
|
|
1490
1923
|
if (item) dispatch({ type: "SELECT", id: item.id, section: item.section });
|
|
1491
1924
|
}, [selectedIndex, visibleItems]);
|
|
1492
|
-
const moveDown =
|
|
1925
|
+
const moveDown = useCallback5(() => {
|
|
1493
1926
|
const newIdx = Math.min(visibleItems.length - 1, selectedIndex + 1);
|
|
1494
1927
|
const item = visibleItems[newIdx];
|
|
1495
1928
|
if (item) dispatch({ type: "SELECT", id: item.id, section: item.section });
|
|
1496
1929
|
}, [selectedIndex, visibleItems]);
|
|
1497
|
-
const nextSection =
|
|
1930
|
+
const nextSection = useCallback5(() => {
|
|
1498
1931
|
const currentItem = visibleItems[selectedIndex];
|
|
1499
1932
|
if (!currentItem) return;
|
|
1500
1933
|
const currentSectionIdx = state.sections.indexOf(currentItem.section);
|
|
@@ -1503,7 +1936,7 @@ function useNavigation(allItems) {
|
|
|
1503
1936
|
const header = visibleItems.find((i) => i.section === nextSectionId && i.type === "header");
|
|
1504
1937
|
if (header) dispatch({ type: "SELECT", id: header.id, section: header.section });
|
|
1505
1938
|
}, [selectedIndex, visibleItems, state.sections]);
|
|
1506
|
-
const prevSection =
|
|
1939
|
+
const prevSection = useCallback5(() => {
|
|
1507
1940
|
const currentItem = visibleItems[selectedIndex];
|
|
1508
1941
|
if (!currentItem) return;
|
|
1509
1942
|
const currentSectionIdx = state.sections.indexOf(currentItem.section);
|
|
@@ -1512,19 +1945,22 @@ function useNavigation(allItems) {
|
|
|
1512
1945
|
const header = visibleItems.find((i) => i.section === prevSectionId && i.type === "header");
|
|
1513
1946
|
if (header) dispatch({ type: "SELECT", id: header.id, section: header.section });
|
|
1514
1947
|
}, [selectedIndex, visibleItems, state.sections]);
|
|
1515
|
-
const toggleSection =
|
|
1948
|
+
const toggleSection = useCallback5(() => {
|
|
1516
1949
|
const currentItem = visibleItems[selectedIndex];
|
|
1517
1950
|
if (!currentItem) return;
|
|
1518
1951
|
const key = currentItem.type === "subHeader" ? currentItem.id : currentItem.section;
|
|
1519
1952
|
dispatch({ type: "TOGGLE_SECTION", section: key });
|
|
1520
1953
|
}, [selectedIndex, visibleItems]);
|
|
1954
|
+
const collapseAll = useCallback5(() => {
|
|
1955
|
+
dispatch({ type: "COLLAPSE_ALL" });
|
|
1956
|
+
}, []);
|
|
1521
1957
|
const allItemsRef = useRef4(allItems);
|
|
1522
1958
|
allItemsRef.current = allItems;
|
|
1523
|
-
const select2 =
|
|
1959
|
+
const select2 = useCallback5((id) => {
|
|
1524
1960
|
const item = allItemsRef.current.find((i) => i.id === id);
|
|
1525
1961
|
dispatch({ type: "SELECT", id, section: item?.section });
|
|
1526
1962
|
}, []);
|
|
1527
|
-
const isCollapsed =
|
|
1963
|
+
const isCollapsed = useCallback5(
|
|
1528
1964
|
(section) => state.collapsedSections.has(section),
|
|
1529
1965
|
[state.collapsedSections]
|
|
1530
1966
|
);
|
|
@@ -1537,6 +1973,7 @@ function useNavigation(allItems) {
|
|
|
1537
1973
|
nextSection,
|
|
1538
1974
|
prevSection,
|
|
1539
1975
|
toggleSection,
|
|
1976
|
+
collapseAll,
|
|
1540
1977
|
select: select2,
|
|
1541
1978
|
isCollapsed
|
|
1542
1979
|
};
|
|
@@ -1548,25 +1985,25 @@ var init_use_navigation = __esm({
|
|
|
1548
1985
|
});
|
|
1549
1986
|
|
|
1550
1987
|
// src/board/hooks/use-toast.ts
|
|
1551
|
-
import { useCallback as
|
|
1988
|
+
import { useCallback as useCallback6, useRef as useRef5, useState as useState3 } from "react";
|
|
1552
1989
|
function useToast() {
|
|
1553
1990
|
const [toasts, setToasts] = useState3([]);
|
|
1554
1991
|
const timersRef = useRef5(/* @__PURE__ */ new Map());
|
|
1555
|
-
const clearTimer =
|
|
1992
|
+
const clearTimer = useCallback6((id) => {
|
|
1556
1993
|
const timer = timersRef.current.get(id);
|
|
1557
1994
|
if (timer) {
|
|
1558
1995
|
clearTimeout(timer);
|
|
1559
1996
|
timersRef.current.delete(id);
|
|
1560
1997
|
}
|
|
1561
1998
|
}, []);
|
|
1562
|
-
const removeToast =
|
|
1999
|
+
const removeToast = useCallback6(
|
|
1563
2000
|
(id) => {
|
|
1564
2001
|
clearTimer(id);
|
|
1565
2002
|
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
1566
2003
|
},
|
|
1567
2004
|
[clearTimer]
|
|
1568
2005
|
);
|
|
1569
|
-
const addToast =
|
|
2006
|
+
const addToast = useCallback6(
|
|
1570
2007
|
(t) => {
|
|
1571
2008
|
const id = `toast-${++nextId}`;
|
|
1572
2009
|
const newToast = { ...t, id, createdAt: Date.now() };
|
|
@@ -1595,25 +2032,25 @@ function useToast() {
|
|
|
1595
2032
|
[removeToast, clearTimer]
|
|
1596
2033
|
);
|
|
1597
2034
|
const toast = {
|
|
1598
|
-
info:
|
|
2035
|
+
info: useCallback6(
|
|
1599
2036
|
(message) => {
|
|
1600
2037
|
addToast({ type: "info", message });
|
|
1601
2038
|
},
|
|
1602
2039
|
[addToast]
|
|
1603
2040
|
),
|
|
1604
|
-
success:
|
|
2041
|
+
success: useCallback6(
|
|
1605
2042
|
(message) => {
|
|
1606
2043
|
addToast({ type: "success", message });
|
|
1607
2044
|
},
|
|
1608
2045
|
[addToast]
|
|
1609
2046
|
),
|
|
1610
|
-
error:
|
|
2047
|
+
error: useCallback6(
|
|
1611
2048
|
(message, retry) => {
|
|
1612
2049
|
addToast(retry ? { type: "error", message, retry } : { type: "error", message });
|
|
1613
2050
|
},
|
|
1614
2051
|
[addToast]
|
|
1615
2052
|
),
|
|
1616
|
-
loading:
|
|
2053
|
+
loading: useCallback6(
|
|
1617
2054
|
(message) => {
|
|
1618
2055
|
const id = addToast({ type: "loading", message });
|
|
1619
2056
|
return {
|
|
@@ -1630,20 +2067,20 @@ function useToast() {
|
|
|
1630
2067
|
[addToast, removeToast]
|
|
1631
2068
|
)
|
|
1632
2069
|
};
|
|
1633
|
-
const dismiss =
|
|
2070
|
+
const dismiss = useCallback6(
|
|
1634
2071
|
(id) => {
|
|
1635
2072
|
removeToast(id);
|
|
1636
2073
|
},
|
|
1637
2074
|
[removeToast]
|
|
1638
2075
|
);
|
|
1639
|
-
const dismissAll =
|
|
2076
|
+
const dismissAll = useCallback6(() => {
|
|
1640
2077
|
for (const timer of timersRef.current.values()) {
|
|
1641
2078
|
clearTimeout(timer);
|
|
1642
2079
|
}
|
|
1643
2080
|
timersRef.current.clear();
|
|
1644
2081
|
setToasts([]);
|
|
1645
2082
|
}, []);
|
|
1646
|
-
const handleErrorAction =
|
|
2083
|
+
const handleErrorAction = useCallback6(
|
|
1647
2084
|
(action) => {
|
|
1648
2085
|
const errorToast = toasts.find((t) => t.type === "error");
|
|
1649
2086
|
if (!errorToast) return false;
|
|
@@ -1673,7 +2110,7 @@ var init_use_toast = __esm({
|
|
|
1673
2110
|
});
|
|
1674
2111
|
|
|
1675
2112
|
// src/board/hooks/use-ui-state.ts
|
|
1676
|
-
import { useCallback as
|
|
2113
|
+
import { useCallback as useCallback7, useReducer as useReducer2 } from "react";
|
|
1677
2114
|
function uiReducer(state, action) {
|
|
1678
2115
|
switch (action.type) {
|
|
1679
2116
|
case "ENTER_SEARCH":
|
|
@@ -1692,6 +2129,12 @@ function uiReducer(state, action) {
|
|
|
1692
2129
|
case "ENTER_CREATE":
|
|
1693
2130
|
if (state.mode !== "normal") return state;
|
|
1694
2131
|
return { ...state, mode: "overlay:create", previousMode: "normal" };
|
|
2132
|
+
case "ENTER_CREATE_NL":
|
|
2133
|
+
if (state.mode !== "normal") return state;
|
|
2134
|
+
return { ...state, mode: "overlay:createNl", previousMode: "normal" };
|
|
2135
|
+
case "ENTER_LABEL":
|
|
2136
|
+
if (state.mode !== "normal") return state;
|
|
2137
|
+
return { ...state, mode: "overlay:label", previousMode: "normal" };
|
|
1695
2138
|
case "ENTER_MULTI_SELECT":
|
|
1696
2139
|
if (state.mode !== "normal" && state.mode !== "multiSelect") return state;
|
|
1697
2140
|
return { ...state, mode: "multiSelect", previousMode: "normal" };
|
|
@@ -1735,18 +2178,20 @@ function useUIState() {
|
|
|
1735
2178
|
const [state, dispatch] = useReducer2(uiReducer, INITIAL_STATE2);
|
|
1736
2179
|
return {
|
|
1737
2180
|
state,
|
|
1738
|
-
enterSearch:
|
|
1739
|
-
enterComment:
|
|
1740
|
-
enterStatus:
|
|
1741
|
-
enterCreate:
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
2181
|
+
enterSearch: useCallback7(() => dispatch({ type: "ENTER_SEARCH" }), []),
|
|
2182
|
+
enterComment: useCallback7(() => dispatch({ type: "ENTER_COMMENT" }), []),
|
|
2183
|
+
enterStatus: useCallback7(() => dispatch({ type: "ENTER_STATUS" }), []),
|
|
2184
|
+
enterCreate: useCallback7(() => dispatch({ type: "ENTER_CREATE" }), []),
|
|
2185
|
+
enterCreateNl: useCallback7(() => dispatch({ type: "ENTER_CREATE_NL" }), []),
|
|
2186
|
+
enterLabel: useCallback7(() => dispatch({ type: "ENTER_LABEL" }), []),
|
|
2187
|
+
enterMultiSelect: useCallback7(() => dispatch({ type: "ENTER_MULTI_SELECT" }), []),
|
|
2188
|
+
enterBulkAction: useCallback7(() => dispatch({ type: "ENTER_BULK_ACTION" }), []),
|
|
2189
|
+
enterConfirmPick: useCallback7(() => dispatch({ type: "ENTER_CONFIRM_PICK" }), []),
|
|
2190
|
+
enterFocus: useCallback7(() => dispatch({ type: "ENTER_FOCUS" }), []),
|
|
2191
|
+
toggleHelp: useCallback7(() => dispatch({ type: "TOGGLE_HELP" }), []),
|
|
2192
|
+
exitOverlay: useCallback7(() => dispatch({ type: "EXIT_OVERLAY" }), []),
|
|
2193
|
+
exitToNormal: useCallback7(() => dispatch({ type: "EXIT_TO_NORMAL" }), []),
|
|
2194
|
+
clearMultiSelect: useCallback7(() => dispatch({ type: "CLEAR_MULTI_SELECT" }), []),
|
|
1750
2195
|
canNavigate: canNavigate(state),
|
|
1751
2196
|
canAct: canAct(state),
|
|
1752
2197
|
isOverlay: isOverlay(state)
|
|
@@ -1764,34 +2209,202 @@ var init_use_ui_state = __esm({
|
|
|
1764
2209
|
}
|
|
1765
2210
|
});
|
|
1766
2211
|
|
|
1767
|
-
// src/board/components/
|
|
1768
|
-
import { Box, Text
|
|
1769
|
-
import {
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
return [
|
|
1774
|
-
{ label: "Assign all to me", action: { type: "assign" } },
|
|
1775
|
-
{ label: "Unassign all from me", action: { type: "unassign" } },
|
|
1776
|
-
{ label: "Move status (all)", action: { type: "statusChange" } }
|
|
1777
|
-
];
|
|
1778
|
-
}
|
|
1779
|
-
if (selectionType === "ticktick") {
|
|
1780
|
-
return [
|
|
1781
|
-
{ label: "Complete all", action: { type: "complete" } },
|
|
1782
|
-
{ label: "Delete all", action: { type: "delete" } }
|
|
1783
|
-
];
|
|
1784
|
-
}
|
|
1785
|
-
return [];
|
|
2212
|
+
// src/board/components/detail-panel.tsx
|
|
2213
|
+
import { Box, Text } from "ink";
|
|
2214
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2215
|
+
function truncateLines(text, maxLines) {
|
|
2216
|
+
const lines = text.split("\n").slice(0, maxLines);
|
|
2217
|
+
return lines.join("\n");
|
|
1786
2218
|
}
|
|
1787
|
-
function
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
2219
|
+
function stripMarkdown(text) {
|
|
2220
|
+
return text.replace(/^#{1,6}\s+/gm, "").replace(/\*\*(.+?)\*\*/g, "$1").replace(/\*(.+?)\*/g, "$1").replace(/__(.+?)__/g, "$1").replace(/_(.+?)_/g, "$1").replace(/~~(.+?)~~/g, "$1").replace(/`{1,3}[^`]*`{1,3}/g, (m) => m.replace(/`/g, "")).replace(/^\s*[-*+]\s+/gm, " - ").replace(/^\s*\d+\.\s+/gm, " ").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/!\[([^\]]*)\]\([^)]+\)/g, "[$1]").replace(/^>\s+/gm, " ").replace(/---+/g, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
2221
|
+
}
|
|
2222
|
+
function formatBody(body, maxLines) {
|
|
2223
|
+
const plain = stripMarkdown(body);
|
|
2224
|
+
const lines = plain.split("\n");
|
|
2225
|
+
const truncated = lines.slice(0, maxLines).join("\n");
|
|
2226
|
+
return { text: truncated, remaining: Math.max(0, lines.length - maxLines) };
|
|
2227
|
+
}
|
|
2228
|
+
function countSlackLinks(body) {
|
|
2229
|
+
if (!body) return 0;
|
|
2230
|
+
return (body.match(SLACK_URL_RE) ?? []).length;
|
|
2231
|
+
}
|
|
2232
|
+
function BodySection({
|
|
2233
|
+
body,
|
|
2234
|
+
issueNumber
|
|
2235
|
+
}) {
|
|
2236
|
+
const { text, remaining } = formatBody(body, 15);
|
|
2237
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2238
|
+
/* @__PURE__ */ jsx(Text, { children: "" }),
|
|
2239
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "--- Description ---" }),
|
|
2240
|
+
/* @__PURE__ */ jsx(Text, { wrap: "wrap", children: text }),
|
|
2241
|
+
remaining > 0 ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
2242
|
+
"... (",
|
|
2243
|
+
remaining,
|
|
2244
|
+
" more lines \u2014 gh issue view ",
|
|
2245
|
+
issueNumber,
|
|
2246
|
+
" for full)"
|
|
2247
|
+
] }) : null
|
|
2248
|
+
] });
|
|
2249
|
+
}
|
|
2250
|
+
function DetailPanel({ issue, task: task2, width }) {
|
|
2251
|
+
if (!(issue || task2)) {
|
|
2252
|
+
return /* @__PURE__ */ jsx(
|
|
2253
|
+
Box,
|
|
2254
|
+
{
|
|
2255
|
+
width,
|
|
2256
|
+
borderStyle: "single",
|
|
2257
|
+
borderColor: "gray",
|
|
2258
|
+
flexDirection: "column",
|
|
2259
|
+
paddingX: 1,
|
|
2260
|
+
children: /* @__PURE__ */ jsx(Text, { color: "gray", children: "No item selected" })
|
|
2261
|
+
}
|
|
2262
|
+
);
|
|
2263
|
+
}
|
|
2264
|
+
if (issue) {
|
|
2265
|
+
return /* @__PURE__ */ jsxs(
|
|
2266
|
+
Box,
|
|
2267
|
+
{
|
|
2268
|
+
width,
|
|
2269
|
+
borderStyle: "single",
|
|
2270
|
+
borderColor: "cyan",
|
|
2271
|
+
flexDirection: "column",
|
|
2272
|
+
paddingX: 1,
|
|
2273
|
+
children: [
|
|
2274
|
+
/* @__PURE__ */ jsxs(Text, { color: "cyan", bold: true, children: [
|
|
2275
|
+
"#",
|
|
2276
|
+
issue.number,
|
|
2277
|
+
" ",
|
|
2278
|
+
issue.title
|
|
2279
|
+
] }),
|
|
2280
|
+
/* @__PURE__ */ jsx(Text, { children: "" }),
|
|
2281
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
2282
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "State: " }),
|
|
2283
|
+
/* @__PURE__ */ jsx(Text, { color: issue.state === "open" ? "green" : "red", children: issue.state })
|
|
2284
|
+
] }),
|
|
2285
|
+
(issue.assignees ?? []).length > 0 ? /* @__PURE__ */ jsxs(Box, { children: [
|
|
2286
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "Assignees: " }),
|
|
2287
|
+
/* @__PURE__ */ jsx(Text, { children: (issue.assignees ?? []).map((a) => a.login).join(", ") })
|
|
2288
|
+
] }) : null,
|
|
2289
|
+
issue.labels.length > 0 ? /* @__PURE__ */ jsxs(Box, { children: [
|
|
2290
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "Labels: " }),
|
|
2291
|
+
/* @__PURE__ */ jsx(Text, { children: issue.labels.map((l) => l.name).join(", ") })
|
|
2292
|
+
] }) : null,
|
|
2293
|
+
issue.projectStatus ? /* @__PURE__ */ jsxs(Box, { children: [
|
|
2294
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "Status: " }),
|
|
2295
|
+
/* @__PURE__ */ jsx(Text, { color: "magenta", children: issue.projectStatus })
|
|
2296
|
+
] }) : null,
|
|
2297
|
+
issue.targetDate ? /* @__PURE__ */ jsxs(Box, { children: [
|
|
2298
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "Target: " }),
|
|
2299
|
+
/* @__PURE__ */ jsx(Text, { children: issue.targetDate })
|
|
2300
|
+
] }) : null,
|
|
2301
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
2302
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "Updated: " }),
|
|
2303
|
+
/* @__PURE__ */ jsx(Text, { children: new Date(issue.updatedAt).toLocaleString() })
|
|
2304
|
+
] }),
|
|
2305
|
+
issue.slackThreadUrl ? /* @__PURE__ */ jsxs(Box, { children: [
|
|
2306
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "Slack: " }),
|
|
2307
|
+
/* @__PURE__ */ jsx(Text, { color: "blue", children: countSlackLinks(issue.body) > 1 ? `${countSlackLinks(issue.body)} links (s opens first)` : "thread (s to open)" })
|
|
2308
|
+
] }) : null,
|
|
2309
|
+
issue.body ? /* @__PURE__ */ jsx(BodySection, { body: issue.body, issueNumber: issue.number }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2310
|
+
/* @__PURE__ */ jsx(Text, { children: "" }),
|
|
2311
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "(no description)" })
|
|
2312
|
+
] }),
|
|
2313
|
+
/* @__PURE__ */ jsx(Text, { children: "" }),
|
|
2314
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", dimColor: true, children: issue.url })
|
|
2315
|
+
]
|
|
2316
|
+
}
|
|
2317
|
+
);
|
|
2318
|
+
}
|
|
2319
|
+
const t = task2;
|
|
2320
|
+
return /* @__PURE__ */ jsxs(
|
|
2321
|
+
Box,
|
|
2322
|
+
{
|
|
2323
|
+
width,
|
|
2324
|
+
borderStyle: "single",
|
|
2325
|
+
borderColor: "yellow",
|
|
2326
|
+
flexDirection: "column",
|
|
2327
|
+
paddingX: 1,
|
|
2328
|
+
children: [
|
|
2329
|
+
/* @__PURE__ */ jsx(Text, { color: "yellow", bold: true, children: t.title }),
|
|
2330
|
+
/* @__PURE__ */ jsx(Text, { children: "" }),
|
|
2331
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
2332
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "Priority: " }),
|
|
2333
|
+
/* @__PURE__ */ jsx(Text, { children: PRIORITY_LABELS2[t.priority] ?? "None" })
|
|
2334
|
+
] }),
|
|
2335
|
+
t.dueDate ? /* @__PURE__ */ jsxs(Box, { children: [
|
|
2336
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "Due: " }),
|
|
2337
|
+
/* @__PURE__ */ jsx(Text, { children: new Date(t.dueDate).toLocaleDateString() })
|
|
2338
|
+
] }) : null,
|
|
2339
|
+
(t.tags ?? []).length > 0 ? /* @__PURE__ */ jsxs(Box, { children: [
|
|
2340
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "Tags: " }),
|
|
2341
|
+
/* @__PURE__ */ jsx(Text, { children: t.tags.join(", ") })
|
|
2342
|
+
] }) : null,
|
|
2343
|
+
t.content ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2344
|
+
/* @__PURE__ */ jsx(Text, { children: "" }),
|
|
2345
|
+
/* @__PURE__ */ jsx(Text, { children: truncateLines(t.content, 8) })
|
|
2346
|
+
] }) : null,
|
|
2347
|
+
(t.items ?? []).length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2348
|
+
/* @__PURE__ */ jsx(Text, { children: "" }),
|
|
2349
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "Checklist:" }),
|
|
2350
|
+
t.items.slice(0, 5).map((item) => /* @__PURE__ */ jsxs(Text, { children: [
|
|
2351
|
+
item.status === 2 ? "\u2611" : "\u2610",
|
|
2352
|
+
" ",
|
|
2353
|
+
item.title
|
|
2354
|
+
] }, item.id)),
|
|
2355
|
+
t.items.length > 5 ? /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
|
|
2356
|
+
"...and ",
|
|
2357
|
+
t.items.length - 5,
|
|
2358
|
+
" more"
|
|
2359
|
+
] }) : null
|
|
2360
|
+
] }) : null
|
|
2361
|
+
]
|
|
2362
|
+
}
|
|
2363
|
+
);
|
|
2364
|
+
}
|
|
2365
|
+
var SLACK_URL_RE, PRIORITY_LABELS2;
|
|
2366
|
+
var init_detail_panel = __esm({
|
|
2367
|
+
"src/board/components/detail-panel.tsx"() {
|
|
2368
|
+
"use strict";
|
|
2369
|
+
init_types();
|
|
2370
|
+
SLACK_URL_RE = /https:\/\/[^/]+\.slack\.com\/archives\/[A-Z0-9]+\/p[0-9]+/gi;
|
|
2371
|
+
PRIORITY_LABELS2 = {
|
|
2372
|
+
[5 /* High */]: "High",
|
|
2373
|
+
[3 /* Medium */]: "Medium",
|
|
2374
|
+
[1 /* Low */]: "Low",
|
|
2375
|
+
[0 /* None */]: "None"
|
|
2376
|
+
};
|
|
2377
|
+
}
|
|
2378
|
+
});
|
|
2379
|
+
|
|
2380
|
+
// src/board/components/bulk-action-menu.tsx
|
|
2381
|
+
import { Box as Box2, Text as Text2, useInput as useInput2 } from "ink";
|
|
2382
|
+
import { useState as useState4 } from "react";
|
|
2383
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
2384
|
+
function getMenuItems(selectionType) {
|
|
2385
|
+
if (selectionType === "github") {
|
|
2386
|
+
return [
|
|
2387
|
+
{ label: "Assign all to me", action: { type: "assign" } },
|
|
2388
|
+
{ label: "Unassign all from me", action: { type: "unassign" } },
|
|
2389
|
+
{ label: "Move status (all)", action: { type: "statusChange" } }
|
|
2390
|
+
];
|
|
2391
|
+
}
|
|
2392
|
+
if (selectionType === "ticktick") {
|
|
2393
|
+
return [
|
|
2394
|
+
{ label: "Complete all", action: { type: "complete" } },
|
|
2395
|
+
{ label: "Delete all", action: { type: "delete" } }
|
|
2396
|
+
];
|
|
2397
|
+
}
|
|
2398
|
+
return [];
|
|
2399
|
+
}
|
|
2400
|
+
function BulkActionMenu({ count, selectionType, onSelect, onCancel }) {
|
|
2401
|
+
const items = getMenuItems(selectionType);
|
|
2402
|
+
const [selectedIdx, setSelectedIdx] = useState4(0);
|
|
2403
|
+
useInput2((input2, key) => {
|
|
2404
|
+
if (key.escape) return onCancel();
|
|
2405
|
+
if (key.return) {
|
|
2406
|
+
const item = items[selectedIdx];
|
|
2407
|
+
if (item) onSelect(item.action);
|
|
1795
2408
|
return;
|
|
1796
2409
|
}
|
|
1797
2410
|
if (input2 === "j" || key.downArrow) {
|
|
@@ -1802,13 +2415,13 @@ function BulkActionMenu({ count, selectionType, onSelect, onCancel }) {
|
|
|
1802
2415
|
}
|
|
1803
2416
|
});
|
|
1804
2417
|
if (items.length === 0) {
|
|
1805
|
-
return /* @__PURE__ */
|
|
1806
|
-
/* @__PURE__ */
|
|
1807
|
-
/* @__PURE__ */
|
|
2418
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
2419
|
+
/* @__PURE__ */ jsx2(Text2, { color: "yellow", children: "No bulk actions for mixed selection types." }),
|
|
2420
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Esc to cancel" })
|
|
1808
2421
|
] });
|
|
1809
2422
|
}
|
|
1810
|
-
return /* @__PURE__ */
|
|
1811
|
-
/* @__PURE__ */
|
|
2423
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
2424
|
+
/* @__PURE__ */ jsxs2(Text2, { color: "cyan", bold: true, children: [
|
|
1812
2425
|
"Bulk action (",
|
|
1813
2426
|
count,
|
|
1814
2427
|
" selected):"
|
|
@@ -1816,12 +2429,12 @@ function BulkActionMenu({ count, selectionType, onSelect, onCancel }) {
|
|
|
1816
2429
|
items.map((item, i) => {
|
|
1817
2430
|
const isSelected = i === selectedIdx;
|
|
1818
2431
|
const prefix = isSelected ? "> " : " ";
|
|
1819
|
-
return /* @__PURE__ */
|
|
2432
|
+
return /* @__PURE__ */ jsxs2(Text2, { ...isSelected ? { color: "cyan" } : {}, children: [
|
|
1820
2433
|
prefix,
|
|
1821
2434
|
item.label
|
|
1822
2435
|
] }, item.action.type);
|
|
1823
2436
|
}),
|
|
1824
|
-
/* @__PURE__ */
|
|
2437
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "j/k:navigate Enter:select Esc:cancel" })
|
|
1825
2438
|
] });
|
|
1826
2439
|
}
|
|
1827
2440
|
var init_bulk_action_menu = __esm({
|
|
@@ -1830,27 +2443,113 @@ var init_bulk_action_menu = __esm({
|
|
|
1830
2443
|
}
|
|
1831
2444
|
});
|
|
1832
2445
|
|
|
2446
|
+
// src/board/ink-instance.ts
|
|
2447
|
+
function setInkInstance(instance) {
|
|
2448
|
+
_instance = instance;
|
|
2449
|
+
}
|
|
2450
|
+
function getInkInstance() {
|
|
2451
|
+
return _instance;
|
|
2452
|
+
}
|
|
2453
|
+
var _instance;
|
|
2454
|
+
var init_ink_instance = __esm({
|
|
2455
|
+
"src/board/ink-instance.ts"() {
|
|
2456
|
+
"use strict";
|
|
2457
|
+
_instance = null;
|
|
2458
|
+
}
|
|
2459
|
+
});
|
|
2460
|
+
|
|
1833
2461
|
// src/board/components/comment-input.tsx
|
|
2462
|
+
import { spawnSync } from "child_process";
|
|
2463
|
+
import { mkdtempSync, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync3 } from "fs";
|
|
2464
|
+
import { tmpdir } from "os";
|
|
2465
|
+
import { join as join3 } from "path";
|
|
1834
2466
|
import { TextInput } from "@inkjs/ui";
|
|
1835
|
-
import { Box as
|
|
1836
|
-
import { useState as useState5 } from "react";
|
|
1837
|
-
import { jsx as
|
|
1838
|
-
function CommentInput({
|
|
2467
|
+
import { Box as Box3, Text as Text3, useInput as useInput3, useStdin } from "ink";
|
|
2468
|
+
import { useEffect as useEffect2, useRef as useRef6, useState as useState5 } from "react";
|
|
2469
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
2470
|
+
function CommentInput({
|
|
2471
|
+
issueNumber,
|
|
2472
|
+
onSubmit,
|
|
2473
|
+
onCancel,
|
|
2474
|
+
onPauseRefresh,
|
|
2475
|
+
onResumeRefresh
|
|
2476
|
+
}) {
|
|
1839
2477
|
const [value, setValue] = useState5("");
|
|
1840
|
-
|
|
1841
|
-
|
|
2478
|
+
const [editing, setEditing] = useState5(false);
|
|
2479
|
+
const { setRawMode } = useStdin();
|
|
2480
|
+
const onSubmitRef = useRef6(onSubmit);
|
|
2481
|
+
const onCancelRef = useRef6(onCancel);
|
|
2482
|
+
const onPauseRef = useRef6(onPauseRefresh);
|
|
2483
|
+
const onResumeRef = useRef6(onResumeRefresh);
|
|
2484
|
+
onSubmitRef.current = onSubmit;
|
|
2485
|
+
onCancelRef.current = onCancel;
|
|
2486
|
+
onPauseRef.current = onPauseRefresh;
|
|
2487
|
+
onResumeRef.current = onResumeRefresh;
|
|
2488
|
+
useInput3((_input, key) => {
|
|
2489
|
+
if (editing) return;
|
|
2490
|
+
if (key.escape) {
|
|
2491
|
+
onCancel();
|
|
2492
|
+
return;
|
|
2493
|
+
}
|
|
2494
|
+
if (_input === "") {
|
|
2495
|
+
setEditing(true);
|
|
2496
|
+
}
|
|
1842
2497
|
});
|
|
1843
|
-
|
|
1844
|
-
|
|
2498
|
+
useEffect2(() => {
|
|
2499
|
+
if (!editing) return;
|
|
2500
|
+
const editorEnv = process.env["VISUAL"] ?? process.env["EDITOR"] ?? "vi";
|
|
2501
|
+
const [cmd, ...extraArgs] = editorEnv.split(" ").filter(Boolean);
|
|
2502
|
+
if (!cmd) {
|
|
2503
|
+
setEditing(false);
|
|
2504
|
+
return;
|
|
2505
|
+
}
|
|
2506
|
+
let tmpDir = null;
|
|
2507
|
+
let tmpFile = null;
|
|
2508
|
+
try {
|
|
2509
|
+
onPauseRef.current?.();
|
|
2510
|
+
tmpDir = mkdtempSync(join3(tmpdir(), "hog-comment-"));
|
|
2511
|
+
tmpFile = join3(tmpDir, "comment.md");
|
|
2512
|
+
writeFileSync3(tmpFile, value);
|
|
2513
|
+
const inkInstance = getInkInstance();
|
|
2514
|
+
inkInstance?.clear();
|
|
2515
|
+
setRawMode(false);
|
|
2516
|
+
spawnSync(cmd, [...extraArgs, tmpFile], { stdio: "inherit" });
|
|
2517
|
+
const content = readFileSync3(tmpFile, "utf-8").trim();
|
|
2518
|
+
setRawMode(true);
|
|
2519
|
+
if (content) {
|
|
2520
|
+
onSubmitRef.current(content);
|
|
2521
|
+
} else {
|
|
2522
|
+
onCancelRef.current();
|
|
2523
|
+
}
|
|
2524
|
+
} finally {
|
|
2525
|
+
onResumeRef.current?.();
|
|
2526
|
+
if (tmpFile) {
|
|
2527
|
+
try {
|
|
2528
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
2529
|
+
} catch {
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
setEditing(false);
|
|
2533
|
+
}
|
|
2534
|
+
}, [editing, value, setRawMode]);
|
|
2535
|
+
if (editing) {
|
|
2536
|
+
return /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsxs3(Text3, { color: "cyan", children: [
|
|
2537
|
+
"Opening editor for #",
|
|
2538
|
+
issueNumber,
|
|
2539
|
+
"\u2026"
|
|
2540
|
+
] }) });
|
|
2541
|
+
}
|
|
2542
|
+
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
2543
|
+
/* @__PURE__ */ jsxs3(Text3, { color: "cyan", children: [
|
|
1845
2544
|
"comment #",
|
|
1846
2545
|
issueNumber,
|
|
1847
2546
|
": "
|
|
1848
2547
|
] }),
|
|
1849
|
-
/* @__PURE__ */
|
|
2548
|
+
/* @__PURE__ */ jsx3(
|
|
1850
2549
|
TextInput,
|
|
1851
2550
|
{
|
|
1852
2551
|
defaultValue: value,
|
|
1853
|
-
placeholder: "type comment, Enter to post...",
|
|
2552
|
+
placeholder: "type comment (ctrl+e for editor), Enter to post...",
|
|
1854
2553
|
onChange: setValue,
|
|
1855
2554
|
onSubmit: (text) => {
|
|
1856
2555
|
if (text.trim()) onSubmit(text.trim());
|
|
@@ -1863,20 +2562,21 @@ function CommentInput({ issueNumber, onSubmit, onCancel }) {
|
|
|
1863
2562
|
var init_comment_input = __esm({
|
|
1864
2563
|
"src/board/components/comment-input.tsx"() {
|
|
1865
2564
|
"use strict";
|
|
2565
|
+
init_ink_instance();
|
|
1866
2566
|
}
|
|
1867
2567
|
});
|
|
1868
2568
|
|
|
1869
2569
|
// src/board/components/confirm-prompt.tsx
|
|
1870
|
-
import { Box as
|
|
1871
|
-
import { jsx as
|
|
2570
|
+
import { Box as Box4, Text as Text4, useInput as useInput4 } from "ink";
|
|
2571
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1872
2572
|
function ConfirmPrompt({ message, onConfirm, onCancel }) {
|
|
1873
|
-
|
|
2573
|
+
useInput4((input2, key) => {
|
|
1874
2574
|
if (input2 === "y" || input2 === "Y") return onConfirm();
|
|
1875
2575
|
if (input2 === "n" || input2 === "N" || key.escape) return onCancel();
|
|
1876
2576
|
});
|
|
1877
|
-
return /* @__PURE__ */
|
|
1878
|
-
/* @__PURE__ */
|
|
1879
|
-
/* @__PURE__ */
|
|
2577
|
+
return /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
2578
|
+
/* @__PURE__ */ jsx4(Text4, { color: "cyan", children: message }),
|
|
2579
|
+
/* @__PURE__ */ jsx4(Text4, { color: "gray", children: " (y/n)" })
|
|
1880
2580
|
] });
|
|
1881
2581
|
}
|
|
1882
2582
|
var init_confirm_prompt = __esm({
|
|
@@ -1885,20 +2585,146 @@ var init_confirm_prompt = __esm({
|
|
|
1885
2585
|
}
|
|
1886
2586
|
});
|
|
1887
2587
|
|
|
2588
|
+
// src/board/components/label-picker.tsx
|
|
2589
|
+
import { Spinner } from "@inkjs/ui";
|
|
2590
|
+
import { Box as Box5, Text as Text5, useInput as useInput5 } from "ink";
|
|
2591
|
+
import { useEffect as useEffect3, useRef as useRef7, useState as useState6 } from "react";
|
|
2592
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
2593
|
+
function LabelPicker({
|
|
2594
|
+
repo,
|
|
2595
|
+
currentLabels,
|
|
2596
|
+
labelCache,
|
|
2597
|
+
onConfirm,
|
|
2598
|
+
onCancel,
|
|
2599
|
+
onError
|
|
2600
|
+
}) {
|
|
2601
|
+
const [labels, setLabels] = useState6(labelCache[repo] ?? null);
|
|
2602
|
+
const [loading, setLoading] = useState6(labels === null);
|
|
2603
|
+
const [fetchAttempted, setFetchAttempted] = useState6(false);
|
|
2604
|
+
const [selected, setSelected] = useState6(new Set(currentLabels));
|
|
2605
|
+
const [cursor, setCursor] = useState6(0);
|
|
2606
|
+
const submittedRef = useRef7(false);
|
|
2607
|
+
useEffect3(() => {
|
|
2608
|
+
if (labels !== null || fetchAttempted) return;
|
|
2609
|
+
setFetchAttempted(true);
|
|
2610
|
+
setLoading(true);
|
|
2611
|
+
let canceled = false;
|
|
2612
|
+
fetchRepoLabelsAsync(repo).then((fetched) => {
|
|
2613
|
+
if (canceled) return;
|
|
2614
|
+
labelCache[repo] = fetched;
|
|
2615
|
+
setLabels(fetched);
|
|
2616
|
+
setLoading(false);
|
|
2617
|
+
}).catch(() => {
|
|
2618
|
+
if (canceled) return;
|
|
2619
|
+
setLoading(false);
|
|
2620
|
+
onError(`Could not fetch labels for ${repo}`);
|
|
2621
|
+
});
|
|
2622
|
+
return () => {
|
|
2623
|
+
canceled = true;
|
|
2624
|
+
};
|
|
2625
|
+
}, [repo, fetchAttempted, labelCache, onError]);
|
|
2626
|
+
useInput5((input2, key) => {
|
|
2627
|
+
if (loading) return;
|
|
2628
|
+
if (key.escape) {
|
|
2629
|
+
onCancel();
|
|
2630
|
+
return;
|
|
2631
|
+
}
|
|
2632
|
+
if (key.return) {
|
|
2633
|
+
if (submittedRef.current) return;
|
|
2634
|
+
submittedRef.current = true;
|
|
2635
|
+
const allLabels2 = labels ?? [];
|
|
2636
|
+
const add = [...selected].filter((l) => !currentLabels.includes(l));
|
|
2637
|
+
const remove = currentLabels.filter((l) => {
|
|
2638
|
+
const exists = allLabels2.some((rl) => rl.name === l);
|
|
2639
|
+
return exists && !selected.has(l);
|
|
2640
|
+
});
|
|
2641
|
+
onConfirm(add, remove);
|
|
2642
|
+
return;
|
|
2643
|
+
}
|
|
2644
|
+
if (input2 === " ") {
|
|
2645
|
+
const allLabels2 = labels ?? [];
|
|
2646
|
+
const item = allLabels2[cursor];
|
|
2647
|
+
if (!item) return;
|
|
2648
|
+
setSelected((prev) => {
|
|
2649
|
+
const next = new Set(prev);
|
|
2650
|
+
if (next.has(item.name)) {
|
|
2651
|
+
next.delete(item.name);
|
|
2652
|
+
} else {
|
|
2653
|
+
next.add(item.name);
|
|
2654
|
+
}
|
|
2655
|
+
return next;
|
|
2656
|
+
});
|
|
2657
|
+
return;
|
|
2658
|
+
}
|
|
2659
|
+
if (input2 === "j" || key.downArrow) {
|
|
2660
|
+
setCursor((i) => Math.min(i + 1, (labels?.length ?? 1) - 1));
|
|
2661
|
+
}
|
|
2662
|
+
if (input2 === "k" || key.upArrow) {
|
|
2663
|
+
setCursor((i) => Math.max(i - 1, 0));
|
|
2664
|
+
}
|
|
2665
|
+
});
|
|
2666
|
+
if (loading) {
|
|
2667
|
+
return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Spinner, { label: "Fetching labels..." }) });
|
|
2668
|
+
}
|
|
2669
|
+
const allLabels = labels ?? [];
|
|
2670
|
+
if (allLabels.length === 0) {
|
|
2671
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
2672
|
+
/* @__PURE__ */ jsx5(Text5, { color: "cyan", bold: true, children: "Labels:" }),
|
|
2673
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "No labels in this repo" }),
|
|
2674
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Esc:cancel" })
|
|
2675
|
+
] });
|
|
2676
|
+
}
|
|
2677
|
+
const repoLabelNames = new Set(allLabels.map((l) => l.name));
|
|
2678
|
+
const orphanedLabels = currentLabels.filter((l) => !repoLabelNames.has(l));
|
|
2679
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
2680
|
+
/* @__PURE__ */ jsx5(Text5, { color: "cyan", bold: true, children: "Labels (Space:toggle Enter:confirm Esc:cancel):" }),
|
|
2681
|
+
orphanedLabels.map((name) => /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
2682
|
+
selected.has(name) ? "[x]" : "[ ]",
|
|
2683
|
+
" ",
|
|
2684
|
+
name,
|
|
2685
|
+
" (orphaned)"
|
|
2686
|
+
] }, `orphan:${name}`)),
|
|
2687
|
+
allLabels.map((label, i) => {
|
|
2688
|
+
const isSel = i === cursor;
|
|
2689
|
+
const isChecked = selected.has(label.name);
|
|
2690
|
+
return /* @__PURE__ */ jsxs5(Text5, { ...isSel ? { color: "cyan" } : {}, children: [
|
|
2691
|
+
isSel ? ">" : " ",
|
|
2692
|
+
" ",
|
|
2693
|
+
isChecked ? "[x]" : "[ ]",
|
|
2694
|
+
" ",
|
|
2695
|
+
label.name
|
|
2696
|
+
] }, label.name);
|
|
2697
|
+
})
|
|
2698
|
+
] });
|
|
2699
|
+
}
|
|
2700
|
+
var init_label_picker = __esm({
|
|
2701
|
+
"src/board/components/label-picker.tsx"() {
|
|
2702
|
+
"use strict";
|
|
2703
|
+
init_github();
|
|
2704
|
+
}
|
|
2705
|
+
});
|
|
2706
|
+
|
|
1888
2707
|
// src/board/components/create-issue-form.tsx
|
|
1889
2708
|
import { TextInput as TextInput2 } from "@inkjs/ui";
|
|
1890
|
-
import { Box as
|
|
1891
|
-
import { useState as
|
|
1892
|
-
import { jsx as
|
|
1893
|
-
function CreateIssueForm({
|
|
2709
|
+
import { Box as Box6, Text as Text6, useInput as useInput6 } from "ink";
|
|
2710
|
+
import { useState as useState7 } from "react";
|
|
2711
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2712
|
+
function CreateIssueForm({
|
|
2713
|
+
repos,
|
|
2714
|
+
defaultRepo,
|
|
2715
|
+
onSubmit,
|
|
2716
|
+
onCancel,
|
|
2717
|
+
labelCache
|
|
2718
|
+
}) {
|
|
1894
2719
|
const defaultRepoIdx = defaultRepo ? Math.max(
|
|
1895
2720
|
0,
|
|
1896
2721
|
repos.findIndex((r) => r.name === defaultRepo)
|
|
1897
2722
|
) : 0;
|
|
1898
|
-
const [repoIdx, setRepoIdx] =
|
|
1899
|
-
const [title, setTitle] =
|
|
1900
|
-
const [field, setField] =
|
|
1901
|
-
|
|
2723
|
+
const [repoIdx, setRepoIdx] = useState7(defaultRepoIdx);
|
|
2724
|
+
const [title, setTitle] = useState7("");
|
|
2725
|
+
const [field, setField] = useState7("title");
|
|
2726
|
+
useInput6((input2, key) => {
|
|
2727
|
+
if (field === "labels") return;
|
|
1902
2728
|
if (key.escape) return onCancel();
|
|
1903
2729
|
if (field === "repo") {
|
|
1904
2730
|
if (input2 === "j" || key.downArrow) {
|
|
@@ -1912,12 +2738,40 @@ function CreateIssueForm({ repos, defaultRepo, onSubmit, onCancel }) {
|
|
|
1912
2738
|
}
|
|
1913
2739
|
});
|
|
1914
2740
|
const selectedRepo = repos[repoIdx];
|
|
1915
|
-
|
|
1916
|
-
/* @__PURE__ */
|
|
1917
|
-
|
|
1918
|
-
/* @__PURE__ */
|
|
1919
|
-
|
|
1920
|
-
|
|
2741
|
+
if (field === "labels" && selectedRepo) {
|
|
2742
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
2743
|
+
/* @__PURE__ */ jsx6(Text6, { color: "cyan", bold: true, children: "Create Issue \u2014 Add Labels (optional)" }),
|
|
2744
|
+
/* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
2745
|
+
"Repo: ",
|
|
2746
|
+
selectedRepo.shortName,
|
|
2747
|
+
" Title: ",
|
|
2748
|
+
title
|
|
2749
|
+
] }),
|
|
2750
|
+
/* @__PURE__ */ jsx6(
|
|
2751
|
+
LabelPicker,
|
|
2752
|
+
{
|
|
2753
|
+
repo: selectedRepo.name,
|
|
2754
|
+
currentLabels: [],
|
|
2755
|
+
labelCache: labelCache ?? {},
|
|
2756
|
+
onConfirm: (addLabels) => {
|
|
2757
|
+
onSubmit(selectedRepo.name, title, addLabels.length > 0 ? addLabels : void 0);
|
|
2758
|
+
},
|
|
2759
|
+
onCancel: () => {
|
|
2760
|
+
onSubmit(selectedRepo.name, title);
|
|
2761
|
+
},
|
|
2762
|
+
onError: () => {
|
|
2763
|
+
onSubmit(selectedRepo.name, title);
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
)
|
|
2767
|
+
] });
|
|
2768
|
+
}
|
|
2769
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
2770
|
+
/* @__PURE__ */ jsx6(Text6, { color: "cyan", bold: true, children: "Create Issue" }),
|
|
2771
|
+
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2772
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: field !== "repo", children: "Repo: " }),
|
|
2773
|
+
repos.map((r, i) => /* @__PURE__ */ jsx6(
|
|
2774
|
+
Text6,
|
|
1921
2775
|
{
|
|
1922
2776
|
...i === repoIdx ? { color: "cyan", bold: true } : {},
|
|
1923
2777
|
dimColor: field !== "repo",
|
|
@@ -1925,215 +2779,53 @@ function CreateIssueForm({ repos, defaultRepo, onSubmit, onCancel }) {
|
|
|
1925
2779
|
},
|
|
1926
2780
|
r.name
|
|
1927
2781
|
)),
|
|
1928
|
-
field === "repo" ? /* @__PURE__ */
|
|
2782
|
+
field === "repo" ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " j/k:select Tab:next" }) : null
|
|
1929
2783
|
] }),
|
|
1930
|
-
/* @__PURE__ */
|
|
1931
|
-
/* @__PURE__ */
|
|
1932
|
-
field === "title" ? /* @__PURE__ */
|
|
2784
|
+
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2785
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: field !== "title", children: "Title: " }),
|
|
2786
|
+
field === "title" ? /* @__PURE__ */ jsx6(
|
|
1933
2787
|
TextInput2,
|
|
1934
2788
|
{
|
|
1935
2789
|
defaultValue: title,
|
|
1936
2790
|
placeholder: "issue title...",
|
|
1937
2791
|
onChange: setTitle,
|
|
1938
2792
|
onSubmit: (text) => {
|
|
1939
|
-
|
|
1940
|
-
|
|
2793
|
+
const trimmed = text.trim();
|
|
2794
|
+
if (!(trimmed && selectedRepo)) return;
|
|
2795
|
+
if (labelCache !== void 0) {
|
|
2796
|
+
setTitle(trimmed);
|
|
2797
|
+
setField("labels");
|
|
2798
|
+
} else {
|
|
2799
|
+
onSubmit(selectedRepo.name, trimmed);
|
|
1941
2800
|
}
|
|
1942
2801
|
}
|
|
1943
2802
|
}
|
|
1944
|
-
) : /* @__PURE__ */
|
|
2803
|
+
) : /* @__PURE__ */ jsx6(Text6, { children: title || "(empty)" })
|
|
1945
2804
|
] }),
|
|
1946
|
-
/* @__PURE__ */
|
|
2805
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "Tab:switch fields Enter:next Esc:cancel" })
|
|
1947
2806
|
] });
|
|
1948
2807
|
}
|
|
1949
2808
|
var init_create_issue_form = __esm({
|
|
1950
2809
|
"src/board/components/create-issue-form.tsx"() {
|
|
1951
2810
|
"use strict";
|
|
1952
|
-
|
|
1953
|
-
});
|
|
1954
|
-
|
|
1955
|
-
// src/board/components/detail-panel.tsx
|
|
1956
|
-
import { Box as Box5, Text as Text5 } from "ink";
|
|
1957
|
-
import { Fragment, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1958
|
-
function truncateLines(text, maxLines) {
|
|
1959
|
-
const lines = text.split("\n").slice(0, maxLines);
|
|
1960
|
-
return lines.join("\n");
|
|
1961
|
-
}
|
|
1962
|
-
function stripMarkdown(text) {
|
|
1963
|
-
return text.replace(/^#{1,6}\s+/gm, "").replace(/\*\*(.+?)\*\*/g, "$1").replace(/\*(.+?)\*/g, "$1").replace(/__(.+?)__/g, "$1").replace(/_(.+?)_/g, "$1").replace(/~~(.+?)~~/g, "$1").replace(/`{1,3}[^`]*`{1,3}/g, (m) => m.replace(/`/g, "")).replace(/^\s*[-*+]\s+/gm, " - ").replace(/^\s*\d+\.\s+/gm, " ").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/!\[([^\]]*)\]\([^)]+\)/g, "[$1]").replace(/^>\s+/gm, " ").replace(/---+/g, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
1964
|
-
}
|
|
1965
|
-
function formatBody(body, maxLines) {
|
|
1966
|
-
const plain = stripMarkdown(body);
|
|
1967
|
-
const lines = plain.split("\n");
|
|
1968
|
-
const truncated = lines.slice(0, maxLines).join("\n");
|
|
1969
|
-
return { text: truncated, remaining: Math.max(0, lines.length - maxLines) };
|
|
1970
|
-
}
|
|
1971
|
-
function countSlackLinks(body) {
|
|
1972
|
-
if (!body) return 0;
|
|
1973
|
-
return (body.match(SLACK_URL_RE) ?? []).length;
|
|
1974
|
-
}
|
|
1975
|
-
function BodySection({
|
|
1976
|
-
body,
|
|
1977
|
-
issueNumber
|
|
1978
|
-
}) {
|
|
1979
|
-
const { text, remaining } = formatBody(body, 15);
|
|
1980
|
-
return /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
1981
|
-
/* @__PURE__ */ jsx5(Text5, { children: "" }),
|
|
1982
|
-
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "--- Description ---" }),
|
|
1983
|
-
/* @__PURE__ */ jsx5(Text5, { wrap: "wrap", children: text }),
|
|
1984
|
-
remaining > 0 ? /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
1985
|
-
"... (",
|
|
1986
|
-
remaining,
|
|
1987
|
-
" more lines \u2014 gh issue view ",
|
|
1988
|
-
issueNumber,
|
|
1989
|
-
" for full)"
|
|
1990
|
-
] }) : null
|
|
1991
|
-
] });
|
|
1992
|
-
}
|
|
1993
|
-
function DetailPanel({ issue, task: task2, width }) {
|
|
1994
|
-
if (!(issue || task2)) {
|
|
1995
|
-
return /* @__PURE__ */ jsx5(
|
|
1996
|
-
Box5,
|
|
1997
|
-
{
|
|
1998
|
-
width,
|
|
1999
|
-
borderStyle: "single",
|
|
2000
|
-
borderColor: "gray",
|
|
2001
|
-
flexDirection: "column",
|
|
2002
|
-
paddingX: 1,
|
|
2003
|
-
children: /* @__PURE__ */ jsx5(Text5, { color: "gray", children: "No item selected" })
|
|
2004
|
-
}
|
|
2005
|
-
);
|
|
2006
|
-
}
|
|
2007
|
-
if (issue) {
|
|
2008
|
-
return /* @__PURE__ */ jsxs5(
|
|
2009
|
-
Box5,
|
|
2010
|
-
{
|
|
2011
|
-
width,
|
|
2012
|
-
borderStyle: "single",
|
|
2013
|
-
borderColor: "cyan",
|
|
2014
|
-
flexDirection: "column",
|
|
2015
|
-
paddingX: 1,
|
|
2016
|
-
children: [
|
|
2017
|
-
/* @__PURE__ */ jsxs5(Text5, { color: "cyan", bold: true, children: [
|
|
2018
|
-
"#",
|
|
2019
|
-
issue.number,
|
|
2020
|
-
" ",
|
|
2021
|
-
issue.title
|
|
2022
|
-
] }),
|
|
2023
|
-
/* @__PURE__ */ jsx5(Text5, { children: "" }),
|
|
2024
|
-
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
2025
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "State: " }),
|
|
2026
|
-
/* @__PURE__ */ jsx5(Text5, { color: issue.state === "open" ? "green" : "red", children: issue.state })
|
|
2027
|
-
] }),
|
|
2028
|
-
(issue.assignees ?? []).length > 0 ? /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
2029
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Assignees: " }),
|
|
2030
|
-
/* @__PURE__ */ jsx5(Text5, { children: (issue.assignees ?? []).map((a) => a.login).join(", ") })
|
|
2031
|
-
] }) : null,
|
|
2032
|
-
issue.labels.length > 0 ? /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
2033
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Labels: " }),
|
|
2034
|
-
/* @__PURE__ */ jsx5(Text5, { children: issue.labels.map((l) => l.name).join(", ") })
|
|
2035
|
-
] }) : null,
|
|
2036
|
-
issue.projectStatus ? /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
2037
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Status: " }),
|
|
2038
|
-
/* @__PURE__ */ jsx5(Text5, { color: "magenta", children: issue.projectStatus })
|
|
2039
|
-
] }) : null,
|
|
2040
|
-
issue.targetDate ? /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
2041
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Target: " }),
|
|
2042
|
-
/* @__PURE__ */ jsx5(Text5, { children: issue.targetDate })
|
|
2043
|
-
] }) : null,
|
|
2044
|
-
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
2045
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Updated: " }),
|
|
2046
|
-
/* @__PURE__ */ jsx5(Text5, { children: new Date(issue.updatedAt).toLocaleString() })
|
|
2047
|
-
] }),
|
|
2048
|
-
issue.slackThreadUrl ? /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
2049
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Slack: " }),
|
|
2050
|
-
/* @__PURE__ */ jsx5(Text5, { color: "blue", children: countSlackLinks(issue.body) > 1 ? `${countSlackLinks(issue.body)} links (s opens first)` : "thread (s to open)" })
|
|
2051
|
-
] }) : null,
|
|
2052
|
-
issue.body ? /* @__PURE__ */ jsx5(BodySection, { body: issue.body, issueNumber: issue.number }) : /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
2053
|
-
/* @__PURE__ */ jsx5(Text5, { children: "" }),
|
|
2054
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "(no description)" })
|
|
2055
|
-
] }),
|
|
2056
|
-
/* @__PURE__ */ jsx5(Text5, { children: "" }),
|
|
2057
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", dimColor: true, children: issue.url })
|
|
2058
|
-
]
|
|
2059
|
-
}
|
|
2060
|
-
);
|
|
2061
|
-
}
|
|
2062
|
-
const t = task2;
|
|
2063
|
-
return /* @__PURE__ */ jsxs5(
|
|
2064
|
-
Box5,
|
|
2065
|
-
{
|
|
2066
|
-
width,
|
|
2067
|
-
borderStyle: "single",
|
|
2068
|
-
borderColor: "yellow",
|
|
2069
|
-
flexDirection: "column",
|
|
2070
|
-
paddingX: 1,
|
|
2071
|
-
children: [
|
|
2072
|
-
/* @__PURE__ */ jsx5(Text5, { color: "yellow", bold: true, children: t.title }),
|
|
2073
|
-
/* @__PURE__ */ jsx5(Text5, { children: "" }),
|
|
2074
|
-
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
2075
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Priority: " }),
|
|
2076
|
-
/* @__PURE__ */ jsx5(Text5, { children: PRIORITY_LABELS2[t.priority] ?? "None" })
|
|
2077
|
-
] }),
|
|
2078
|
-
t.dueDate ? /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
2079
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Due: " }),
|
|
2080
|
-
/* @__PURE__ */ jsx5(Text5, { children: new Date(t.dueDate).toLocaleDateString() })
|
|
2081
|
-
] }) : null,
|
|
2082
|
-
(t.tags ?? []).length > 0 ? /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
2083
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Tags: " }),
|
|
2084
|
-
/* @__PURE__ */ jsx5(Text5, { children: t.tags.join(", ") })
|
|
2085
|
-
] }) : null,
|
|
2086
|
-
t.content ? /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
2087
|
-
/* @__PURE__ */ jsx5(Text5, { children: "" }),
|
|
2088
|
-
/* @__PURE__ */ jsx5(Text5, { children: truncateLines(t.content, 8) })
|
|
2089
|
-
] }) : null,
|
|
2090
|
-
(t.items ?? []).length > 0 ? /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
2091
|
-
/* @__PURE__ */ jsx5(Text5, { children: "" }),
|
|
2092
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Checklist:" }),
|
|
2093
|
-
t.items.slice(0, 5).map((item) => /* @__PURE__ */ jsxs5(Text5, { children: [
|
|
2094
|
-
item.status === 2 ? "\u2611" : "\u2610",
|
|
2095
|
-
" ",
|
|
2096
|
-
item.title
|
|
2097
|
-
] }, item.id)),
|
|
2098
|
-
t.items.length > 5 ? /* @__PURE__ */ jsxs5(Text5, { color: "gray", children: [
|
|
2099
|
-
"...and ",
|
|
2100
|
-
t.items.length - 5,
|
|
2101
|
-
" more"
|
|
2102
|
-
] }) : null
|
|
2103
|
-
] }) : null
|
|
2104
|
-
]
|
|
2105
|
-
}
|
|
2106
|
-
);
|
|
2107
|
-
}
|
|
2108
|
-
var SLACK_URL_RE, PRIORITY_LABELS2;
|
|
2109
|
-
var init_detail_panel = __esm({
|
|
2110
|
-
"src/board/components/detail-panel.tsx"() {
|
|
2111
|
-
"use strict";
|
|
2112
|
-
init_types();
|
|
2113
|
-
SLACK_URL_RE = /https:\/\/[^/]+\.slack\.com\/archives\/[A-Z0-9]+\/p[0-9]+/gi;
|
|
2114
|
-
PRIORITY_LABELS2 = {
|
|
2115
|
-
[5 /* High */]: "High",
|
|
2116
|
-
[3 /* Medium */]: "Medium",
|
|
2117
|
-
[1 /* Low */]: "Low",
|
|
2118
|
-
[0 /* None */]: "None"
|
|
2119
|
-
};
|
|
2811
|
+
init_label_picker();
|
|
2120
2812
|
}
|
|
2121
2813
|
});
|
|
2122
2814
|
|
|
2123
2815
|
// src/board/components/focus-mode.tsx
|
|
2124
|
-
import { Box as
|
|
2125
|
-
import { useCallback as
|
|
2126
|
-
import { jsx as
|
|
2816
|
+
import { Box as Box7, Text as Text7, useInput as useInput7 } from "ink";
|
|
2817
|
+
import { useCallback as useCallback8, useEffect as useEffect4, useRef as useRef8, useState as useState8 } from "react";
|
|
2818
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2127
2819
|
function formatTime(secs) {
|
|
2128
2820
|
const m = Math.floor(secs / 60);
|
|
2129
2821
|
const s = secs % 60;
|
|
2130
2822
|
return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
|
2131
2823
|
}
|
|
2132
2824
|
function FocusMode({ label, durationSec, onExit, onEndAction }) {
|
|
2133
|
-
const [remaining, setRemaining] =
|
|
2134
|
-
const [timerDone, setTimerDone] =
|
|
2135
|
-
const bellSentRef =
|
|
2136
|
-
|
|
2825
|
+
const [remaining, setRemaining] = useState8(durationSec);
|
|
2826
|
+
const [timerDone, setTimerDone] = useState8(false);
|
|
2827
|
+
const bellSentRef = useRef8(false);
|
|
2828
|
+
useEffect4(() => {
|
|
2137
2829
|
if (timerDone) return;
|
|
2138
2830
|
const interval = setInterval(() => {
|
|
2139
2831
|
setRemaining((prev) => {
|
|
@@ -2147,13 +2839,13 @@ function FocusMode({ label, durationSec, onExit, onEndAction }) {
|
|
|
2147
2839
|
}, 1e3);
|
|
2148
2840
|
return () => clearInterval(interval);
|
|
2149
2841
|
}, [timerDone]);
|
|
2150
|
-
|
|
2842
|
+
useEffect4(() => {
|
|
2151
2843
|
if (timerDone && !bellSentRef.current) {
|
|
2152
2844
|
bellSentRef.current = true;
|
|
2153
2845
|
process.stdout.write("\x07");
|
|
2154
2846
|
}
|
|
2155
2847
|
}, [timerDone]);
|
|
2156
|
-
const handleInput =
|
|
2848
|
+
const handleInput = useCallback8(
|
|
2157
2849
|
(input2, key) => {
|
|
2158
2850
|
if (key.escape) {
|
|
2159
2851
|
if (timerDone) {
|
|
@@ -2178,25 +2870,25 @@ function FocusMode({ label, durationSec, onExit, onEndAction }) {
|
|
|
2178
2870
|
},
|
|
2179
2871
|
[timerDone, onExit, onEndAction]
|
|
2180
2872
|
);
|
|
2181
|
-
|
|
2873
|
+
useInput7(handleInput);
|
|
2182
2874
|
if (timerDone) {
|
|
2183
|
-
return /* @__PURE__ */
|
|
2184
|
-
/* @__PURE__ */
|
|
2185
|
-
/* @__PURE__ */
|
|
2186
|
-
/* @__PURE__ */
|
|
2875
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
2876
|
+
/* @__PURE__ */ jsxs7(Box7, { children: [
|
|
2877
|
+
/* @__PURE__ */ jsx7(Text7, { color: "green", bold: true, children: "Focus complete!" }),
|
|
2878
|
+
/* @__PURE__ */ jsxs7(Text7, { color: "gray", children: [
|
|
2187
2879
|
" ",
|
|
2188
2880
|
label
|
|
2189
2881
|
] })
|
|
2190
2882
|
] }),
|
|
2191
|
-
/* @__PURE__ */
|
|
2192
|
-
/* @__PURE__ */
|
|
2193
|
-
/* @__PURE__ */
|
|
2194
|
-
/* @__PURE__ */
|
|
2195
|
-
/* @__PURE__ */
|
|
2196
|
-
/* @__PURE__ */
|
|
2197
|
-
/* @__PURE__ */
|
|
2198
|
-
/* @__PURE__ */
|
|
2199
|
-
/* @__PURE__ */
|
|
2883
|
+
/* @__PURE__ */ jsxs7(Box7, { marginTop: 1, children: [
|
|
2884
|
+
/* @__PURE__ */ jsx7(Text7, { color: "cyan", children: "[c]" }),
|
|
2885
|
+
/* @__PURE__ */ jsx7(Text7, { children: " Continue " }),
|
|
2886
|
+
/* @__PURE__ */ jsx7(Text7, { color: "cyan", children: "[b]" }),
|
|
2887
|
+
/* @__PURE__ */ jsx7(Text7, { children: " Break " }),
|
|
2888
|
+
/* @__PURE__ */ jsx7(Text7, { color: "cyan", children: "[d]" }),
|
|
2889
|
+
/* @__PURE__ */ jsx7(Text7, { children: " Done " }),
|
|
2890
|
+
/* @__PURE__ */ jsx7(Text7, { color: "gray", children: "[Esc]" }),
|
|
2891
|
+
/* @__PURE__ */ jsx7(Text7, { children: " Exit" })
|
|
2200
2892
|
] })
|
|
2201
2893
|
] });
|
|
2202
2894
|
}
|
|
@@ -2204,21 +2896,21 @@ function FocusMode({ label, durationSec, onExit, onEndAction }) {
|
|
|
2204
2896
|
const barWidth = 20;
|
|
2205
2897
|
const filled = Math.round(progress * barWidth);
|
|
2206
2898
|
const bar = "\u2588".repeat(filled) + "\u2591".repeat(barWidth - filled);
|
|
2207
|
-
return /* @__PURE__ */
|
|
2208
|
-
/* @__PURE__ */
|
|
2209
|
-
/* @__PURE__ */
|
|
2899
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
2900
|
+
/* @__PURE__ */ jsxs7(Box7, { children: [
|
|
2901
|
+
/* @__PURE__ */ jsxs7(Text7, { color: "magenta", bold: true, children: [
|
|
2210
2902
|
"Focus:",
|
|
2211
2903
|
" "
|
|
2212
2904
|
] }),
|
|
2213
|
-
/* @__PURE__ */
|
|
2905
|
+
/* @__PURE__ */ jsx7(Text7, { children: label })
|
|
2214
2906
|
] }),
|
|
2215
|
-
/* @__PURE__ */
|
|
2216
|
-
/* @__PURE__ */
|
|
2217
|
-
/* @__PURE__ */
|
|
2218
|
-
/* @__PURE__ */
|
|
2219
|
-
/* @__PURE__ */
|
|
2907
|
+
/* @__PURE__ */ jsxs7(Box7, { children: [
|
|
2908
|
+
/* @__PURE__ */ jsx7(Text7, { color: "magenta", children: bar }),
|
|
2909
|
+
/* @__PURE__ */ jsx7(Text7, { children: " " }),
|
|
2910
|
+
/* @__PURE__ */ jsx7(Text7, { bold: true, children: formatTime(remaining) }),
|
|
2911
|
+
/* @__PURE__ */ jsx7(Text7, { color: "gray", children: " remaining" })
|
|
2220
2912
|
] }),
|
|
2221
|
-
/* @__PURE__ */
|
|
2913
|
+
/* @__PURE__ */ jsx7(Text7, { color: "gray", dimColor: true, children: "Esc to exit focus" })
|
|
2222
2914
|
] });
|
|
2223
2915
|
}
|
|
2224
2916
|
var init_focus_mode = __esm({
|
|
@@ -2228,29 +2920,29 @@ var init_focus_mode = __esm({
|
|
|
2228
2920
|
});
|
|
2229
2921
|
|
|
2230
2922
|
// src/board/components/help-overlay.tsx
|
|
2231
|
-
import { Box as
|
|
2232
|
-
import { jsx as
|
|
2923
|
+
import { Box as Box8, Text as Text8, useInput as useInput8 } from "ink";
|
|
2924
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2233
2925
|
function HelpOverlay({ currentMode, onClose }) {
|
|
2234
|
-
|
|
2926
|
+
useInput8((_input, key) => {
|
|
2235
2927
|
if (key.escape) onClose();
|
|
2236
2928
|
});
|
|
2237
|
-
return /* @__PURE__ */
|
|
2238
|
-
/* @__PURE__ */
|
|
2239
|
-
/* @__PURE__ */
|
|
2240
|
-
/* @__PURE__ */
|
|
2929
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
|
|
2930
|
+
/* @__PURE__ */ jsxs8(Box8, { justifyContent: "space-between", children: [
|
|
2931
|
+
/* @__PURE__ */ jsx8(Text8, { color: "cyan", bold: true, children: "Keyboard Shortcuts" }),
|
|
2932
|
+
/* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
2241
2933
|
"mode: ",
|
|
2242
2934
|
currentMode
|
|
2243
2935
|
] })
|
|
2244
2936
|
] }),
|
|
2245
|
-
/* @__PURE__ */
|
|
2246
|
-
SHORTCUTS.map((group) => /* @__PURE__ */
|
|
2247
|
-
/* @__PURE__ */
|
|
2248
|
-
group.items.map((item) => /* @__PURE__ */
|
|
2249
|
-
/* @__PURE__ */
|
|
2250
|
-
/* @__PURE__ */
|
|
2937
|
+
/* @__PURE__ */ jsx8(Text8, { children: " " }),
|
|
2938
|
+
SHORTCUTS.map((group) => /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginBottom: 1, children: [
|
|
2939
|
+
/* @__PURE__ */ jsx8(Text8, { color: "yellow", bold: true, children: group.category }),
|
|
2940
|
+
group.items.map((item) => /* @__PURE__ */ jsxs8(Box8, { children: [
|
|
2941
|
+
/* @__PURE__ */ jsx8(Box8, { width: 16, children: /* @__PURE__ */ jsx8(Text8, { color: "green", children: item.key }) }),
|
|
2942
|
+
/* @__PURE__ */ jsx8(Text8, { children: item.desc })
|
|
2251
2943
|
] }, item.key))
|
|
2252
2944
|
] }, group.category)),
|
|
2253
|
-
/* @__PURE__ */
|
|
2945
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Press ? or Esc to close" })
|
|
2254
2946
|
] });
|
|
2255
2947
|
}
|
|
2256
2948
|
var SHORTCUTS;
|
|
@@ -2286,23 +2978,439 @@ var init_help_overlay = __esm({
|
|
|
2286
2978
|
{ key: "c", desc: "Comment on issue" },
|
|
2287
2979
|
{ key: "m", desc: "Move status" },
|
|
2288
2980
|
{ key: "s", desc: "Open Slack thread" },
|
|
2981
|
+
{ key: "y", desc: "Copy issue link to clipboard" },
|
|
2289
2982
|
{ key: "n", desc: "Create new issue" }
|
|
2290
2983
|
]
|
|
2291
2984
|
},
|
|
2292
2985
|
{
|
|
2293
|
-
category: "Board",
|
|
2294
|
-
items: [
|
|
2295
|
-
{ key: "r", desc: "Refresh data" },
|
|
2296
|
-
{ key: "q", desc: "Quit" }
|
|
2297
|
-
]
|
|
2986
|
+
category: "Board",
|
|
2987
|
+
items: [
|
|
2988
|
+
{ key: "r", desc: "Refresh data" },
|
|
2989
|
+
{ key: "q", desc: "Quit" }
|
|
2990
|
+
]
|
|
2991
|
+
}
|
|
2992
|
+
];
|
|
2993
|
+
}
|
|
2994
|
+
});
|
|
2995
|
+
|
|
2996
|
+
// src/board/components/nl-create-overlay.tsx
|
|
2997
|
+
import { Spinner as Spinner2, TextInput as TextInput3 } from "@inkjs/ui";
|
|
2998
|
+
import { Box as Box9, Text as Text9, useInput as useInput9 } from "ink";
|
|
2999
|
+
import { useCallback as useCallback9, useEffect as useEffect5, useRef as useRef9, useState as useState9 } from "react";
|
|
3000
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
3001
|
+
function NlCreateOverlay({
|
|
3002
|
+
repos,
|
|
3003
|
+
defaultRepoName,
|
|
3004
|
+
labelCache,
|
|
3005
|
+
onSubmit,
|
|
3006
|
+
onCancel,
|
|
3007
|
+
onLlmFallback
|
|
3008
|
+
}) {
|
|
3009
|
+
const [, setInput] = useState9("");
|
|
3010
|
+
const [isParsing, setIsParsing] = useState9(false);
|
|
3011
|
+
const [parsed, setParsed] = useState9(null);
|
|
3012
|
+
const [parseError, setParseError] = useState9(null);
|
|
3013
|
+
const [createError, setCreateError] = useState9(null);
|
|
3014
|
+
const submittedRef = useRef9(false);
|
|
3015
|
+
const parseParamsRef = useRef9(null);
|
|
3016
|
+
const defaultRepoIdx = defaultRepoName ? Math.max(
|
|
3017
|
+
0,
|
|
3018
|
+
repos.findIndex((r) => r.name === defaultRepoName)
|
|
3019
|
+
) : 0;
|
|
3020
|
+
const [repoIdx, setRepoIdx] = useState9(defaultRepoIdx);
|
|
3021
|
+
const selectedRepo = repos[repoIdx];
|
|
3022
|
+
useInput9((inputChar, key) => {
|
|
3023
|
+
if (isParsing) return;
|
|
3024
|
+
if (key.escape) {
|
|
3025
|
+
onCancel();
|
|
3026
|
+
return;
|
|
3027
|
+
}
|
|
3028
|
+
if (parsed) {
|
|
3029
|
+
if (key.return) {
|
|
3030
|
+
if (submittedRef.current) return;
|
|
3031
|
+
submittedRef.current = true;
|
|
3032
|
+
if (!selectedRepo) return;
|
|
3033
|
+
setCreateError(null);
|
|
3034
|
+
const labels = buildLabelList(parsed);
|
|
3035
|
+
onSubmit(selectedRepo.name, parsed.title, labels.length > 0 ? labels : void 0);
|
|
3036
|
+
return;
|
|
3037
|
+
}
|
|
3038
|
+
if (inputChar === "r") {
|
|
3039
|
+
setRepoIdx((i) => (i + 1) % repos.length);
|
|
3040
|
+
return;
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
});
|
|
3044
|
+
const handleInputSubmit = useCallback9(
|
|
3045
|
+
(text) => {
|
|
3046
|
+
const trimmed = text.trim();
|
|
3047
|
+
if (!trimmed) return;
|
|
3048
|
+
const validLabels = selectedRepo ? (labelCache[selectedRepo.name] ?? []).map((l) => l.name) : [];
|
|
3049
|
+
parseParamsRef.current = { input: trimmed, validLabels };
|
|
3050
|
+
setInput(trimmed);
|
|
3051
|
+
setParseError(null);
|
|
3052
|
+
setIsParsing(true);
|
|
3053
|
+
},
|
|
3054
|
+
[selectedRepo, labelCache]
|
|
3055
|
+
);
|
|
3056
|
+
useEffect5(() => {
|
|
3057
|
+
if (!(isParsing && parseParamsRef.current)) return;
|
|
3058
|
+
const { input: capturedInput, validLabels } = parseParamsRef.current;
|
|
3059
|
+
extractIssueFields(capturedInput, {
|
|
3060
|
+
validLabels,
|
|
3061
|
+
onLlmFallback
|
|
3062
|
+
}).then((result) => {
|
|
3063
|
+
if (!result) {
|
|
3064
|
+
setParseError("Title is required");
|
|
3065
|
+
setIsParsing(false);
|
|
3066
|
+
return;
|
|
3067
|
+
}
|
|
3068
|
+
const filteredLabels = validLabels.length > 0 ? result.labels.filter((l) => validLabels.includes(l)) : result.labels;
|
|
3069
|
+
setParsed({ ...result, labels: filteredLabels });
|
|
3070
|
+
setIsParsing(false);
|
|
3071
|
+
}).catch(() => {
|
|
3072
|
+
setParseError("Parsing failed \u2014 please try again");
|
|
3073
|
+
setIsParsing(false);
|
|
3074
|
+
});
|
|
3075
|
+
}, [isParsing, onLlmFallback]);
|
|
3076
|
+
if (isParsing) {
|
|
3077
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
3078
|
+
/* @__PURE__ */ jsx9(Text9, { color: "cyan", bold: true, children: "\u2728 Creating Issue" }),
|
|
3079
|
+
/* @__PURE__ */ jsx9(Spinner2, { label: "Parsing..." })
|
|
3080
|
+
] });
|
|
3081
|
+
}
|
|
3082
|
+
if (parsed) {
|
|
3083
|
+
const labels = buildLabelList(parsed);
|
|
3084
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
3085
|
+
/* @__PURE__ */ jsx9(Text9, { color: "cyan", bold: true, children: "\u2728 Creating Issue" }),
|
|
3086
|
+
/* @__PURE__ */ jsxs9(Box9, { children: [
|
|
3087
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Repo: " }),
|
|
3088
|
+
/* @__PURE__ */ jsx9(Text9, { color: "cyan", children: selectedRepo?.shortName ?? "(none)" }),
|
|
3089
|
+
repos.length > 1 ? /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " r:cycle" }) : null
|
|
3090
|
+
] }),
|
|
3091
|
+
/* @__PURE__ */ jsxs9(Box9, { children: [
|
|
3092
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Title: " }),
|
|
3093
|
+
/* @__PURE__ */ jsx9(Text9, { children: parsed.title })
|
|
3094
|
+
] }),
|
|
3095
|
+
labels.length > 0 ? /* @__PURE__ */ jsxs9(Box9, { children: [
|
|
3096
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Labels: " }),
|
|
3097
|
+
/* @__PURE__ */ jsx9(Text9, { children: labels.join(", ") })
|
|
3098
|
+
] }) : null,
|
|
3099
|
+
parsed.assignee ? /* @__PURE__ */ jsxs9(Box9, { children: [
|
|
3100
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Assignee: " }),
|
|
3101
|
+
/* @__PURE__ */ jsxs9(Text9, { children: [
|
|
3102
|
+
"@",
|
|
3103
|
+
parsed.assignee
|
|
3104
|
+
] })
|
|
3105
|
+
] }) : null,
|
|
3106
|
+
parsed.dueDate ? /* @__PURE__ */ jsxs9(Box9, { children: [
|
|
3107
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Due: " }),
|
|
3108
|
+
/* @__PURE__ */ jsx9(Text9, { children: formatDue(parsed.dueDate) })
|
|
3109
|
+
] }) : null,
|
|
3110
|
+
parsed.dueDate && selectedRepo && !hasDueLabelInCache(labelCache, selectedRepo.name) ? /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "\u26A0 No due:* label in this repo \u2014 will try to create label on submit" }) : null,
|
|
3111
|
+
createError ? /* @__PURE__ */ jsx9(Text9, { color: "red", children: createError }) : null,
|
|
3112
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Enter:create Esc:cancel" })
|
|
3113
|
+
] });
|
|
3114
|
+
}
|
|
3115
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
3116
|
+
/* @__PURE__ */ jsx9(Text9, { color: "cyan", bold: true, children: "\u2728 What do you need to do?" }),
|
|
3117
|
+
/* @__PURE__ */ jsxs9(Box9, { children: [
|
|
3118
|
+
/* @__PURE__ */ jsx9(Text9, { color: "cyan", children: "> " }),
|
|
3119
|
+
/* @__PURE__ */ jsx9(
|
|
3120
|
+
TextInput3,
|
|
3121
|
+
{
|
|
3122
|
+
placeholder: "fix login bug #bug #priority:high @me due friday",
|
|
3123
|
+
onChange: setInput,
|
|
3124
|
+
onSubmit: handleInputSubmit
|
|
3125
|
+
}
|
|
3126
|
+
)
|
|
3127
|
+
] }),
|
|
3128
|
+
parseError ? /* @__PURE__ */ jsx9(Text9, { color: "red", children: parseError }) : null,
|
|
3129
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Tip: #label @user due <date> Enter:parse Esc:cancel" })
|
|
3130
|
+
] });
|
|
3131
|
+
}
|
|
3132
|
+
function buildLabelList(parsed) {
|
|
3133
|
+
const labels = [...parsed.labels];
|
|
3134
|
+
if (parsed.dueDate) {
|
|
3135
|
+
labels.push(`due:${parsed.dueDate}`);
|
|
3136
|
+
}
|
|
3137
|
+
return labels;
|
|
3138
|
+
}
|
|
3139
|
+
function hasDueLabelInCache(labelCache, repoName) {
|
|
3140
|
+
return (labelCache[repoName] ?? []).some((l) => l.name.startsWith("due:"));
|
|
3141
|
+
}
|
|
3142
|
+
function formatDue(dueDate) {
|
|
3143
|
+
const d = /* @__PURE__ */ new Date(`${dueDate}T12:00:00`);
|
|
3144
|
+
const human = d.toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric" });
|
|
3145
|
+
return `${human} (label: due:${dueDate})`;
|
|
3146
|
+
}
|
|
3147
|
+
var init_nl_create_overlay = __esm({
|
|
3148
|
+
"src/board/components/nl-create-overlay.tsx"() {
|
|
3149
|
+
"use strict";
|
|
3150
|
+
init_ai();
|
|
3151
|
+
}
|
|
3152
|
+
});
|
|
3153
|
+
|
|
3154
|
+
// src/board/components/search-bar.tsx
|
|
3155
|
+
import { TextInput as TextInput4 } from "@inkjs/ui";
|
|
3156
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
3157
|
+
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
3158
|
+
function SearchBar({ defaultValue, onChange, onSubmit }) {
|
|
3159
|
+
return /* @__PURE__ */ jsxs10(Box10, { children: [
|
|
3160
|
+
/* @__PURE__ */ jsx10(Text10, { color: "yellow", children: "/" }),
|
|
3161
|
+
/* @__PURE__ */ jsx10(
|
|
3162
|
+
TextInput4,
|
|
3163
|
+
{
|
|
3164
|
+
defaultValue,
|
|
3165
|
+
placeholder: "search...",
|
|
3166
|
+
onChange,
|
|
3167
|
+
onSubmit
|
|
3168
|
+
}
|
|
3169
|
+
)
|
|
3170
|
+
] });
|
|
3171
|
+
}
|
|
3172
|
+
var init_search_bar = __esm({
|
|
3173
|
+
"src/board/components/search-bar.tsx"() {
|
|
3174
|
+
"use strict";
|
|
3175
|
+
}
|
|
3176
|
+
});
|
|
3177
|
+
|
|
3178
|
+
// src/board/components/status-picker.tsx
|
|
3179
|
+
import { Box as Box11, Text as Text11, useInput as useInput10 } from "ink";
|
|
3180
|
+
import { useRef as useRef10, useState as useState10 } from "react";
|
|
3181
|
+
import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
3182
|
+
function isTerminal(name) {
|
|
3183
|
+
return TERMINAL_STATUS_RE2.test(name);
|
|
3184
|
+
}
|
|
3185
|
+
function StatusPicker({
|
|
3186
|
+
options,
|
|
3187
|
+
currentStatus,
|
|
3188
|
+
onSelect,
|
|
3189
|
+
onCancel,
|
|
3190
|
+
showTerminalStatuses = true
|
|
3191
|
+
}) {
|
|
3192
|
+
const [selectedIdx, setSelectedIdx] = useState10(() => {
|
|
3193
|
+
const idx = options.findIndex((o) => o.name === currentStatus);
|
|
3194
|
+
return idx >= 0 ? idx : 0;
|
|
3195
|
+
});
|
|
3196
|
+
const [confirmingTerminal, setConfirmingTerminal] = useState10(false);
|
|
3197
|
+
const submittedRef = useRef10(false);
|
|
3198
|
+
useInput10((input2, key) => {
|
|
3199
|
+
if (confirmingTerminal) {
|
|
3200
|
+
if (input2 === "y" || input2 === "Y") {
|
|
3201
|
+
if (submittedRef.current) return;
|
|
3202
|
+
submittedRef.current = true;
|
|
3203
|
+
const opt = options[selectedIdx];
|
|
3204
|
+
if (opt) onSelect(opt.id);
|
|
3205
|
+
return;
|
|
3206
|
+
}
|
|
3207
|
+
if (input2 === "n" || input2 === "N" || key.escape) {
|
|
3208
|
+
setConfirmingTerminal(false);
|
|
3209
|
+
return;
|
|
3210
|
+
}
|
|
3211
|
+
return;
|
|
3212
|
+
}
|
|
3213
|
+
if (key.escape) return onCancel();
|
|
3214
|
+
if (key.return) {
|
|
3215
|
+
if (submittedRef.current) return;
|
|
3216
|
+
const opt = options[selectedIdx];
|
|
3217
|
+
if (!opt) return;
|
|
3218
|
+
if (isTerminal(opt.name) && showTerminalStatuses) {
|
|
3219
|
+
setConfirmingTerminal(true);
|
|
3220
|
+
return;
|
|
3221
|
+
}
|
|
3222
|
+
submittedRef.current = true;
|
|
3223
|
+
onSelect(opt.id);
|
|
3224
|
+
return;
|
|
3225
|
+
}
|
|
3226
|
+
if (input2 === "j" || key.downArrow) {
|
|
3227
|
+
setSelectedIdx((i) => Math.min(i + 1, options.length - 1));
|
|
3228
|
+
}
|
|
3229
|
+
if (input2 === "k" || key.upArrow) {
|
|
3230
|
+
setSelectedIdx((i) => Math.max(i - 1, 0));
|
|
3231
|
+
}
|
|
3232
|
+
});
|
|
3233
|
+
if (confirmingTerminal) {
|
|
3234
|
+
const opt = options[selectedIdx];
|
|
3235
|
+
return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
|
|
3236
|
+
/* @__PURE__ */ jsxs11(Text11, { color: "yellow", bold: true, children: [
|
|
3237
|
+
"Mark as ",
|
|
3238
|
+
opt?.name,
|
|
3239
|
+
"?"
|
|
3240
|
+
] }),
|
|
3241
|
+
/* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "This will close the issue on GitHub." }),
|
|
3242
|
+
/* @__PURE__ */ jsx11(Text11, { children: "Continue? [y/n]" })
|
|
3243
|
+
] });
|
|
3244
|
+
}
|
|
3245
|
+
return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
|
|
3246
|
+
/* @__PURE__ */ jsx11(Text11, { color: "cyan", bold: true, children: "Move to status:" }),
|
|
3247
|
+
options.map((opt, i) => {
|
|
3248
|
+
const isCurrent = opt.name === currentStatus;
|
|
3249
|
+
const isSelected = i === selectedIdx;
|
|
3250
|
+
const terminal = isTerminal(opt.name) && showTerminalStatuses;
|
|
3251
|
+
const prefix = isSelected ? "> " : " ";
|
|
3252
|
+
const suffix = isCurrent ? " (current)" : terminal ? " (Done)" : "";
|
|
3253
|
+
return /* @__PURE__ */ jsxs11(
|
|
3254
|
+
Text11,
|
|
3255
|
+
{
|
|
3256
|
+
...isSelected ? { color: "cyan" } : terminal ? { color: "yellow" } : {},
|
|
3257
|
+
dimColor: isCurrent,
|
|
3258
|
+
children: [
|
|
3259
|
+
prefix,
|
|
3260
|
+
opt.name,
|
|
3261
|
+
suffix
|
|
3262
|
+
]
|
|
3263
|
+
},
|
|
3264
|
+
opt.id
|
|
3265
|
+
);
|
|
3266
|
+
}),
|
|
3267
|
+
/* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "j/k:navigate Enter:select Esc:cancel" })
|
|
3268
|
+
] });
|
|
3269
|
+
}
|
|
3270
|
+
var TERMINAL_STATUS_RE2;
|
|
3271
|
+
var init_status_picker = __esm({
|
|
3272
|
+
"src/board/components/status-picker.tsx"() {
|
|
3273
|
+
"use strict";
|
|
3274
|
+
TERMINAL_STATUS_RE2 = /^(done|shipped|won't|wont|closed|complete|completed)$/i;
|
|
3275
|
+
}
|
|
3276
|
+
});
|
|
3277
|
+
|
|
3278
|
+
// src/board/components/overlay-renderer.tsx
|
|
3279
|
+
import { Fragment as Fragment2, jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3280
|
+
function OverlayRenderer({
|
|
3281
|
+
uiState,
|
|
3282
|
+
config: config2,
|
|
3283
|
+
selectedRepoStatusOptions,
|
|
3284
|
+
currentStatus,
|
|
3285
|
+
onStatusSelect,
|
|
3286
|
+
onExitOverlay,
|
|
3287
|
+
defaultRepo,
|
|
3288
|
+
onCreateIssue,
|
|
3289
|
+
onConfirmPick,
|
|
3290
|
+
onCancelPick,
|
|
3291
|
+
multiSelectCount,
|
|
3292
|
+
multiSelectType,
|
|
3293
|
+
onBulkAction,
|
|
3294
|
+
focusLabel,
|
|
3295
|
+
focusKey,
|
|
3296
|
+
onFocusExit,
|
|
3297
|
+
onFocusEndAction,
|
|
3298
|
+
searchQuery,
|
|
3299
|
+
onSearchChange,
|
|
3300
|
+
onSearchSubmit,
|
|
3301
|
+
selectedIssue,
|
|
3302
|
+
onComment,
|
|
3303
|
+
onPauseRefresh,
|
|
3304
|
+
onResumeRefresh,
|
|
3305
|
+
onToggleHelp,
|
|
3306
|
+
labelCache,
|
|
3307
|
+
onLabelConfirm,
|
|
3308
|
+
onLabelError,
|
|
3309
|
+
onLlmFallback
|
|
3310
|
+
}) {
|
|
3311
|
+
const { mode, helpVisible } = uiState;
|
|
3312
|
+
return /* @__PURE__ */ jsxs12(Fragment2, { children: [
|
|
3313
|
+
helpVisible ? /* @__PURE__ */ jsx12(HelpOverlay, { currentMode: mode, onClose: onToggleHelp }) : null,
|
|
3314
|
+
mode === "overlay:status" && selectedRepoStatusOptions.length > 0 ? /* @__PURE__ */ jsx12(
|
|
3315
|
+
StatusPicker,
|
|
3316
|
+
{
|
|
3317
|
+
options: selectedRepoStatusOptions,
|
|
3318
|
+
currentStatus,
|
|
3319
|
+
onSelect: onStatusSelect,
|
|
3320
|
+
onCancel: onExitOverlay
|
|
3321
|
+
}
|
|
3322
|
+
) : null,
|
|
3323
|
+
mode === "overlay:create" ? /* @__PURE__ */ jsx12(
|
|
3324
|
+
CreateIssueForm,
|
|
3325
|
+
{
|
|
3326
|
+
repos: config2.repos,
|
|
3327
|
+
defaultRepo,
|
|
3328
|
+
onSubmit: onCreateIssue,
|
|
3329
|
+
onCancel: onExitOverlay,
|
|
3330
|
+
labelCache
|
|
3331
|
+
}
|
|
3332
|
+
) : null,
|
|
3333
|
+
mode === "overlay:confirmPick" ? /* @__PURE__ */ jsx12(
|
|
3334
|
+
ConfirmPrompt,
|
|
3335
|
+
{
|
|
3336
|
+
message: "Pick this issue?",
|
|
3337
|
+
onConfirm: onConfirmPick,
|
|
3338
|
+
onCancel: onCancelPick
|
|
3339
|
+
}
|
|
3340
|
+
) : null,
|
|
3341
|
+
mode === "overlay:bulkAction" ? /* @__PURE__ */ jsx12(
|
|
3342
|
+
BulkActionMenu,
|
|
3343
|
+
{
|
|
3344
|
+
count: multiSelectCount,
|
|
3345
|
+
selectionType: multiSelectType,
|
|
3346
|
+
onSelect: onBulkAction,
|
|
3347
|
+
onCancel: onExitOverlay
|
|
3348
|
+
}
|
|
3349
|
+
) : null,
|
|
3350
|
+
mode === "focus" && focusLabel ? /* @__PURE__ */ jsx12(
|
|
3351
|
+
FocusMode,
|
|
3352
|
+
{
|
|
3353
|
+
label: focusLabel,
|
|
3354
|
+
durationSec: config2.board.focusDuration ?? 1500,
|
|
3355
|
+
onExit: onFocusExit,
|
|
3356
|
+
onEndAction: onFocusEndAction
|
|
3357
|
+
},
|
|
3358
|
+
focusKey
|
|
3359
|
+
) : null,
|
|
3360
|
+
mode === "overlay:label" && selectedIssue && defaultRepo ? /* @__PURE__ */ jsx12(
|
|
3361
|
+
LabelPicker,
|
|
3362
|
+
{
|
|
3363
|
+
repo: defaultRepo,
|
|
3364
|
+
currentLabels: selectedIssue.labels.map((l) => l.name),
|
|
3365
|
+
labelCache,
|
|
3366
|
+
onConfirm: onLabelConfirm,
|
|
3367
|
+
onCancel: onExitOverlay,
|
|
3368
|
+
onError: onLabelError
|
|
2298
3369
|
}
|
|
2299
|
-
|
|
3370
|
+
) : null,
|
|
3371
|
+
mode === "search" ? /* @__PURE__ */ jsx12(SearchBar, { defaultValue: searchQuery, onChange: onSearchChange, onSubmit: onSearchSubmit }) : null,
|
|
3372
|
+
mode === "overlay:comment" && selectedIssue ? /* @__PURE__ */ jsx12(
|
|
3373
|
+
CommentInput,
|
|
3374
|
+
{
|
|
3375
|
+
issueNumber: selectedIssue.number,
|
|
3376
|
+
onSubmit: onComment,
|
|
3377
|
+
onCancel: onExitOverlay,
|
|
3378
|
+
onPauseRefresh,
|
|
3379
|
+
onResumeRefresh
|
|
3380
|
+
}
|
|
3381
|
+
) : null,
|
|
3382
|
+
mode === "overlay:createNl" ? /* @__PURE__ */ jsx12(
|
|
3383
|
+
NlCreateOverlay,
|
|
3384
|
+
{
|
|
3385
|
+
repos: config2.repos,
|
|
3386
|
+
defaultRepoName: defaultRepo,
|
|
3387
|
+
labelCache,
|
|
3388
|
+
onSubmit: onCreateIssue,
|
|
3389
|
+
onCancel: onExitOverlay,
|
|
3390
|
+
onLlmFallback
|
|
3391
|
+
}
|
|
3392
|
+
) : null
|
|
3393
|
+
] });
|
|
3394
|
+
}
|
|
3395
|
+
var init_overlay_renderer = __esm({
|
|
3396
|
+
"src/board/components/overlay-renderer.tsx"() {
|
|
3397
|
+
"use strict";
|
|
3398
|
+
init_bulk_action_menu();
|
|
3399
|
+
init_comment_input();
|
|
3400
|
+
init_confirm_prompt();
|
|
3401
|
+
init_create_issue_form();
|
|
3402
|
+
init_focus_mode();
|
|
3403
|
+
init_help_overlay();
|
|
3404
|
+
init_label_picker();
|
|
3405
|
+
init_nl_create_overlay();
|
|
3406
|
+
init_search_bar();
|
|
3407
|
+
init_status_picker();
|
|
2300
3408
|
}
|
|
2301
3409
|
});
|
|
2302
3410
|
|
|
2303
3411
|
// src/board/components/issue-row.tsx
|
|
2304
|
-
import { Box as
|
|
2305
|
-
import { Fragment as
|
|
3412
|
+
import { Box as Box12, Text as Text12 } from "ink";
|
|
3413
|
+
import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
2306
3414
|
function truncate(s, max) {
|
|
2307
3415
|
return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
|
|
2308
3416
|
}
|
|
@@ -2343,30 +3451,30 @@ function IssueRow({ issue, selfLogin, isSelected }) {
|
|
|
2343
3451
|
const labels = (issue.labels ?? []).slice(0, 2);
|
|
2344
3452
|
const target = formatTargetDate(issue.targetDate);
|
|
2345
3453
|
const titleStr = truncate(issue.title, 42).padEnd(42);
|
|
2346
|
-
return /* @__PURE__ */
|
|
2347
|
-
isSelected ? /* @__PURE__ */
|
|
2348
|
-
/* @__PURE__ */
|
|
3454
|
+
return /* @__PURE__ */ jsxs13(Box12, { children: [
|
|
3455
|
+
isSelected ? /* @__PURE__ */ jsx13(Text12, { color: "cyan", bold: true, children: "\u25B6 " }) : /* @__PURE__ */ jsx13(Text12, { children: " " }),
|
|
3456
|
+
/* @__PURE__ */ jsxs13(Text12, { color: "cyan", children: [
|
|
2349
3457
|
"#",
|
|
2350
3458
|
String(issue.number).padEnd(5)
|
|
2351
3459
|
] }),
|
|
2352
|
-
/* @__PURE__ */
|
|
2353
|
-
isSelected ? /* @__PURE__ */
|
|
2354
|
-
/* @__PURE__ */
|
|
2355
|
-
/* @__PURE__ */
|
|
3460
|
+
/* @__PURE__ */ jsx13(Text12, { children: " " }),
|
|
3461
|
+
isSelected ? /* @__PURE__ */ jsx13(Text12, { color: "white", bold: true, children: titleStr }) : /* @__PURE__ */ jsx13(Text12, { children: titleStr }),
|
|
3462
|
+
/* @__PURE__ */ jsx13(Text12, { children: " " }),
|
|
3463
|
+
/* @__PURE__ */ jsx13(Box12, { width: LABEL_COL_WIDTH, children: labels.map((l, i) => /* @__PURE__ */ jsxs13(Text12, { children: [
|
|
2356
3464
|
i > 0 ? " " : "",
|
|
2357
|
-
/* @__PURE__ */
|
|
3465
|
+
/* @__PURE__ */ jsxs13(Text12, { color: labelColor(l.name), children: [
|
|
2358
3466
|
"[",
|
|
2359
3467
|
truncate(l.name, 12),
|
|
2360
3468
|
"]"
|
|
2361
3469
|
] })
|
|
2362
3470
|
] }, l.name)) }),
|
|
2363
|
-
/* @__PURE__ */
|
|
2364
|
-
/* @__PURE__ */
|
|
2365
|
-
/* @__PURE__ */
|
|
2366
|
-
/* @__PURE__ */
|
|
2367
|
-
target.text ? /* @__PURE__ */
|
|
2368
|
-
/* @__PURE__ */
|
|
2369
|
-
/* @__PURE__ */
|
|
3471
|
+
/* @__PURE__ */ jsx13(Text12, { children: " " }),
|
|
3472
|
+
/* @__PURE__ */ jsx13(Text12, { color: assigneeColor, children: assigneeText.padEnd(14) }),
|
|
3473
|
+
/* @__PURE__ */ jsx13(Text12, { children: " " }),
|
|
3474
|
+
/* @__PURE__ */ jsx13(Text12, { color: "gray", children: timeAgo(issue.updatedAt).padStart(4) }),
|
|
3475
|
+
target.text ? /* @__PURE__ */ jsxs13(Fragment3, { children: [
|
|
3476
|
+
/* @__PURE__ */ jsx13(Text12, { children: " " }),
|
|
3477
|
+
/* @__PURE__ */ jsx13(Text12, { color: target.color, children: target.text })
|
|
2370
3478
|
] }) : null
|
|
2371
3479
|
] });
|
|
2372
3480
|
}
|
|
@@ -2389,90 +3497,13 @@ var init_issue_row = __esm({
|
|
|
2389
3497
|
}
|
|
2390
3498
|
});
|
|
2391
3499
|
|
|
2392
|
-
// src/board/components/search-bar.tsx
|
|
2393
|
-
import { TextInput as TextInput3 } from "@inkjs/ui";
|
|
2394
|
-
import { Box as Box9, Text as Text9 } from "ink";
|
|
2395
|
-
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2396
|
-
function SearchBar({ defaultValue, onChange, onSubmit }) {
|
|
2397
|
-
return /* @__PURE__ */ jsxs9(Box9, { children: [
|
|
2398
|
-
/* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "/" }),
|
|
2399
|
-
/* @__PURE__ */ jsx9(
|
|
2400
|
-
TextInput3,
|
|
2401
|
-
{
|
|
2402
|
-
defaultValue,
|
|
2403
|
-
placeholder: "search...",
|
|
2404
|
-
onChange,
|
|
2405
|
-
onSubmit
|
|
2406
|
-
}
|
|
2407
|
-
)
|
|
2408
|
-
] });
|
|
2409
|
-
}
|
|
2410
|
-
var init_search_bar = __esm({
|
|
2411
|
-
"src/board/components/search-bar.tsx"() {
|
|
2412
|
-
"use strict";
|
|
2413
|
-
}
|
|
2414
|
-
});
|
|
2415
|
-
|
|
2416
|
-
// src/board/components/status-picker.tsx
|
|
2417
|
-
import { Box as Box10, Text as Text10, useInput as useInput7 } from "ink";
|
|
2418
|
-
import { useState as useState8 } from "react";
|
|
2419
|
-
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2420
|
-
function StatusPicker({ options, currentStatus, onSelect, onCancel }) {
|
|
2421
|
-
const [selectedIdx, setSelectedIdx] = useState8(() => {
|
|
2422
|
-
const idx = options.findIndex((o) => o.name === currentStatus);
|
|
2423
|
-
return idx >= 0 ? idx : 0;
|
|
2424
|
-
});
|
|
2425
|
-
useInput7((input2, key) => {
|
|
2426
|
-
if (key.escape) return onCancel();
|
|
2427
|
-
if (key.return) {
|
|
2428
|
-
const opt = options[selectedIdx];
|
|
2429
|
-
if (opt) onSelect(opt.id);
|
|
2430
|
-
return;
|
|
2431
|
-
}
|
|
2432
|
-
if (input2 === "j" || key.downArrow) {
|
|
2433
|
-
setSelectedIdx((i) => Math.min(i + 1, options.length - 1));
|
|
2434
|
-
}
|
|
2435
|
-
if (input2 === "k" || key.upArrow) {
|
|
2436
|
-
setSelectedIdx((i) => Math.max(i - 1, 0));
|
|
2437
|
-
}
|
|
2438
|
-
});
|
|
2439
|
-
return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
|
|
2440
|
-
/* @__PURE__ */ jsx10(Text10, { color: "cyan", bold: true, children: "Move to status:" }),
|
|
2441
|
-
options.map((opt, i) => {
|
|
2442
|
-
const isCurrent = opt.name === currentStatus;
|
|
2443
|
-
const isSelected = i === selectedIdx;
|
|
2444
|
-
const prefix = isSelected ? "> " : " ";
|
|
2445
|
-
const suffix = isCurrent ? " (current)" : "";
|
|
2446
|
-
return /* @__PURE__ */ jsxs10(
|
|
2447
|
-
Text10,
|
|
2448
|
-
{
|
|
2449
|
-
...isSelected ? { color: "cyan" } : {},
|
|
2450
|
-
dimColor: isCurrent,
|
|
2451
|
-
children: [
|
|
2452
|
-
prefix,
|
|
2453
|
-
opt.name,
|
|
2454
|
-
suffix
|
|
2455
|
-
]
|
|
2456
|
-
},
|
|
2457
|
-
opt.id
|
|
2458
|
-
);
|
|
2459
|
-
}),
|
|
2460
|
-
/* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "j/k:navigate Enter:select Esc:cancel" })
|
|
2461
|
-
] });
|
|
2462
|
-
}
|
|
2463
|
-
var init_status_picker = __esm({
|
|
2464
|
-
"src/board/components/status-picker.tsx"() {
|
|
2465
|
-
"use strict";
|
|
2466
|
-
}
|
|
2467
|
-
});
|
|
2468
|
-
|
|
2469
3500
|
// src/board/components/task-row.tsx
|
|
2470
|
-
import { Box as
|
|
2471
|
-
import { jsx as
|
|
3501
|
+
import { Box as Box13, Text as Text13 } from "ink";
|
|
3502
|
+
import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
2472
3503
|
function truncate2(s, max) {
|
|
2473
3504
|
return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
|
|
2474
3505
|
}
|
|
2475
|
-
function
|
|
3506
|
+
function formatDue2(dateStr) {
|
|
2476
3507
|
if (!dateStr) return { text: "", color: "gray" };
|
|
2477
3508
|
const d = new Date(dateStr);
|
|
2478
3509
|
const days = Math.ceil((d.getTime() - Date.now()) / 864e5);
|
|
@@ -2487,15 +3518,15 @@ function formatDue(dateStr) {
|
|
|
2487
3518
|
}
|
|
2488
3519
|
function TaskRow({ task: task2, isSelected }) {
|
|
2489
3520
|
const pri = PRIORITY_INDICATORS[task2.priority] ?? DEFAULT_PRIORITY;
|
|
2490
|
-
const due =
|
|
3521
|
+
const due = formatDue2(task2.dueDate);
|
|
2491
3522
|
const titleStr = truncate2(task2.title, 45).padEnd(45);
|
|
2492
|
-
return /* @__PURE__ */
|
|
2493
|
-
isSelected ? /* @__PURE__ */
|
|
2494
|
-
/* @__PURE__ */
|
|
2495
|
-
/* @__PURE__ */
|
|
2496
|
-
isSelected ? /* @__PURE__ */
|
|
2497
|
-
/* @__PURE__ */
|
|
2498
|
-
/* @__PURE__ */
|
|
3523
|
+
return /* @__PURE__ */ jsxs14(Box13, { children: [
|
|
3524
|
+
isSelected ? /* @__PURE__ */ jsx14(Text13, { color: "cyan", bold: true, children: "\u25B6 " }) : /* @__PURE__ */ jsx14(Text13, { children: " " }),
|
|
3525
|
+
/* @__PURE__ */ jsx14(Text13, { color: pri.color, children: pri.text }),
|
|
3526
|
+
/* @__PURE__ */ jsx14(Text13, { children: " " }),
|
|
3527
|
+
isSelected ? /* @__PURE__ */ jsx14(Text13, { color: "white", bold: true, children: titleStr }) : /* @__PURE__ */ jsx14(Text13, { children: titleStr }),
|
|
3528
|
+
/* @__PURE__ */ jsx14(Text13, { children: " " }),
|
|
3529
|
+
/* @__PURE__ */ jsx14(Text13, { color: due.color, children: due.text })
|
|
2499
3530
|
] });
|
|
2500
3531
|
}
|
|
2501
3532
|
var PRIORITY_INDICATORS, DEFAULT_PRIORITY;
|
|
@@ -2513,23 +3544,128 @@ var init_task_row = __esm({
|
|
|
2513
3544
|
}
|
|
2514
3545
|
});
|
|
2515
3546
|
|
|
3547
|
+
// src/board/components/row-renderer.tsx
|
|
3548
|
+
import { Box as Box14, Text as Text14 } from "ink";
|
|
3549
|
+
import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
3550
|
+
function RowRenderer({ row, selectedId, selfLogin, isMultiSelected }) {
|
|
3551
|
+
switch (row.type) {
|
|
3552
|
+
case "sectionHeader": {
|
|
3553
|
+
const arrow = row.isCollapsed ? "\u25B6" : "\u25BC";
|
|
3554
|
+
const isSel = selectedId === row.navId;
|
|
3555
|
+
return /* @__PURE__ */ jsxs15(Box14, { children: [
|
|
3556
|
+
/* @__PURE__ */ jsxs15(Text14, { color: isSel ? "cyan" : "white", bold: true, children: [
|
|
3557
|
+
arrow,
|
|
3558
|
+
" ",
|
|
3559
|
+
row.label
|
|
3560
|
+
] }),
|
|
3561
|
+
/* @__PURE__ */ jsxs15(Text14, { color: "gray", children: [
|
|
3562
|
+
" ",
|
|
3563
|
+
"(",
|
|
3564
|
+
row.count,
|
|
3565
|
+
" ",
|
|
3566
|
+
row.countLabel,
|
|
3567
|
+
")"
|
|
3568
|
+
] })
|
|
3569
|
+
] });
|
|
3570
|
+
}
|
|
3571
|
+
case "subHeader": {
|
|
3572
|
+
if (row.navId) {
|
|
3573
|
+
const arrow = row.isCollapsed ? "\u25B6" : "\u25BC";
|
|
3574
|
+
const isSel = selectedId === row.navId;
|
|
3575
|
+
return /* @__PURE__ */ jsxs15(Box14, { children: [
|
|
3576
|
+
/* @__PURE__ */ jsxs15(Text14, { color: isSel ? "cyan" : "gray", children: [
|
|
3577
|
+
" ",
|
|
3578
|
+
arrow,
|
|
3579
|
+
" ",
|
|
3580
|
+
row.text
|
|
3581
|
+
] }),
|
|
3582
|
+
/* @__PURE__ */ jsxs15(Text14, { color: "gray", children: [
|
|
3583
|
+
" (",
|
|
3584
|
+
row.count,
|
|
3585
|
+
")"
|
|
3586
|
+
] })
|
|
3587
|
+
] });
|
|
3588
|
+
}
|
|
3589
|
+
return /* @__PURE__ */ jsxs15(Text14, { color: "gray", children: [
|
|
3590
|
+
" ",
|
|
3591
|
+
row.text
|
|
3592
|
+
] });
|
|
3593
|
+
}
|
|
3594
|
+
case "issue": {
|
|
3595
|
+
const checkbox2 = isMultiSelected != null ? isMultiSelected ? "\u2611 " : "\u2610 " : "";
|
|
3596
|
+
return /* @__PURE__ */ jsxs15(Box14, { children: [
|
|
3597
|
+
checkbox2 ? /* @__PURE__ */ jsx15(Text14, { color: isMultiSelected ? "cyan" : "gray", children: checkbox2 }) : null,
|
|
3598
|
+
/* @__PURE__ */ jsx15(IssueRow, { issue: row.issue, selfLogin, isSelected: selectedId === row.navId })
|
|
3599
|
+
] });
|
|
3600
|
+
}
|
|
3601
|
+
case "task": {
|
|
3602
|
+
const checkbox2 = isMultiSelected != null ? isMultiSelected ? "\u2611 " : "\u2610 " : "";
|
|
3603
|
+
return /* @__PURE__ */ jsxs15(Box14, { children: [
|
|
3604
|
+
checkbox2 ? /* @__PURE__ */ jsx15(Text14, { color: isMultiSelected ? "cyan" : "gray", children: checkbox2 }) : null,
|
|
3605
|
+
/* @__PURE__ */ jsx15(TaskRow, { task: row.task, isSelected: selectedId === row.navId })
|
|
3606
|
+
] });
|
|
3607
|
+
}
|
|
3608
|
+
case "activity": {
|
|
3609
|
+
const ago = timeAgo2(row.event.timestamp);
|
|
3610
|
+
return /* @__PURE__ */ jsxs15(Text14, { dimColor: true, children: [
|
|
3611
|
+
" ",
|
|
3612
|
+
ago,
|
|
3613
|
+
": ",
|
|
3614
|
+
/* @__PURE__ */ jsxs15(Text14, { color: "gray", children: [
|
|
3615
|
+
"@",
|
|
3616
|
+
row.event.actor
|
|
3617
|
+
] }),
|
|
3618
|
+
" ",
|
|
3619
|
+
row.event.summary,
|
|
3620
|
+
" ",
|
|
3621
|
+
/* @__PURE__ */ jsxs15(Text14, { dimColor: true, children: [
|
|
3622
|
+
"(",
|
|
3623
|
+
row.event.repoShortName,
|
|
3624
|
+
")"
|
|
3625
|
+
] })
|
|
3626
|
+
] });
|
|
3627
|
+
}
|
|
3628
|
+
case "error":
|
|
3629
|
+
return /* @__PURE__ */ jsxs15(Text14, { color: "red", children: [
|
|
3630
|
+
" Error: ",
|
|
3631
|
+
row.text
|
|
3632
|
+
] });
|
|
3633
|
+
case "gap":
|
|
3634
|
+
return /* @__PURE__ */ jsx15(Text14, { children: "" });
|
|
3635
|
+
}
|
|
3636
|
+
}
|
|
3637
|
+
function timeAgo2(date) {
|
|
3638
|
+
const seconds = Math.floor((Date.now() - date.getTime()) / 1e3);
|
|
3639
|
+
if (seconds < 10) return "just now";
|
|
3640
|
+
if (seconds < 60) return `${seconds}s ago`;
|
|
3641
|
+
const minutes = Math.floor(seconds / 60);
|
|
3642
|
+
return `${minutes}m ago`;
|
|
3643
|
+
}
|
|
3644
|
+
var init_row_renderer = __esm({
|
|
3645
|
+
"src/board/components/row-renderer.tsx"() {
|
|
3646
|
+
"use strict";
|
|
3647
|
+
init_issue_row();
|
|
3648
|
+
init_task_row();
|
|
3649
|
+
}
|
|
3650
|
+
});
|
|
3651
|
+
|
|
2516
3652
|
// src/board/components/toast-container.tsx
|
|
2517
|
-
import { Spinner } from "@inkjs/ui";
|
|
2518
|
-
import { Box as
|
|
2519
|
-
import { Fragment as
|
|
3653
|
+
import { Spinner as Spinner3 } from "@inkjs/ui";
|
|
3654
|
+
import { Box as Box15, Text as Text15 } from "ink";
|
|
3655
|
+
import { Fragment as Fragment4, jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
2520
3656
|
function ToastContainer({ toasts }) {
|
|
2521
3657
|
if (toasts.length === 0) return null;
|
|
2522
|
-
return /* @__PURE__ */
|
|
2523
|
-
/* @__PURE__ */
|
|
2524
|
-
/* @__PURE__ */
|
|
3658
|
+
return /* @__PURE__ */ jsx16(Box15, { flexDirection: "column", children: toasts.map((t) => /* @__PURE__ */ jsx16(Box15, { children: t.type === "loading" ? /* @__PURE__ */ jsxs16(Fragment4, { children: [
|
|
3659
|
+
/* @__PURE__ */ jsx16(Spinner3, { label: "" }),
|
|
3660
|
+
/* @__PURE__ */ jsxs16(Text15, { color: "cyan", children: [
|
|
2525
3661
|
" ",
|
|
2526
3662
|
t.message
|
|
2527
3663
|
] })
|
|
2528
|
-
] }) : /* @__PURE__ */
|
|
3664
|
+
] }) : /* @__PURE__ */ jsxs16(Text15, { color: TYPE_COLORS[t.type], children: [
|
|
2529
3665
|
TYPE_PREFIXES[t.type],
|
|
2530
3666
|
" ",
|
|
2531
3667
|
t.message,
|
|
2532
|
-
t.type === "error" ? /* @__PURE__ */
|
|
3668
|
+
t.type === "error" ? /* @__PURE__ */ jsx16(Text15, { color: "gray", children: t.retry ? " [r]etry [d]ismiss" : " [d]ismiss" }) : null
|
|
2533
3669
|
] }) }, t.id)) });
|
|
2534
3670
|
}
|
|
2535
3671
|
var TYPE_COLORS, TYPE_PREFIXES;
|
|
@@ -2551,13 +3687,13 @@ var init_toast_container = __esm({
|
|
|
2551
3687
|
});
|
|
2552
3688
|
|
|
2553
3689
|
// src/board/components/dashboard.tsx
|
|
2554
|
-
import { execFileSync as execFileSync3 } from "child_process";
|
|
2555
|
-
import { Spinner as
|
|
2556
|
-
import { Box as
|
|
2557
|
-
import { useCallback as
|
|
2558
|
-
import { Fragment as
|
|
3690
|
+
import { execFileSync as execFileSync3, spawnSync as spawnSync2 } from "child_process";
|
|
3691
|
+
import { Spinner as Spinner4 } from "@inkjs/ui";
|
|
3692
|
+
import { Box as Box16, Text as Text16, useApp, useStdout } from "ink";
|
|
3693
|
+
import { useCallback as useCallback10, useEffect as useEffect6, useMemo as useMemo2, useRef as useRef11, useState as useState11 } from "react";
|
|
3694
|
+
import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
2559
3695
|
function isTerminalStatus(status) {
|
|
2560
|
-
return
|
|
3696
|
+
return TERMINAL_STATUS_RE3.test(status);
|
|
2561
3697
|
}
|
|
2562
3698
|
function resolveStatusGroups(statusOptions, configuredGroups) {
|
|
2563
3699
|
if (configuredGroups && configuredGroups.length > 0) {
|
|
@@ -2780,7 +3916,7 @@ function buildFlatRows(repos, tasks, activity, isCollapsed) {
|
|
|
2780
3916
|
}
|
|
2781
3917
|
return rows;
|
|
2782
3918
|
}
|
|
2783
|
-
function
|
|
3919
|
+
function timeAgo3(date) {
|
|
2784
3920
|
const seconds = Math.floor((Date.now() - date.getTime()) / 1e3);
|
|
2785
3921
|
if (seconds < 10) return "just now";
|
|
2786
3922
|
if (seconds < 60) return `${seconds}s ago`;
|
|
@@ -2807,100 +3943,13 @@ function findSelectedIssueWithRepo(repos, selectedId) {
|
|
|
2807
3943
|
for (const rd of repos) {
|
|
2808
3944
|
for (const issue of rd.issues) {
|
|
2809
3945
|
if (`gh:${rd.repo.name}:${issue.number}` === selectedId)
|
|
2810
|
-
return { issue, repoName: rd.repo.name };
|
|
2811
|
-
}
|
|
2812
|
-
}
|
|
2813
|
-
return null;
|
|
2814
|
-
}
|
|
2815
|
-
function isHeaderId(id) {
|
|
2816
|
-
return id != null && (id.startsWith("header:") || id.startsWith("sub:"));
|
|
2817
|
-
}
|
|
2818
|
-
function RowRenderer({ row, selectedId, selfLogin, isMultiSelected }) {
|
|
2819
|
-
switch (row.type) {
|
|
2820
|
-
case "sectionHeader": {
|
|
2821
|
-
const arrow = row.isCollapsed ? "\u25B6" : "\u25BC";
|
|
2822
|
-
const isSel = selectedId === row.navId;
|
|
2823
|
-
return /* @__PURE__ */ jsxs13(Box13, { children: [
|
|
2824
|
-
/* @__PURE__ */ jsxs13(Text13, { color: isSel ? "cyan" : "white", bold: true, children: [
|
|
2825
|
-
arrow,
|
|
2826
|
-
" ",
|
|
2827
|
-
row.label
|
|
2828
|
-
] }),
|
|
2829
|
-
/* @__PURE__ */ jsxs13(Text13, { color: "gray", children: [
|
|
2830
|
-
" ",
|
|
2831
|
-
"(",
|
|
2832
|
-
row.count,
|
|
2833
|
-
" ",
|
|
2834
|
-
row.countLabel,
|
|
2835
|
-
")"
|
|
2836
|
-
] })
|
|
2837
|
-
] });
|
|
2838
|
-
}
|
|
2839
|
-
case "subHeader": {
|
|
2840
|
-
if (row.navId) {
|
|
2841
|
-
const arrow = row.isCollapsed ? "\u25B6" : "\u25BC";
|
|
2842
|
-
const isSel = selectedId === row.navId;
|
|
2843
|
-
return /* @__PURE__ */ jsxs13(Box13, { children: [
|
|
2844
|
-
/* @__PURE__ */ jsxs13(Text13, { color: isSel ? "cyan" : "gray", children: [
|
|
2845
|
-
" ",
|
|
2846
|
-
arrow,
|
|
2847
|
-
" ",
|
|
2848
|
-
row.text
|
|
2849
|
-
] }),
|
|
2850
|
-
/* @__PURE__ */ jsxs13(Text13, { color: "gray", children: [
|
|
2851
|
-
" (",
|
|
2852
|
-
row.count,
|
|
2853
|
-
")"
|
|
2854
|
-
] })
|
|
2855
|
-
] });
|
|
2856
|
-
}
|
|
2857
|
-
return /* @__PURE__ */ jsxs13(Text13, { color: "gray", children: [
|
|
2858
|
-
" ",
|
|
2859
|
-
row.text
|
|
2860
|
-
] });
|
|
2861
|
-
}
|
|
2862
|
-
case "issue": {
|
|
2863
|
-
const checkbox2 = isMultiSelected != null ? isMultiSelected ? "\u2611 " : "\u2610 " : "";
|
|
2864
|
-
return /* @__PURE__ */ jsxs13(Box13, { children: [
|
|
2865
|
-
checkbox2 ? /* @__PURE__ */ jsx13(Text13, { color: isMultiSelected ? "cyan" : "gray", children: checkbox2 }) : null,
|
|
2866
|
-
/* @__PURE__ */ jsx13(IssueRow, { issue: row.issue, selfLogin, isSelected: selectedId === row.navId })
|
|
2867
|
-
] });
|
|
2868
|
-
}
|
|
2869
|
-
case "task": {
|
|
2870
|
-
const checkbox2 = isMultiSelected != null ? isMultiSelected ? "\u2611 " : "\u2610 " : "";
|
|
2871
|
-
return /* @__PURE__ */ jsxs13(Box13, { children: [
|
|
2872
|
-
checkbox2 ? /* @__PURE__ */ jsx13(Text13, { color: isMultiSelected ? "cyan" : "gray", children: checkbox2 }) : null,
|
|
2873
|
-
/* @__PURE__ */ jsx13(TaskRow, { task: row.task, isSelected: selectedId === row.navId })
|
|
2874
|
-
] });
|
|
2875
|
-
}
|
|
2876
|
-
case "activity": {
|
|
2877
|
-
const ago = timeAgo2(row.event.timestamp);
|
|
2878
|
-
return /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
|
|
2879
|
-
" ",
|
|
2880
|
-
ago,
|
|
2881
|
-
": ",
|
|
2882
|
-
/* @__PURE__ */ jsxs13(Text13, { color: "gray", children: [
|
|
2883
|
-
"@",
|
|
2884
|
-
row.event.actor
|
|
2885
|
-
] }),
|
|
2886
|
-
" ",
|
|
2887
|
-
row.event.summary,
|
|
2888
|
-
" ",
|
|
2889
|
-
/* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
|
|
2890
|
-
"(",
|
|
2891
|
-
row.event.repoShortName,
|
|
2892
|
-
")"
|
|
2893
|
-
] })
|
|
2894
|
-
] });
|
|
2895
|
-
}
|
|
2896
|
-
case "error":
|
|
2897
|
-
return /* @__PURE__ */ jsxs13(Text13, { color: "red", children: [
|
|
2898
|
-
" Error: ",
|
|
2899
|
-
row.text
|
|
2900
|
-
] });
|
|
2901
|
-
case "gap":
|
|
2902
|
-
return /* @__PURE__ */ jsx13(Text13, { children: "" });
|
|
3946
|
+
return { issue, repoName: rd.repo.name };
|
|
3947
|
+
}
|
|
2903
3948
|
}
|
|
3949
|
+
return null;
|
|
3950
|
+
}
|
|
3951
|
+
function isHeaderId2(id) {
|
|
3952
|
+
return id != null && (id.startsWith("header:") || id.startsWith("sub:"));
|
|
2904
3953
|
}
|
|
2905
3954
|
function Dashboard({ config: config2, options, activeProfile }) {
|
|
2906
3955
|
const { exit } = useApp();
|
|
@@ -2914,7 +3963,9 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
2914
3963
|
consecutiveFailures,
|
|
2915
3964
|
autoRefreshPaused,
|
|
2916
3965
|
refresh,
|
|
2917
|
-
mutateData
|
|
3966
|
+
mutateData,
|
|
3967
|
+
pauseAutoRefresh,
|
|
3968
|
+
resumeAutoRefresh
|
|
2918
3969
|
} = useData(config2, options, refreshMs);
|
|
2919
3970
|
const allRepos = useMemo2(() => data?.repos ?? [], [data?.repos]);
|
|
2920
3971
|
const allTasks = useMemo2(
|
|
@@ -2923,10 +3974,10 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
2923
3974
|
);
|
|
2924
3975
|
const allActivity = useMemo2(() => data?.activity ?? [], [data?.activity]);
|
|
2925
3976
|
const ui = useUIState();
|
|
2926
|
-
const [searchQuery, setSearchQuery] =
|
|
3977
|
+
const [searchQuery, setSearchQuery] = useState11("");
|
|
2927
3978
|
const { toasts, toast, handleErrorAction } = useToast();
|
|
2928
|
-
const [, setTick] =
|
|
2929
|
-
|
|
3979
|
+
const [, setTick] = useState11(0);
|
|
3980
|
+
useEffect6(() => {
|
|
2930
3981
|
const id = setInterval(() => setTick((t) => t + 1), 1e4);
|
|
2931
3982
|
return () => clearInterval(id);
|
|
2932
3983
|
}, []);
|
|
@@ -2945,7 +3996,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
2945
3996
|
[repos, tasks, allActivity.length]
|
|
2946
3997
|
);
|
|
2947
3998
|
const nav = useNavigation(navItems);
|
|
2948
|
-
const getRepoForId =
|
|
3999
|
+
const getRepoForId = useCallback10((id) => {
|
|
2949
4000
|
if (id.startsWith("gh:")) {
|
|
2950
4001
|
const parts = id.split(":");
|
|
2951
4002
|
return parts.length >= 3 ? `${parts[1]}` : null;
|
|
@@ -2954,7 +4005,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
2954
4005
|
return null;
|
|
2955
4006
|
}, []);
|
|
2956
4007
|
const multiSelect = useMultiSelect(getRepoForId);
|
|
2957
|
-
|
|
4008
|
+
useEffect6(() => {
|
|
2958
4009
|
if (multiSelect.count === 0) return;
|
|
2959
4010
|
const validIds = new Set(navItems.map((i) => i.id));
|
|
2960
4011
|
multiSelect.prune(validIds);
|
|
@@ -2968,8 +4019,9 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
2968
4019
|
mutateData,
|
|
2969
4020
|
onOverlayDone: ui.exitOverlay
|
|
2970
4021
|
});
|
|
2971
|
-
const pendingPickRef =
|
|
2972
|
-
const
|
|
4022
|
+
const pendingPickRef = useRef11(null);
|
|
4023
|
+
const labelCacheRef = useRef11({});
|
|
4024
|
+
const handleCreateIssueWithPrompt = useCallback10(
|
|
2973
4025
|
(repo, title, labels) => {
|
|
2974
4026
|
actions.handleCreateIssue(repo, title, labels).then((result) => {
|
|
2975
4027
|
if (result) {
|
|
@@ -2980,7 +4032,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
2980
4032
|
},
|
|
2981
4033
|
[actions, ui]
|
|
2982
4034
|
);
|
|
2983
|
-
const handleConfirmPick =
|
|
4035
|
+
const handleConfirmPick = useCallback10(() => {
|
|
2984
4036
|
const pending = pendingPickRef.current;
|
|
2985
4037
|
pendingPickRef.current = null;
|
|
2986
4038
|
ui.exitOverlay();
|
|
@@ -2998,14 +4050,14 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
2998
4050
|
})
|
|
2999
4051
|
);
|
|
3000
4052
|
}, [config2, toast, refresh, ui]);
|
|
3001
|
-
const handleCancelPick =
|
|
4053
|
+
const handleCancelPick = useCallback10(() => {
|
|
3002
4054
|
pendingPickRef.current = null;
|
|
3003
4055
|
ui.exitOverlay();
|
|
3004
4056
|
}, [ui]);
|
|
3005
|
-
const [focusLabel, setFocusLabel] =
|
|
3006
|
-
const handleEnterFocus =
|
|
4057
|
+
const [focusLabel, setFocusLabel] = useState11(null);
|
|
4058
|
+
const handleEnterFocus = useCallback10(() => {
|
|
3007
4059
|
const id = nav.selectedId;
|
|
3008
|
-
if (!id ||
|
|
4060
|
+
if (!id || isHeaderId2(id)) return;
|
|
3009
4061
|
let label = "";
|
|
3010
4062
|
if (id.startsWith("gh:")) {
|
|
3011
4063
|
const found = findSelectedIssueWithRepo(repos, id);
|
|
@@ -3022,11 +4074,11 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3022
4074
|
setFocusLabel(label);
|
|
3023
4075
|
ui.enterFocus();
|
|
3024
4076
|
}, [nav.selectedId, repos, tasks, config2.repos, ui]);
|
|
3025
|
-
const handleFocusExit =
|
|
4077
|
+
const handleFocusExit = useCallback10(() => {
|
|
3026
4078
|
setFocusLabel(null);
|
|
3027
4079
|
ui.exitToNormal();
|
|
3028
4080
|
}, [ui]);
|
|
3029
|
-
const handleFocusEndAction =
|
|
4081
|
+
const handleFocusEndAction = useCallback10(
|
|
3030
4082
|
(action) => {
|
|
3031
4083
|
switch (action) {
|
|
3032
4084
|
case "restart":
|
|
@@ -3052,13 +4104,13 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3052
4104
|
},
|
|
3053
4105
|
[toast, ui]
|
|
3054
4106
|
);
|
|
3055
|
-
const [focusKey, setFocusKey] =
|
|
4107
|
+
const [focusKey, setFocusKey] = useState11(0);
|
|
3056
4108
|
const { stdout } = useStdout();
|
|
3057
|
-
const [termSize, setTermSize] =
|
|
4109
|
+
const [termSize, setTermSize] = useState11({
|
|
3058
4110
|
cols: stdout?.columns ?? 80,
|
|
3059
4111
|
rows: stdout?.rows ?? 24
|
|
3060
4112
|
});
|
|
3061
|
-
|
|
4113
|
+
useEffect6(() => {
|
|
3062
4114
|
if (!stdout) return;
|
|
3063
4115
|
const onResize = () => setTermSize({ cols: stdout.columns, rows: stdout.rows });
|
|
3064
4116
|
stdout.on("resize", onResize);
|
|
@@ -3075,7 +4127,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3075
4127
|
() => buildFlatRows(repos, tasks, allActivity, nav.isCollapsed),
|
|
3076
4128
|
[repos, tasks, allActivity, nav.isCollapsed]
|
|
3077
4129
|
);
|
|
3078
|
-
const scrollRef =
|
|
4130
|
+
const scrollRef = useRef11(0);
|
|
3079
4131
|
const selectedRowIdx = flatRows.findIndex((r) => r.navId === nav.selectedId);
|
|
3080
4132
|
if (selectedRowIdx >= 0) {
|
|
3081
4133
|
if (selectedRowIdx < scrollRef.current) {
|
|
@@ -3093,7 +4145,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3093
4145
|
const belowCount = flatRows.length - scrollRef.current - viewportHeight;
|
|
3094
4146
|
const selectedItem = useMemo2(() => {
|
|
3095
4147
|
const id = nav.selectedId;
|
|
3096
|
-
if (!id ||
|
|
4148
|
+
if (!id || isHeaderId2(id)) return { issue: null, task: null, repoName: null };
|
|
3097
4149
|
if (id.startsWith("gh:")) {
|
|
3098
4150
|
for (const rd of repos) {
|
|
3099
4151
|
for (const issue of rd.issues) {
|
|
@@ -3113,17 +4165,42 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3113
4165
|
const repoName = multiSelect.count > 0 ? multiSelect.constrainedRepo : selectedItem.repoName;
|
|
3114
4166
|
if (!repoName || repoName === "ticktick") return [];
|
|
3115
4167
|
const rd = repos.find((r) => r.repo.name === repoName);
|
|
3116
|
-
return rd?.statusOptions
|
|
4168
|
+
return rd?.statusOptions ?? [];
|
|
3117
4169
|
}, [selectedItem.repoName, repos, multiSelect.count, multiSelect.constrainedRepo]);
|
|
3118
|
-
const handleOpen =
|
|
4170
|
+
const handleOpen = useCallback10(() => {
|
|
3119
4171
|
const url = findSelectedUrl(repos, nav.selectedId);
|
|
3120
4172
|
if (url) openInBrowser(url);
|
|
3121
4173
|
}, [repos, nav.selectedId]);
|
|
3122
|
-
const handleSlack =
|
|
4174
|
+
const handleSlack = useCallback10(() => {
|
|
3123
4175
|
const found = findSelectedIssueWithRepo(repos, nav.selectedId);
|
|
3124
4176
|
if (!found?.issue.slackThreadUrl) return;
|
|
3125
4177
|
openInBrowser(found.issue.slackThreadUrl);
|
|
3126
4178
|
}, [repos, nav.selectedId]);
|
|
4179
|
+
const handleCopyLink = useCallback10(() => {
|
|
4180
|
+
const found = findSelectedIssueWithRepo(repos, nav.selectedId);
|
|
4181
|
+
if (!found) return;
|
|
4182
|
+
const rc = config2.repos.find((r) => r.name === found.repoName);
|
|
4183
|
+
const label = `${rc?.shortName ?? found.repoName}#${found.issue.number}`;
|
|
4184
|
+
const clipArgs = getClipboardArgs();
|
|
4185
|
+
if (clipArgs) {
|
|
4186
|
+
const [cmd, ...args] = clipArgs;
|
|
4187
|
+
if (!cmd) {
|
|
4188
|
+
toast.info(`${label} \u2014 ${found.issue.url}`);
|
|
4189
|
+
return;
|
|
4190
|
+
}
|
|
4191
|
+
const result = spawnSync2(cmd, args, {
|
|
4192
|
+
input: found.issue.url,
|
|
4193
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4194
|
+
});
|
|
4195
|
+
if (result.status === 0) {
|
|
4196
|
+
toast.success(`Copied ${label} to clipboard`);
|
|
4197
|
+
} else {
|
|
4198
|
+
toast.info(`${label} \u2014 ${found.issue.url}`);
|
|
4199
|
+
}
|
|
4200
|
+
} else {
|
|
4201
|
+
toast.info(`${label} \u2014 ${found.issue.url}`);
|
|
4202
|
+
}
|
|
4203
|
+
}, [repos, nav.selectedId, config2.repos, toast]);
|
|
3127
4204
|
const multiSelectType = useMemo2(() => {
|
|
3128
4205
|
let hasGh = false;
|
|
3129
4206
|
let hasTt = false;
|
|
@@ -3135,7 +4212,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3135
4212
|
if (hasTt) return "ticktick";
|
|
3136
4213
|
return "github";
|
|
3137
4214
|
}, [multiSelect.selected]);
|
|
3138
|
-
const handleBulkAction =
|
|
4215
|
+
const handleBulkAction = useCallback10(
|
|
3139
4216
|
(action) => {
|
|
3140
4217
|
const ids = multiSelect.selected;
|
|
3141
4218
|
switch (action.type) {
|
|
@@ -3179,7 +4256,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3179
4256
|
},
|
|
3180
4257
|
[multiSelect, actions, ui, toast]
|
|
3181
4258
|
);
|
|
3182
|
-
const handleBulkStatusSelect =
|
|
4259
|
+
const handleBulkStatusSelect = useCallback10(
|
|
3183
4260
|
(optionId) => {
|
|
3184
4261
|
const ids = multiSelect.selected;
|
|
3185
4262
|
ui.exitOverlay();
|
|
@@ -3195,168 +4272,35 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3195
4272
|
},
|
|
3196
4273
|
[multiSelect, actions, ui]
|
|
3197
4274
|
);
|
|
3198
|
-
const
|
|
3199
|
-
(
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
return;
|
|
3210
|
-
}
|
|
3211
|
-
if (ui.canNavigate) {
|
|
3212
|
-
if (input2 === "j" || key.downArrow) {
|
|
3213
|
-
nav.moveDown();
|
|
3214
|
-
return;
|
|
3215
|
-
}
|
|
3216
|
-
if (input2 === "k" || key.upArrow) {
|
|
3217
|
-
nav.moveUp();
|
|
3218
|
-
return;
|
|
3219
|
-
}
|
|
3220
|
-
if (key.tab) {
|
|
3221
|
-
if (ui.state.mode === "multiSelect") {
|
|
3222
|
-
multiSelect.clear();
|
|
3223
|
-
ui.clearMultiSelect();
|
|
3224
|
-
}
|
|
3225
|
-
key.shift ? nav.prevSection() : nav.nextSection();
|
|
3226
|
-
return;
|
|
3227
|
-
}
|
|
3228
|
-
}
|
|
3229
|
-
if (ui.state.mode === "multiSelect") {
|
|
3230
|
-
if (input2 === " ") {
|
|
3231
|
-
const id = nav.selectedId;
|
|
3232
|
-
if (id && !isHeaderId(id)) {
|
|
3233
|
-
multiSelect.toggle(id);
|
|
3234
|
-
}
|
|
3235
|
-
return;
|
|
3236
|
-
}
|
|
3237
|
-
if (key.return) {
|
|
3238
|
-
if (multiSelect.count > 0) {
|
|
3239
|
-
ui.enterBulkAction();
|
|
3240
|
-
}
|
|
3241
|
-
return;
|
|
3242
|
-
}
|
|
3243
|
-
if (input2 === "m" && multiSelect.count > 0) {
|
|
3244
|
-
ui.enterBulkAction();
|
|
3245
|
-
return;
|
|
3246
|
-
}
|
|
3247
|
-
return;
|
|
3248
|
-
}
|
|
3249
|
-
if (input2 === "d") {
|
|
3250
|
-
if (handleErrorAction("dismiss")) return;
|
|
3251
|
-
}
|
|
3252
|
-
if (input2 === "r" && handleErrorAction("retry")) return;
|
|
3253
|
-
if (ui.canAct) {
|
|
3254
|
-
if (input2 === "/") {
|
|
3255
|
-
multiSelect.clear();
|
|
3256
|
-
ui.enterSearch();
|
|
3257
|
-
return;
|
|
3258
|
-
}
|
|
3259
|
-
if (input2 === "q") {
|
|
3260
|
-
exit();
|
|
3261
|
-
return;
|
|
3262
|
-
}
|
|
3263
|
-
if (input2 === "r" || input2 === "R") {
|
|
3264
|
-
multiSelect.clear();
|
|
3265
|
-
refresh();
|
|
3266
|
-
return;
|
|
3267
|
-
}
|
|
3268
|
-
if (input2 === "s") {
|
|
3269
|
-
handleSlack();
|
|
3270
|
-
return;
|
|
3271
|
-
}
|
|
3272
|
-
if (input2 === "p") {
|
|
3273
|
-
actions.handlePick();
|
|
3274
|
-
return;
|
|
3275
|
-
}
|
|
3276
|
-
if (input2 === "a") {
|
|
3277
|
-
actions.handleAssign();
|
|
3278
|
-
return;
|
|
3279
|
-
}
|
|
3280
|
-
if (input2 === "u") {
|
|
3281
|
-
actions.handleUnassign();
|
|
3282
|
-
return;
|
|
3283
|
-
}
|
|
3284
|
-
if (input2 === "c") {
|
|
3285
|
-
if (selectedItem.issue) {
|
|
3286
|
-
multiSelect.clear();
|
|
3287
|
-
ui.enterComment();
|
|
3288
|
-
}
|
|
3289
|
-
return;
|
|
3290
|
-
}
|
|
3291
|
-
if (input2 === "m") {
|
|
3292
|
-
if (selectedItem.issue && selectedRepoStatusOptions.length > 0) {
|
|
3293
|
-
multiSelect.clear();
|
|
3294
|
-
ui.enterStatus();
|
|
3295
|
-
} else if (selectedItem.issue) {
|
|
3296
|
-
toast.info("Issue not in a project board");
|
|
3297
|
-
}
|
|
3298
|
-
return;
|
|
3299
|
-
}
|
|
3300
|
-
if (input2 === "n") {
|
|
3301
|
-
multiSelect.clear();
|
|
3302
|
-
ui.enterCreate();
|
|
3303
|
-
return;
|
|
3304
|
-
}
|
|
3305
|
-
if (input2 === "f") {
|
|
3306
|
-
handleEnterFocus();
|
|
3307
|
-
return;
|
|
3308
|
-
}
|
|
3309
|
-
if (input2 === " ") {
|
|
3310
|
-
const id = nav.selectedId;
|
|
3311
|
-
if (id && !isHeaderId(id)) {
|
|
3312
|
-
multiSelect.toggle(id);
|
|
3313
|
-
ui.enterMultiSelect();
|
|
3314
|
-
} else if (isHeaderId(nav.selectedId)) {
|
|
3315
|
-
nav.toggleSection();
|
|
3316
|
-
}
|
|
3317
|
-
return;
|
|
3318
|
-
}
|
|
3319
|
-
if (key.return) {
|
|
3320
|
-
if (isHeaderId(nav.selectedId)) {
|
|
3321
|
-
nav.toggleSection();
|
|
3322
|
-
return;
|
|
3323
|
-
}
|
|
3324
|
-
handleOpen();
|
|
3325
|
-
return;
|
|
3326
|
-
}
|
|
3327
|
-
}
|
|
3328
|
-
},
|
|
3329
|
-
[
|
|
3330
|
-
ui,
|
|
3331
|
-
nav,
|
|
4275
|
+
const onSearchEscape = useCallback10(() => {
|
|
4276
|
+
ui.exitOverlay();
|
|
4277
|
+
setSearchQuery("");
|
|
4278
|
+
}, [ui]);
|
|
4279
|
+
useKeyboard({
|
|
4280
|
+
ui,
|
|
4281
|
+
nav,
|
|
4282
|
+
multiSelect,
|
|
4283
|
+
selectedIssue: selectedItem.issue,
|
|
4284
|
+
selectedRepoStatusOptionsLength: selectedRepoStatusOptions.length,
|
|
4285
|
+
actions: {
|
|
3332
4286
|
exit,
|
|
3333
4287
|
refresh,
|
|
3334
4288
|
handleSlack,
|
|
4289
|
+
handleCopyLink,
|
|
3335
4290
|
handleOpen,
|
|
3336
|
-
actions,
|
|
3337
|
-
selectedItem.issue,
|
|
3338
|
-
selectedRepoStatusOptions.length,
|
|
3339
|
-
toast,
|
|
3340
|
-
nav.selectedId,
|
|
3341
|
-
multiSelect,
|
|
3342
4291
|
handleEnterFocus,
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
if (key.escape) {
|
|
3351
|
-
ui.exitOverlay();
|
|
3352
|
-
setSearchQuery("");
|
|
3353
|
-
}
|
|
4292
|
+
handlePick: actions.handlePick,
|
|
4293
|
+
handleAssign: actions.handleAssign,
|
|
4294
|
+
handleUnassign: actions.handleUnassign,
|
|
4295
|
+
handleEnterLabel: ui.enterLabel,
|
|
4296
|
+
handleEnterCreateNl: ui.enterCreateNl,
|
|
4297
|
+
handleErrorAction,
|
|
4298
|
+
toastInfo: toast.info
|
|
3354
4299
|
},
|
|
3355
|
-
|
|
3356
|
-
);
|
|
3357
|
-
useInput8(handleSearchEscape, { isActive: ui.state.mode === "search" });
|
|
4300
|
+
onSearchEscape
|
|
4301
|
+
});
|
|
3358
4302
|
if (status === "loading" && !data) {
|
|
3359
|
-
return /* @__PURE__ */
|
|
4303
|
+
return /* @__PURE__ */ jsx17(Box16, { flexDirection: "column", padding: 1, children: /* @__PURE__ */ jsx17(Spinner4, { label: "Loading dashboard..." }) });
|
|
3360
4304
|
}
|
|
3361
4305
|
const now = data?.fetchedAt ?? /* @__PURE__ */ new Date();
|
|
3362
4306
|
const dateStr = now.toLocaleDateString("en-US", {
|
|
@@ -3364,93 +4308,81 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3364
4308
|
day: "numeric",
|
|
3365
4309
|
year: "numeric"
|
|
3366
4310
|
});
|
|
3367
|
-
return /* @__PURE__ */
|
|
3368
|
-
/* @__PURE__ */
|
|
3369
|
-
/* @__PURE__ */
|
|
3370
|
-
activeProfile ? /* @__PURE__ */
|
|
4311
|
+
return /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", paddingX: 1, children: [
|
|
4312
|
+
/* @__PURE__ */ jsxs17(Box16, { children: [
|
|
4313
|
+
/* @__PURE__ */ jsx17(Text16, { color: "cyan", bold: true, children: "HOG BOARD" }),
|
|
4314
|
+
activeProfile ? /* @__PURE__ */ jsxs17(Text16, { color: "yellow", children: [
|
|
3371
4315
|
" [",
|
|
3372
4316
|
activeProfile,
|
|
3373
4317
|
"]"
|
|
3374
4318
|
] }) : null,
|
|
3375
|
-
/* @__PURE__ */
|
|
4319
|
+
/* @__PURE__ */ jsxs17(Text16, { color: "gray", children: [
|
|
3376
4320
|
" ",
|
|
3377
4321
|
"\u2014",
|
|
3378
4322
|
" ",
|
|
3379
4323
|
dateStr
|
|
3380
4324
|
] }),
|
|
3381
|
-
/* @__PURE__ */
|
|
3382
|
-
isRefreshing ? /* @__PURE__ */
|
|
3383
|
-
/* @__PURE__ */
|
|
3384
|
-
/* @__PURE__ */
|
|
3385
|
-
] }) : lastRefresh ? /* @__PURE__ */
|
|
3386
|
-
/* @__PURE__ */
|
|
4325
|
+
/* @__PURE__ */ jsx17(Text16, { children: " " }),
|
|
4326
|
+
isRefreshing ? /* @__PURE__ */ jsxs17(Fragment5, { children: [
|
|
4327
|
+
/* @__PURE__ */ jsx17(Spinner4, { label: "" }),
|
|
4328
|
+
/* @__PURE__ */ jsx17(Text16, { color: "cyan", children: " Refreshing..." })
|
|
4329
|
+
] }) : lastRefresh ? /* @__PURE__ */ jsxs17(Fragment5, { children: [
|
|
4330
|
+
/* @__PURE__ */ jsxs17(Text16, { color: refreshAgeColor(lastRefresh), children: [
|
|
3387
4331
|
"Updated ",
|
|
3388
|
-
|
|
4332
|
+
timeAgo3(lastRefresh)
|
|
3389
4333
|
] }),
|
|
3390
|
-
consecutiveFailures > 0 ? /* @__PURE__ */
|
|
4334
|
+
consecutiveFailures > 0 ? /* @__PURE__ */ jsx17(Text16, { color: "red", children: " (!)" }) : null
|
|
3391
4335
|
] }) : null,
|
|
3392
|
-
autoRefreshPaused ? /* @__PURE__ */
|
|
4336
|
+
autoRefreshPaused ? /* @__PURE__ */ jsx17(Text16, { color: "yellow", children: " Auto-refresh paused \u2014 press r to retry" }) : null
|
|
3393
4337
|
] }),
|
|
3394
|
-
error ? /* @__PURE__ */
|
|
4338
|
+
error ? /* @__PURE__ */ jsxs17(Text16, { color: "red", children: [
|
|
3395
4339
|
"Error: ",
|
|
3396
4340
|
error
|
|
3397
4341
|
] }) : null,
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
StatusPicker,
|
|
4342
|
+
/* @__PURE__ */ jsx17(
|
|
4343
|
+
OverlayRenderer,
|
|
3401
4344
|
{
|
|
3402
|
-
|
|
4345
|
+
uiState: ui.state,
|
|
4346
|
+
config: config2,
|
|
4347
|
+
selectedRepoStatusOptions,
|
|
3403
4348
|
currentStatus: multiSelect.count > 0 ? void 0 : selectedItem.issue?.projectStatus,
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
}
|
|
3407
|
-
) : null,
|
|
3408
|
-
ui.state.mode === "overlay:create" ? /* @__PURE__ */ jsx13(
|
|
3409
|
-
CreateIssueForm,
|
|
3410
|
-
{
|
|
3411
|
-
repos: config2.repos,
|
|
4349
|
+
onStatusSelect: multiSelect.count > 0 ? handleBulkStatusSelect : actions.handleStatusChange,
|
|
4350
|
+
onExitOverlay: ui.exitOverlay,
|
|
3412
4351
|
defaultRepo: selectedItem.repoName,
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
4352
|
+
onCreateIssue: handleCreateIssueWithPrompt,
|
|
4353
|
+
onConfirmPick: handleConfirmPick,
|
|
4354
|
+
onCancelPick: handleCancelPick,
|
|
4355
|
+
multiSelectCount: multiSelect.count,
|
|
4356
|
+
multiSelectType,
|
|
4357
|
+
onBulkAction: handleBulkAction,
|
|
4358
|
+
focusLabel,
|
|
4359
|
+
focusKey,
|
|
4360
|
+
onFocusExit: handleFocusExit,
|
|
4361
|
+
onFocusEndAction: handleFocusEndAction,
|
|
4362
|
+
searchQuery,
|
|
4363
|
+
onSearchChange: setSearchQuery,
|
|
4364
|
+
onSearchSubmit: ui.exitOverlay,
|
|
4365
|
+
selectedIssue: selectedItem.issue,
|
|
4366
|
+
onComment: actions.handleComment,
|
|
4367
|
+
onPauseRefresh: pauseAutoRefresh,
|
|
4368
|
+
onResumeRefresh: resumeAutoRefresh,
|
|
4369
|
+
onToggleHelp: ui.toggleHelp,
|
|
4370
|
+
labelCache: labelCacheRef.current,
|
|
4371
|
+
onLabelConfirm: actions.handleLabelChange,
|
|
4372
|
+
onLabelError: (msg) => toast.error(msg),
|
|
4373
|
+
onLlmFallback: (msg) => toast.info(msg)
|
|
3432
4374
|
}
|
|
3433
|
-
)
|
|
3434
|
-
ui.state.mode
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
label: focusLabel,
|
|
3438
|
-
durationSec: config2.board.focusDuration ?? 1500,
|
|
3439
|
-
onExit: handleFocusExit,
|
|
3440
|
-
onEndAction: handleFocusEndAction
|
|
3441
|
-
},
|
|
3442
|
-
focusKey
|
|
3443
|
-
) : null,
|
|
3444
|
-
!ui.state.helpVisible && ui.state.mode !== "overlay:status" && ui.state.mode !== "overlay:create" && ui.state.mode !== "overlay:bulkAction" && ui.state.mode !== "overlay:confirmPick" && ui.state.mode !== "focus" ? /* @__PURE__ */ jsxs13(Box13, { height: viewportHeight, children: [
|
|
3445
|
-
/* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", flexGrow: 1, children: [
|
|
3446
|
-
hasMoreAbove ? /* @__PURE__ */ jsxs13(Text13, { color: "gray", dimColor: true, children: [
|
|
4375
|
+
),
|
|
4376
|
+
!ui.state.helpVisible && ui.state.mode !== "overlay:status" && ui.state.mode !== "overlay:create" && ui.state.mode !== "overlay:createNl" && ui.state.mode !== "overlay:bulkAction" && ui.state.mode !== "overlay:confirmPick" && ui.state.mode !== "focus" ? /* @__PURE__ */ jsxs17(Box16, { height: viewportHeight, children: [
|
|
4377
|
+
/* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", flexGrow: 1, children: [
|
|
4378
|
+
hasMoreAbove ? /* @__PURE__ */ jsxs17(Text16, { color: "gray", dimColor: true, children: [
|
|
3447
4379
|
" ",
|
|
3448
4380
|
"\u25B2",
|
|
3449
4381
|
" ",
|
|
3450
4382
|
aboveCount,
|
|
3451
4383
|
" more above"
|
|
3452
4384
|
] }) : null,
|
|
3453
|
-
visibleRows.map((row) => /* @__PURE__ */
|
|
4385
|
+
visibleRows.map((row) => /* @__PURE__ */ jsx17(
|
|
3454
4386
|
RowRenderer,
|
|
3455
4387
|
{
|
|
3456
4388
|
row,
|
|
@@ -3460,7 +4392,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3460
4392
|
},
|
|
3461
4393
|
row.key
|
|
3462
4394
|
)),
|
|
3463
|
-
hasMoreBelow ? /* @__PURE__ */
|
|
4395
|
+
hasMoreBelow ? /* @__PURE__ */ jsxs17(Text16, { color: "gray", dimColor: true, children: [
|
|
3464
4396
|
" ",
|
|
3465
4397
|
"\u25BC",
|
|
3466
4398
|
" ",
|
|
@@ -3468,7 +4400,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3468
4400
|
" more below"
|
|
3469
4401
|
] }) : null
|
|
3470
4402
|
] }),
|
|
3471
|
-
showDetailPanel ? /* @__PURE__ */
|
|
4403
|
+
showDetailPanel ? /* @__PURE__ */ jsx17(Box16, { marginLeft: 1, width: detailPanelWidth, children: /* @__PURE__ */ jsx17(
|
|
3472
4404
|
DetailPanel,
|
|
3473
4405
|
{
|
|
3474
4406
|
issue: selectedItem.issue,
|
|
@@ -3477,25 +4409,16 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3477
4409
|
}
|
|
3478
4410
|
) }) : null
|
|
3479
4411
|
] }) : null,
|
|
3480
|
-
|
|
3481
|
-
ui.state.mode === "
|
|
3482
|
-
|
|
3483
|
-
{
|
|
3484
|
-
issueNumber: selectedItem.issue.number,
|
|
3485
|
-
onSubmit: actions.handleComment,
|
|
3486
|
-
onCancel: ui.exitOverlay
|
|
3487
|
-
}
|
|
3488
|
-
) : null,
|
|
3489
|
-
/* @__PURE__ */ jsx13(ToastContainer, { toasts }),
|
|
3490
|
-
/* @__PURE__ */ jsx13(Box13, { children: ui.state.mode === "multiSelect" ? /* @__PURE__ */ jsxs13(Fragment4, { children: [
|
|
3491
|
-
/* @__PURE__ */ jsxs13(Text13, { color: "cyan", bold: true, children: [
|
|
4412
|
+
/* @__PURE__ */ jsx17(ToastContainer, { toasts }),
|
|
4413
|
+
/* @__PURE__ */ jsx17(Box16, { children: ui.state.mode === "multiSelect" ? /* @__PURE__ */ jsxs17(Fragment5, { children: [
|
|
4414
|
+
/* @__PURE__ */ jsxs17(Text16, { color: "cyan", bold: true, children: [
|
|
3492
4415
|
multiSelect.count,
|
|
3493
4416
|
" selected"
|
|
3494
4417
|
] }),
|
|
3495
|
-
/* @__PURE__ */
|
|
3496
|
-
] }) : ui.state.mode === "focus" ? /* @__PURE__ */
|
|
3497
|
-
/* @__PURE__ */
|
|
3498
|
-
searchQuery && ui.state.mode !== "search" ? /* @__PURE__ */
|
|
4418
|
+
/* @__PURE__ */ jsx17(Text16, { color: "gray", children: " Space:toggle Enter:actions Esc:cancel" })
|
|
4419
|
+
] }) : ui.state.mode === "focus" ? /* @__PURE__ */ jsx17(Text16, { color: "magenta", bold: true, children: "Focus mode \u2014 Esc to exit" }) : /* @__PURE__ */ jsxs17(Fragment5, { children: [
|
|
4420
|
+
/* @__PURE__ */ jsx17(Text16, { color: "gray", children: "j/k:nav Tab:section Enter:open Space:select /:search p:pick c:comment m:status a/u:assign s:slack y:copy l:labels n:new I:nlcreate C:collapse f:focus ?:help q:quit" }),
|
|
4421
|
+
searchQuery && ui.state.mode !== "search" ? /* @__PURE__ */ jsxs17(Text16, { color: "yellow", children: [
|
|
3499
4422
|
' filter: "',
|
|
3500
4423
|
searchQuery,
|
|
3501
4424
|
'"'
|
|
@@ -3503,29 +4426,23 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3503
4426
|
] }) })
|
|
3504
4427
|
] });
|
|
3505
4428
|
}
|
|
3506
|
-
var
|
|
4429
|
+
var TERMINAL_STATUS_RE3, PRIORITY_RANK, CHROME_ROWS;
|
|
3507
4430
|
var init_dashboard = __esm({
|
|
3508
4431
|
"src/board/components/dashboard.tsx"() {
|
|
3509
4432
|
"use strict";
|
|
4433
|
+
init_clipboard();
|
|
3510
4434
|
init_use_actions();
|
|
3511
4435
|
init_use_data();
|
|
4436
|
+
init_use_keyboard();
|
|
3512
4437
|
init_use_multi_select();
|
|
3513
4438
|
init_use_navigation();
|
|
3514
4439
|
init_use_toast();
|
|
3515
4440
|
init_use_ui_state();
|
|
3516
|
-
init_bulk_action_menu();
|
|
3517
|
-
init_comment_input();
|
|
3518
|
-
init_confirm_prompt();
|
|
3519
|
-
init_create_issue_form();
|
|
3520
4441
|
init_detail_panel();
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
init_issue_row();
|
|
3524
|
-
init_search_bar();
|
|
3525
|
-
init_status_picker();
|
|
3526
|
-
init_task_row();
|
|
4442
|
+
init_overlay_renderer();
|
|
4443
|
+
init_row_renderer();
|
|
3527
4444
|
init_toast_container();
|
|
3528
|
-
|
|
4445
|
+
TERMINAL_STATUS_RE3 = /^(done|shipped|won't|wont|closed|complete|completed)$/i;
|
|
3529
4446
|
PRIORITY_RANK = {
|
|
3530
4447
|
"priority:critical": 0,
|
|
3531
4448
|
"priority:high": 1,
|
|
@@ -3542,17 +4459,19 @@ __export(live_exports, {
|
|
|
3542
4459
|
runLiveDashboard: () => runLiveDashboard
|
|
3543
4460
|
});
|
|
3544
4461
|
import { render } from "ink";
|
|
3545
|
-
import { jsx as
|
|
4462
|
+
import { jsx as jsx18 } from "react/jsx-runtime";
|
|
3546
4463
|
async function runLiveDashboard(config2, options, activeProfile) {
|
|
3547
|
-
const
|
|
3548
|
-
/* @__PURE__ */
|
|
4464
|
+
const instance = render(
|
|
4465
|
+
/* @__PURE__ */ jsx18(Dashboard, { config: config2, options, activeProfile: activeProfile ?? null })
|
|
3549
4466
|
);
|
|
3550
|
-
|
|
4467
|
+
setInkInstance(instance);
|
|
4468
|
+
await instance.waitUntilExit();
|
|
3551
4469
|
}
|
|
3552
4470
|
var init_live = __esm({
|
|
3553
4471
|
"src/board/live.tsx"() {
|
|
3554
4472
|
"use strict";
|
|
3555
4473
|
init_dashboard();
|
|
4474
|
+
init_ink_instance();
|
|
3556
4475
|
}
|
|
3557
4476
|
});
|
|
3558
4477
|
|
|
@@ -3918,83 +4837,17 @@ var init_format_static = __esm({
|
|
|
3918
4837
|
});
|
|
3919
4838
|
|
|
3920
4839
|
// src/cli.ts
|
|
4840
|
+
init_ai();
|
|
3921
4841
|
init_api();
|
|
3922
4842
|
init_config();
|
|
4843
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
3923
4844
|
import { Command } from "commander";
|
|
3924
4845
|
|
|
3925
4846
|
// src/init.ts
|
|
4847
|
+
init_config();
|
|
3926
4848
|
import { execFileSync } from "child_process";
|
|
3927
4849
|
import { existsSync as existsSync2 } from "fs";
|
|
3928
4850
|
import { checkbox, confirm, input, select } from "@inquirer/prompts";
|
|
3929
|
-
|
|
3930
|
-
// src/auth.ts
|
|
3931
|
-
import { createServer } from "http";
|
|
3932
|
-
var AUTH_URL = "https://ticktick.com/oauth/authorize";
|
|
3933
|
-
var TOKEN_URL = "https://ticktick.com/oauth/token";
|
|
3934
|
-
var REDIRECT_URI = "http://localhost:8080";
|
|
3935
|
-
var SCOPE = "tasks:write tasks:read";
|
|
3936
|
-
function getAuthorizationUrl(clientId) {
|
|
3937
|
-
const params = new URLSearchParams({
|
|
3938
|
-
scope: SCOPE,
|
|
3939
|
-
client_id: clientId,
|
|
3940
|
-
state: "hog",
|
|
3941
|
-
redirect_uri: REDIRECT_URI,
|
|
3942
|
-
response_type: "code"
|
|
3943
|
-
});
|
|
3944
|
-
return `${AUTH_URL}?${params}`;
|
|
3945
|
-
}
|
|
3946
|
-
async function waitForAuthCode() {
|
|
3947
|
-
return new Promise((resolve, reject) => {
|
|
3948
|
-
const server = createServer((req, res) => {
|
|
3949
|
-
const url = new URL(req.url ?? "", REDIRECT_URI);
|
|
3950
|
-
const code = url.searchParams.get("code");
|
|
3951
|
-
if (code) {
|
|
3952
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
3953
|
-
res.end(
|
|
3954
|
-
"<html><body><h1>Heart of Gold authenticated!</h1><p>You can close this window.</p></body></html>"
|
|
3955
|
-
);
|
|
3956
|
-
server.close();
|
|
3957
|
-
resolve(code);
|
|
3958
|
-
} else {
|
|
3959
|
-
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
3960
|
-
res.end("Missing authorization code");
|
|
3961
|
-
server.close();
|
|
3962
|
-
reject(new Error("No authorization code received"));
|
|
3963
|
-
}
|
|
3964
|
-
});
|
|
3965
|
-
server.listen(8080, () => {
|
|
3966
|
-
});
|
|
3967
|
-
server.on("error", reject);
|
|
3968
|
-
setTimeout(() => {
|
|
3969
|
-
server.close();
|
|
3970
|
-
reject(new Error("Authorization timed out (2 min)"));
|
|
3971
|
-
}, 12e4);
|
|
3972
|
-
});
|
|
3973
|
-
}
|
|
3974
|
-
async function exchangeCodeForToken(clientId, clientSecret, code) {
|
|
3975
|
-
const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");
|
|
3976
|
-
const res = await fetch(TOKEN_URL, {
|
|
3977
|
-
method: "POST",
|
|
3978
|
-
headers: {
|
|
3979
|
-
Authorization: `Basic ${credentials}`,
|
|
3980
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
3981
|
-
},
|
|
3982
|
-
body: new URLSearchParams({
|
|
3983
|
-
grant_type: "authorization_code",
|
|
3984
|
-
code,
|
|
3985
|
-
redirect_uri: REDIRECT_URI
|
|
3986
|
-
})
|
|
3987
|
-
});
|
|
3988
|
-
if (!res.ok) {
|
|
3989
|
-
const text = await res.text();
|
|
3990
|
-
throw new Error(`Token exchange failed: ${text}`);
|
|
3991
|
-
}
|
|
3992
|
-
const data = await res.json();
|
|
3993
|
-
return data.access_token;
|
|
3994
|
-
}
|
|
3995
|
-
|
|
3996
|
-
// src/init.ts
|
|
3997
|
-
init_config();
|
|
3998
4851
|
function ghJson(args) {
|
|
3999
4852
|
const output = execFileSync("gh", args, { encoding: "utf-8", timeout: 3e4 }).trim();
|
|
4000
4853
|
return JSON.parse(output);
|
|
@@ -4078,12 +4931,13 @@ function listProjectFields(owner, projectNumber) {
|
|
|
4078
4931
|
return [];
|
|
4079
4932
|
}
|
|
4080
4933
|
}
|
|
4081
|
-
function
|
|
4934
|
+
function detectStatusField(owner, projectNumber) {
|
|
4082
4935
|
const fields = listProjectFields(owner, projectNumber);
|
|
4083
4936
|
const statusField = fields.find(
|
|
4084
4937
|
(f) => f.name === "Status" && f.type === "ProjectV2SingleSelectField"
|
|
4085
4938
|
);
|
|
4086
|
-
|
|
4939
|
+
if (!statusField) return null;
|
|
4940
|
+
return { fieldId: statusField.id, options: statusField.options ?? [] };
|
|
4087
4941
|
}
|
|
4088
4942
|
async function runInit(opts = {}) {
|
|
4089
4943
|
try {
|
|
@@ -4157,8 +5011,10 @@ Configuring ${repoName}...`);
|
|
|
4157
5011
|
});
|
|
4158
5012
|
}
|
|
4159
5013
|
console.log(" Detecting status field...");
|
|
4160
|
-
|
|
4161
|
-
|
|
5014
|
+
const statusInfo = detectStatusField(owner, projectNumber);
|
|
5015
|
+
let statusFieldId;
|
|
5016
|
+
if (statusInfo) {
|
|
5017
|
+
statusFieldId = statusInfo.fieldId;
|
|
4162
5018
|
console.log(` Found status field: ${statusFieldId}`);
|
|
4163
5019
|
} else {
|
|
4164
5020
|
console.log(" Could not auto-detect status field.");
|
|
@@ -4167,7 +5023,7 @@ Configuring ${repoName}...`);
|
|
|
4167
5023
|
});
|
|
4168
5024
|
}
|
|
4169
5025
|
const completionType = await select({
|
|
4170
|
-
message: ` When a task is completed
|
|
5026
|
+
message: ` When a task is completed, what should happen on GitHub?`,
|
|
4171
5027
|
choices: [
|
|
4172
5028
|
{ name: "Close the issue", value: "closeIssue" },
|
|
4173
5029
|
{ name: "Add a label (e.g. review:pending)", value: "addLabel" },
|
|
@@ -4182,9 +5038,21 @@ Configuring ${repoName}...`);
|
|
|
4182
5038
|
});
|
|
4183
5039
|
completionAction = { type: "addLabel", label };
|
|
4184
5040
|
} else if (completionType === "updateProjectStatus") {
|
|
4185
|
-
const
|
|
4186
|
-
|
|
4187
|
-
|
|
5041
|
+
const statusOptions = statusInfo?.options ?? [];
|
|
5042
|
+
let optionId;
|
|
5043
|
+
if (statusOptions.length > 0) {
|
|
5044
|
+
optionId = await select({
|
|
5045
|
+
message: " Status to set when completed:",
|
|
5046
|
+
choices: statusOptions.map((o) => ({
|
|
5047
|
+
name: o.name,
|
|
5048
|
+
value: o.id
|
|
5049
|
+
}))
|
|
5050
|
+
});
|
|
5051
|
+
} else {
|
|
5052
|
+
optionId = await input({
|
|
5053
|
+
message: " Status option ID to set:"
|
|
5054
|
+
});
|
|
5055
|
+
}
|
|
4188
5056
|
completionAction = { type: "updateProjectStatus", optionId };
|
|
4189
5057
|
} else {
|
|
4190
5058
|
completionAction = { type: "closeIssue" };
|
|
@@ -4201,43 +5069,11 @@ Configuring ${repoName}...`);
|
|
|
4201
5069
|
completionAction
|
|
4202
5070
|
});
|
|
4203
5071
|
}
|
|
4204
|
-
const
|
|
4205
|
-
message: "Enable TickTick integration?",
|
|
4206
|
-
default: false
|
|
4207
|
-
});
|
|
5072
|
+
const ticktickAlreadyEnabled = existsSync2(`${CONFIG_DIR}/auth.json`);
|
|
4208
5073
|
let ticktickAuth = false;
|
|
4209
|
-
if (
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
console.log(" TickTick auth already configured.");
|
|
4213
|
-
ticktickAuth = true;
|
|
4214
|
-
} else {
|
|
4215
|
-
const setupNow = await confirm({
|
|
4216
|
-
message: " Set up TickTick OAuth now?",
|
|
4217
|
-
default: true
|
|
4218
|
-
});
|
|
4219
|
-
if (setupNow) {
|
|
4220
|
-
const clientId = await input({ message: " TickTick OAuth client ID:" });
|
|
4221
|
-
const clientSecret = await input({ message: " TickTick OAuth client secret:" });
|
|
4222
|
-
const url = getAuthorizationUrl(clientId);
|
|
4223
|
-
console.log(`
|
|
4224
|
-
Open this URL to authorize:
|
|
4225
|
-
|
|
4226
|
-
${url}
|
|
4227
|
-
`);
|
|
4228
|
-
try {
|
|
4229
|
-
const { exec } = await import("child_process");
|
|
4230
|
-
exec(`open "${url}"`);
|
|
4231
|
-
} catch {
|
|
4232
|
-
}
|
|
4233
|
-
console.log(" Waiting for authorization...");
|
|
4234
|
-
const code = await waitForAuthCode();
|
|
4235
|
-
const accessToken = await exchangeCodeForToken(clientId, clientSecret, code);
|
|
4236
|
-
saveAuth({ accessToken, clientId, clientSecret });
|
|
4237
|
-
console.log(" TickTick authenticated successfully.");
|
|
4238
|
-
ticktickAuth = true;
|
|
4239
|
-
}
|
|
4240
|
-
}
|
|
5074
|
+
if (ticktickAlreadyEnabled) {
|
|
5075
|
+
ticktickAuth = true;
|
|
5076
|
+
console.log("TickTick auth found \u2014 integration enabled.");
|
|
4241
5077
|
}
|
|
4242
5078
|
console.log("\nBoard settings:");
|
|
4243
5079
|
const refreshInterval = await input({
|
|
@@ -4264,7 +5100,7 @@ Configuring ${repoName}...`);
|
|
|
4264
5100
|
assignee: login,
|
|
4265
5101
|
focusDuration: Number.parseInt(focusDuration, 10) || 1500
|
|
4266
5102
|
},
|
|
4267
|
-
ticktick: { enabled:
|
|
5103
|
+
ticktick: { enabled: ticktickAuth },
|
|
4268
5104
|
profiles: existingConfig?.profiles ?? {}
|
|
4269
5105
|
};
|
|
4270
5106
|
saveFullConfig(config2);
|
|
@@ -4674,7 +5510,7 @@ function resolveProjectId(projectId) {
|
|
|
4674
5510
|
process.exit(1);
|
|
4675
5511
|
}
|
|
4676
5512
|
var program = new Command();
|
|
4677
|
-
program.name("hog").description("Personal command deck \u2014 unified task dashboard for GitHub Projects + TickTick").version("1.
|
|
5513
|
+
program.name("hog").description("Personal command deck \u2014 unified task dashboard for GitHub Projects + TickTick").version("1.3.0").option("--json", "Force JSON output").option("--human", "Force human-readable output").hook("preAction", (thisCommand) => {
|
|
4678
5514
|
const opts = thisCommand.opts();
|
|
4679
5515
|
if (opts.json) setFormat("json");
|
|
4680
5516
|
if (opts.human) setFormat("human");
|
|
@@ -5023,6 +5859,53 @@ config.command("profile:default [name]").description("Set or show the default bo
|
|
|
5023
5859
|
printSuccess(`Default profile set to "${name}".`);
|
|
5024
5860
|
}
|
|
5025
5861
|
});
|
|
5862
|
+
var issueCommand = new Command("issue").description("GitHub issue utilities");
|
|
5863
|
+
issueCommand.command("create <text>").description("Create a GitHub issue from natural language text").option("--repo <repo>", "Target repository (owner/name)").option("--dry-run", "Print parsed fields without creating the issue").action(async (text, opts) => {
|
|
5864
|
+
const config2 = loadFullConfig();
|
|
5865
|
+
const repo = opts.repo ?? config2.repos[0]?.name;
|
|
5866
|
+
if (!repo) {
|
|
5867
|
+
console.error(
|
|
5868
|
+
"Error: no repo specified. Use --repo owner/name or configure repos in hog init."
|
|
5869
|
+
);
|
|
5870
|
+
process.exit(1);
|
|
5871
|
+
}
|
|
5872
|
+
if (hasLlmApiKey()) {
|
|
5873
|
+
console.error("[info] LLM parsing enabled");
|
|
5874
|
+
}
|
|
5875
|
+
const parsed = await extractIssueFields(text, {
|
|
5876
|
+
onLlmFallback: (msg) => console.error(`[warn] ${msg}`)
|
|
5877
|
+
});
|
|
5878
|
+
if (!parsed) {
|
|
5879
|
+
console.error(
|
|
5880
|
+
"Error: could not parse a title from input. Ensure your text has a non-empty title."
|
|
5881
|
+
);
|
|
5882
|
+
process.exit(1);
|
|
5883
|
+
}
|
|
5884
|
+
const labels = [...parsed.labels];
|
|
5885
|
+
if (parsed.dueDate) labels.push(`due:${parsed.dueDate}`);
|
|
5886
|
+
console.error(`Title: ${parsed.title}`);
|
|
5887
|
+
if (labels.length > 0) console.error(`Labels: ${labels.join(", ")}`);
|
|
5888
|
+
if (parsed.assignee) console.error(`Assignee: @${parsed.assignee}`);
|
|
5889
|
+
if (parsed.dueDate) console.error(`Due: ${parsed.dueDate}`);
|
|
5890
|
+
console.error(`Repo: ${repo}`);
|
|
5891
|
+
if (opts.dryRun) {
|
|
5892
|
+
console.error("[dry-run] Skipping issue creation.");
|
|
5893
|
+
return;
|
|
5894
|
+
}
|
|
5895
|
+
const args = ["issue", "create", "--repo", repo, "--title", parsed.title];
|
|
5896
|
+
for (const label of labels) {
|
|
5897
|
+
args.push("--label", label);
|
|
5898
|
+
}
|
|
5899
|
+
try {
|
|
5900
|
+
execFileSync5("gh", args, { stdio: "inherit" });
|
|
5901
|
+
} catch (err) {
|
|
5902
|
+
console.error(
|
|
5903
|
+
`Error: gh issue create failed: ${err instanceof Error ? err.message : String(err)}`
|
|
5904
|
+
);
|
|
5905
|
+
process.exit(1);
|
|
5906
|
+
}
|
|
5907
|
+
});
|
|
5908
|
+
program.addCommand(issueCommand);
|
|
5026
5909
|
program.parseAsync().catch((err) => {
|
|
5027
5910
|
const message = err instanceof Error ? err.message : String(err);
|
|
5028
5911
|
console.error(`Error: ${message}`);
|