@ondrej-svec/hog 1.2.0 → 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 +1821 -846
- 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({
|
|
@@ -461,6 +621,21 @@ function fetchProjectStatusOptions(repo, projectNumber, _statusFieldId) {
|
|
|
461
621
|
function addLabel(repo, issueNumber, label) {
|
|
462
622
|
runGh(["issue", "edit", String(issueNumber), "--repo", repo, "--add-label", label]);
|
|
463
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
|
+
}
|
|
464
639
|
function updateProjectItemStatus(repo, issueNumber, projectConfig) {
|
|
465
640
|
const [owner, repoName] = repo.split("/");
|
|
466
641
|
const findItemQuery = `
|
|
@@ -678,6 +853,21 @@ var init_sync_state = __esm({
|
|
|
678
853
|
}
|
|
679
854
|
});
|
|
680
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
|
+
|
|
681
871
|
// src/pick.ts
|
|
682
872
|
var pick_exports = {};
|
|
683
873
|
__export(pick_exports, {
|
|
@@ -1034,6 +1224,26 @@ function useActions({
|
|
|
1034
1224
|
},
|
|
1035
1225
|
[toast, refresh, onOverlayDone]
|
|
1036
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
|
+
);
|
|
1037
1247
|
const handleBulkAssign = useCallback(
|
|
1038
1248
|
async (ids) => {
|
|
1039
1249
|
const failed = [];
|
|
@@ -1164,6 +1374,7 @@ function useActions({
|
|
|
1164
1374
|
handleStatusChange,
|
|
1165
1375
|
handleAssign,
|
|
1166
1376
|
handleUnassign,
|
|
1377
|
+
handleLabelChange,
|
|
1167
1378
|
handleCreateIssue,
|
|
1168
1379
|
handleBulkAssign,
|
|
1169
1380
|
handleBulkUnassign,
|
|
@@ -1299,7 +1510,13 @@ function useData(config2, options, refreshIntervalMs) {
|
|
|
1299
1510
|
return { ...prev, data: fn(prev.data) };
|
|
1300
1511
|
});
|
|
1301
1512
|
}, []);
|
|
1302
|
-
|
|
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 };
|
|
1303
1520
|
}
|
|
1304
1521
|
var INITIAL_STATE, STALE_THRESHOLDS, MAX_REFRESH_FAILURES;
|
|
1305
1522
|
var init_use_data = __esm({
|
|
@@ -1325,14 +1542,234 @@ var init_use_data = __esm({
|
|
|
1325
1542
|
}
|
|
1326
1543
|
});
|
|
1327
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
|
+
|
|
1328
1765
|
// src/board/hooks/use-multi-select.ts
|
|
1329
|
-
import { useCallback as
|
|
1766
|
+
import { useCallback as useCallback4, useRef as useRef3, useState as useState2 } from "react";
|
|
1330
1767
|
function useMultiSelect(getRepoForId) {
|
|
1331
1768
|
const [selected, setSelected] = useState2(/* @__PURE__ */ new Set());
|
|
1332
1769
|
const repoRef = useRef3(null);
|
|
1333
1770
|
const getRepoRef = useRef3(getRepoForId);
|
|
1334
1771
|
getRepoRef.current = getRepoForId;
|
|
1335
|
-
const toggle =
|
|
1772
|
+
const toggle = useCallback4((id) => {
|
|
1336
1773
|
setSelected((prev) => {
|
|
1337
1774
|
const repo = getRepoRef.current(id);
|
|
1338
1775
|
if (!repo) return prev;
|
|
@@ -1350,11 +1787,11 @@ function useMultiSelect(getRepoForId) {
|
|
|
1350
1787
|
return next;
|
|
1351
1788
|
});
|
|
1352
1789
|
}, []);
|
|
1353
|
-
const clear =
|
|
1790
|
+
const clear = useCallback4(() => {
|
|
1354
1791
|
setSelected(/* @__PURE__ */ new Set());
|
|
1355
1792
|
repoRef.current = null;
|
|
1356
1793
|
}, []);
|
|
1357
|
-
const prune =
|
|
1794
|
+
const prune = useCallback4((validIds) => {
|
|
1358
1795
|
setSelected((prev) => {
|
|
1359
1796
|
const next = /* @__PURE__ */ new Set();
|
|
1360
1797
|
for (const id of prev) {
|
|
@@ -1365,7 +1802,7 @@ function useMultiSelect(getRepoForId) {
|
|
|
1365
1802
|
return next;
|
|
1366
1803
|
});
|
|
1367
1804
|
}, []);
|
|
1368
|
-
const isSelected =
|
|
1805
|
+
const isSelected = useCallback4((id) => selected.has(id), [selected]);
|
|
1369
1806
|
return {
|
|
1370
1807
|
selected,
|
|
1371
1808
|
count: selected.size,
|
|
@@ -1383,7 +1820,7 @@ var init_use_multi_select = __esm({
|
|
|
1383
1820
|
});
|
|
1384
1821
|
|
|
1385
1822
|
// src/board/hooks/use-navigation.ts
|
|
1386
|
-
import { useCallback as
|
|
1823
|
+
import { useCallback as useCallback5, useMemo, useReducer, useRef as useRef4 } from "react";
|
|
1387
1824
|
function arraysEqual(a, b) {
|
|
1388
1825
|
if (a.length !== b.length) return false;
|
|
1389
1826
|
for (let i = 0; i < a.length; i++) {
|
|
@@ -1405,7 +1842,7 @@ function navReducer(state, action) {
|
|
|
1405
1842
|
case "SET_ITEMS": {
|
|
1406
1843
|
const sections = [...new Set(action.items.map((i) => i.section))];
|
|
1407
1844
|
const isFirstLoad = state.sections.length === 0;
|
|
1408
|
-
const collapsedSections = isFirstLoad ? new Set(sections) : state.collapsedSections;
|
|
1845
|
+
const collapsedSections = isFirstLoad ? new Set(sections.filter((s) => s === "activity")) : state.collapsedSections;
|
|
1409
1846
|
const selectionValid = state.selectedId != null && action.items.some((i) => i.id === state.selectedId);
|
|
1410
1847
|
if (!isFirstLoad && selectionValid && arraysEqual(sections, state.sections)) {
|
|
1411
1848
|
return state;
|
|
@@ -1443,6 +1880,9 @@ function navReducer(state, action) {
|
|
|
1443
1880
|
}
|
|
1444
1881
|
return { ...state, collapsedSections: next };
|
|
1445
1882
|
}
|
|
1883
|
+
case "COLLAPSE_ALL": {
|
|
1884
|
+
return { ...state, collapsedSections: new Set(state.sections) };
|
|
1885
|
+
}
|
|
1446
1886
|
default:
|
|
1447
1887
|
return state;
|
|
1448
1888
|
}
|
|
@@ -1477,17 +1917,17 @@ function useNavigation(allItems) {
|
|
|
1477
1917
|
const idx = visibleItems.findIndex((i) => i.id === state.selectedId);
|
|
1478
1918
|
return idx >= 0 ? idx : 0;
|
|
1479
1919
|
}, [state.selectedId, visibleItems]);
|
|
1480
|
-
const moveUp =
|
|
1920
|
+
const moveUp = useCallback5(() => {
|
|
1481
1921
|
const newIdx = Math.max(0, selectedIndex - 1);
|
|
1482
1922
|
const item = visibleItems[newIdx];
|
|
1483
1923
|
if (item) dispatch({ type: "SELECT", id: item.id, section: item.section });
|
|
1484
1924
|
}, [selectedIndex, visibleItems]);
|
|
1485
|
-
const moveDown =
|
|
1925
|
+
const moveDown = useCallback5(() => {
|
|
1486
1926
|
const newIdx = Math.min(visibleItems.length - 1, selectedIndex + 1);
|
|
1487
1927
|
const item = visibleItems[newIdx];
|
|
1488
1928
|
if (item) dispatch({ type: "SELECT", id: item.id, section: item.section });
|
|
1489
1929
|
}, [selectedIndex, visibleItems]);
|
|
1490
|
-
const nextSection =
|
|
1930
|
+
const nextSection = useCallback5(() => {
|
|
1491
1931
|
const currentItem = visibleItems[selectedIndex];
|
|
1492
1932
|
if (!currentItem) return;
|
|
1493
1933
|
const currentSectionIdx = state.sections.indexOf(currentItem.section);
|
|
@@ -1496,7 +1936,7 @@ function useNavigation(allItems) {
|
|
|
1496
1936
|
const header = visibleItems.find((i) => i.section === nextSectionId && i.type === "header");
|
|
1497
1937
|
if (header) dispatch({ type: "SELECT", id: header.id, section: header.section });
|
|
1498
1938
|
}, [selectedIndex, visibleItems, state.sections]);
|
|
1499
|
-
const prevSection =
|
|
1939
|
+
const prevSection = useCallback5(() => {
|
|
1500
1940
|
const currentItem = visibleItems[selectedIndex];
|
|
1501
1941
|
if (!currentItem) return;
|
|
1502
1942
|
const currentSectionIdx = state.sections.indexOf(currentItem.section);
|
|
@@ -1505,19 +1945,22 @@ function useNavigation(allItems) {
|
|
|
1505
1945
|
const header = visibleItems.find((i) => i.section === prevSectionId && i.type === "header");
|
|
1506
1946
|
if (header) dispatch({ type: "SELECT", id: header.id, section: header.section });
|
|
1507
1947
|
}, [selectedIndex, visibleItems, state.sections]);
|
|
1508
|
-
const toggleSection =
|
|
1948
|
+
const toggleSection = useCallback5(() => {
|
|
1509
1949
|
const currentItem = visibleItems[selectedIndex];
|
|
1510
1950
|
if (!currentItem) return;
|
|
1511
1951
|
const key = currentItem.type === "subHeader" ? currentItem.id : currentItem.section;
|
|
1512
1952
|
dispatch({ type: "TOGGLE_SECTION", section: key });
|
|
1513
1953
|
}, [selectedIndex, visibleItems]);
|
|
1954
|
+
const collapseAll = useCallback5(() => {
|
|
1955
|
+
dispatch({ type: "COLLAPSE_ALL" });
|
|
1956
|
+
}, []);
|
|
1514
1957
|
const allItemsRef = useRef4(allItems);
|
|
1515
1958
|
allItemsRef.current = allItems;
|
|
1516
|
-
const select2 =
|
|
1959
|
+
const select2 = useCallback5((id) => {
|
|
1517
1960
|
const item = allItemsRef.current.find((i) => i.id === id);
|
|
1518
1961
|
dispatch({ type: "SELECT", id, section: item?.section });
|
|
1519
1962
|
}, []);
|
|
1520
|
-
const isCollapsed =
|
|
1963
|
+
const isCollapsed = useCallback5(
|
|
1521
1964
|
(section) => state.collapsedSections.has(section),
|
|
1522
1965
|
[state.collapsedSections]
|
|
1523
1966
|
);
|
|
@@ -1530,6 +1973,7 @@ function useNavigation(allItems) {
|
|
|
1530
1973
|
nextSection,
|
|
1531
1974
|
prevSection,
|
|
1532
1975
|
toggleSection,
|
|
1976
|
+
collapseAll,
|
|
1533
1977
|
select: select2,
|
|
1534
1978
|
isCollapsed
|
|
1535
1979
|
};
|
|
@@ -1541,25 +1985,25 @@ var init_use_navigation = __esm({
|
|
|
1541
1985
|
});
|
|
1542
1986
|
|
|
1543
1987
|
// src/board/hooks/use-toast.ts
|
|
1544
|
-
import { useCallback as
|
|
1988
|
+
import { useCallback as useCallback6, useRef as useRef5, useState as useState3 } from "react";
|
|
1545
1989
|
function useToast() {
|
|
1546
1990
|
const [toasts, setToasts] = useState3([]);
|
|
1547
1991
|
const timersRef = useRef5(/* @__PURE__ */ new Map());
|
|
1548
|
-
const clearTimer =
|
|
1992
|
+
const clearTimer = useCallback6((id) => {
|
|
1549
1993
|
const timer = timersRef.current.get(id);
|
|
1550
1994
|
if (timer) {
|
|
1551
1995
|
clearTimeout(timer);
|
|
1552
1996
|
timersRef.current.delete(id);
|
|
1553
1997
|
}
|
|
1554
1998
|
}, []);
|
|
1555
|
-
const removeToast =
|
|
1999
|
+
const removeToast = useCallback6(
|
|
1556
2000
|
(id) => {
|
|
1557
2001
|
clearTimer(id);
|
|
1558
2002
|
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
1559
2003
|
},
|
|
1560
2004
|
[clearTimer]
|
|
1561
2005
|
);
|
|
1562
|
-
const addToast =
|
|
2006
|
+
const addToast = useCallback6(
|
|
1563
2007
|
(t) => {
|
|
1564
2008
|
const id = `toast-${++nextId}`;
|
|
1565
2009
|
const newToast = { ...t, id, createdAt: Date.now() };
|
|
@@ -1588,25 +2032,25 @@ function useToast() {
|
|
|
1588
2032
|
[removeToast, clearTimer]
|
|
1589
2033
|
);
|
|
1590
2034
|
const toast = {
|
|
1591
|
-
info:
|
|
2035
|
+
info: useCallback6(
|
|
1592
2036
|
(message) => {
|
|
1593
2037
|
addToast({ type: "info", message });
|
|
1594
2038
|
},
|
|
1595
2039
|
[addToast]
|
|
1596
2040
|
),
|
|
1597
|
-
success:
|
|
2041
|
+
success: useCallback6(
|
|
1598
2042
|
(message) => {
|
|
1599
2043
|
addToast({ type: "success", message });
|
|
1600
2044
|
},
|
|
1601
2045
|
[addToast]
|
|
1602
2046
|
),
|
|
1603
|
-
error:
|
|
2047
|
+
error: useCallback6(
|
|
1604
2048
|
(message, retry) => {
|
|
1605
2049
|
addToast(retry ? { type: "error", message, retry } : { type: "error", message });
|
|
1606
2050
|
},
|
|
1607
2051
|
[addToast]
|
|
1608
2052
|
),
|
|
1609
|
-
loading:
|
|
2053
|
+
loading: useCallback6(
|
|
1610
2054
|
(message) => {
|
|
1611
2055
|
const id = addToast({ type: "loading", message });
|
|
1612
2056
|
return {
|
|
@@ -1623,20 +2067,20 @@ function useToast() {
|
|
|
1623
2067
|
[addToast, removeToast]
|
|
1624
2068
|
)
|
|
1625
2069
|
};
|
|
1626
|
-
const dismiss =
|
|
2070
|
+
const dismiss = useCallback6(
|
|
1627
2071
|
(id) => {
|
|
1628
2072
|
removeToast(id);
|
|
1629
2073
|
},
|
|
1630
2074
|
[removeToast]
|
|
1631
2075
|
);
|
|
1632
|
-
const dismissAll =
|
|
2076
|
+
const dismissAll = useCallback6(() => {
|
|
1633
2077
|
for (const timer of timersRef.current.values()) {
|
|
1634
2078
|
clearTimeout(timer);
|
|
1635
2079
|
}
|
|
1636
2080
|
timersRef.current.clear();
|
|
1637
2081
|
setToasts([]);
|
|
1638
2082
|
}, []);
|
|
1639
|
-
const handleErrorAction =
|
|
2083
|
+
const handleErrorAction = useCallback6(
|
|
1640
2084
|
(action) => {
|
|
1641
2085
|
const errorToast = toasts.find((t) => t.type === "error");
|
|
1642
2086
|
if (!errorToast) return false;
|
|
@@ -1666,7 +2110,7 @@ var init_use_toast = __esm({
|
|
|
1666
2110
|
});
|
|
1667
2111
|
|
|
1668
2112
|
// src/board/hooks/use-ui-state.ts
|
|
1669
|
-
import { useCallback as
|
|
2113
|
+
import { useCallback as useCallback7, useReducer as useReducer2 } from "react";
|
|
1670
2114
|
function uiReducer(state, action) {
|
|
1671
2115
|
switch (action.type) {
|
|
1672
2116
|
case "ENTER_SEARCH":
|
|
@@ -1685,6 +2129,12 @@ function uiReducer(state, action) {
|
|
|
1685
2129
|
case "ENTER_CREATE":
|
|
1686
2130
|
if (state.mode !== "normal") return state;
|
|
1687
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" };
|
|
1688
2138
|
case "ENTER_MULTI_SELECT":
|
|
1689
2139
|
if (state.mode !== "normal" && state.mode !== "multiSelect") return state;
|
|
1690
2140
|
return { ...state, mode: "multiSelect", previousMode: "normal" };
|
|
@@ -1728,18 +2178,20 @@ function useUIState() {
|
|
|
1728
2178
|
const [state, dispatch] = useReducer2(uiReducer, INITIAL_STATE2);
|
|
1729
2179
|
return {
|
|
1730
2180
|
state,
|
|
1731
|
-
enterSearch:
|
|
1732
|
-
enterComment:
|
|
1733
|
-
enterStatus:
|
|
1734
|
-
enterCreate:
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
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" }), []),
|
|
1743
2195
|
canNavigate: canNavigate(state),
|
|
1744
2196
|
canAct: canAct(state),
|
|
1745
2197
|
isOverlay: isOverlay(state)
|
|
@@ -1757,37 +2209,205 @@ var init_use_ui_state = __esm({
|
|
|
1757
2209
|
}
|
|
1758
2210
|
});
|
|
1759
2211
|
|
|
1760
|
-
// src/board/components/
|
|
1761
|
-
import { Box, Text
|
|
1762
|
-
import {
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
return [
|
|
1767
|
-
{ label: "Assign all to me", action: { type: "assign" } },
|
|
1768
|
-
{ label: "Unassign all from me", action: { type: "unassign" } },
|
|
1769
|
-
{ label: "Move status (all)", action: { type: "statusChange" } }
|
|
1770
|
-
];
|
|
1771
|
-
}
|
|
1772
|
-
if (selectionType === "ticktick") {
|
|
1773
|
-
return [
|
|
1774
|
-
{ label: "Complete all", action: { type: "complete" } },
|
|
1775
|
-
{ label: "Delete all", action: { type: "delete" } }
|
|
1776
|
-
];
|
|
1777
|
-
}
|
|
1778
|
-
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");
|
|
1779
2218
|
}
|
|
1780
|
-
function
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
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);
|
|
2408
|
+
return;
|
|
2409
|
+
}
|
|
2410
|
+
if (input2 === "j" || key.downArrow) {
|
|
1791
2411
|
setSelectedIdx((i) => Math.min(i + 1, items.length - 1));
|
|
1792
2412
|
}
|
|
1793
2413
|
if (input2 === "k" || key.upArrow) {
|
|
@@ -1795,13 +2415,13 @@ function BulkActionMenu({ count, selectionType, onSelect, onCancel }) {
|
|
|
1795
2415
|
}
|
|
1796
2416
|
});
|
|
1797
2417
|
if (items.length === 0) {
|
|
1798
|
-
return /* @__PURE__ */
|
|
1799
|
-
/* @__PURE__ */
|
|
1800
|
-
/* @__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" })
|
|
1801
2421
|
] });
|
|
1802
2422
|
}
|
|
1803
|
-
return /* @__PURE__ */
|
|
1804
|
-
/* @__PURE__ */
|
|
2423
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
2424
|
+
/* @__PURE__ */ jsxs2(Text2, { color: "cyan", bold: true, children: [
|
|
1805
2425
|
"Bulk action (",
|
|
1806
2426
|
count,
|
|
1807
2427
|
" selected):"
|
|
@@ -1809,12 +2429,12 @@ function BulkActionMenu({ count, selectionType, onSelect, onCancel }) {
|
|
|
1809
2429
|
items.map((item, i) => {
|
|
1810
2430
|
const isSelected = i === selectedIdx;
|
|
1811
2431
|
const prefix = isSelected ? "> " : " ";
|
|
1812
|
-
return /* @__PURE__ */
|
|
2432
|
+
return /* @__PURE__ */ jsxs2(Text2, { ...isSelected ? { color: "cyan" } : {}, children: [
|
|
1813
2433
|
prefix,
|
|
1814
2434
|
item.label
|
|
1815
2435
|
] }, item.action.type);
|
|
1816
2436
|
}),
|
|
1817
|
-
/* @__PURE__ */
|
|
2437
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "j/k:navigate Enter:select Esc:cancel" })
|
|
1818
2438
|
] });
|
|
1819
2439
|
}
|
|
1820
2440
|
var init_bulk_action_menu = __esm({
|
|
@@ -1823,27 +2443,113 @@ var init_bulk_action_menu = __esm({
|
|
|
1823
2443
|
}
|
|
1824
2444
|
});
|
|
1825
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
|
+
|
|
1826
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";
|
|
1827
2466
|
import { TextInput } from "@inkjs/ui";
|
|
1828
|
-
import { Box as
|
|
1829
|
-
import { useState as useState5 } from "react";
|
|
1830
|
-
import { jsx as
|
|
1831
|
-
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
|
+
}) {
|
|
1832
2477
|
const [value, setValue] = useState5("");
|
|
1833
|
-
|
|
1834
|
-
|
|
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
|
+
}
|
|
1835
2497
|
});
|
|
1836
|
-
|
|
1837
|
-
|
|
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: [
|
|
1838
2544
|
"comment #",
|
|
1839
2545
|
issueNumber,
|
|
1840
2546
|
": "
|
|
1841
2547
|
] }),
|
|
1842
|
-
/* @__PURE__ */
|
|
2548
|
+
/* @__PURE__ */ jsx3(
|
|
1843
2549
|
TextInput,
|
|
1844
2550
|
{
|
|
1845
2551
|
defaultValue: value,
|
|
1846
|
-
placeholder: "type comment, Enter to post...",
|
|
2552
|
+
placeholder: "type comment (ctrl+e for editor), Enter to post...",
|
|
1847
2553
|
onChange: setValue,
|
|
1848
2554
|
onSubmit: (text) => {
|
|
1849
2555
|
if (text.trim()) onSubmit(text.trim());
|
|
@@ -1856,20 +2562,21 @@ function CommentInput({ issueNumber, onSubmit, onCancel }) {
|
|
|
1856
2562
|
var init_comment_input = __esm({
|
|
1857
2563
|
"src/board/components/comment-input.tsx"() {
|
|
1858
2564
|
"use strict";
|
|
2565
|
+
init_ink_instance();
|
|
1859
2566
|
}
|
|
1860
2567
|
});
|
|
1861
2568
|
|
|
1862
2569
|
// src/board/components/confirm-prompt.tsx
|
|
1863
|
-
import { Box as
|
|
1864
|
-
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";
|
|
1865
2572
|
function ConfirmPrompt({ message, onConfirm, onCancel }) {
|
|
1866
|
-
|
|
2573
|
+
useInput4((input2, key) => {
|
|
1867
2574
|
if (input2 === "y" || input2 === "Y") return onConfirm();
|
|
1868
2575
|
if (input2 === "n" || input2 === "N" || key.escape) return onCancel();
|
|
1869
2576
|
});
|
|
1870
|
-
return /* @__PURE__ */
|
|
1871
|
-
/* @__PURE__ */
|
|
1872
|
-
/* @__PURE__ */
|
|
2577
|
+
return /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
2578
|
+
/* @__PURE__ */ jsx4(Text4, { color: "cyan", children: message }),
|
|
2579
|
+
/* @__PURE__ */ jsx4(Text4, { color: "gray", children: " (y/n)" })
|
|
1873
2580
|
] });
|
|
1874
2581
|
}
|
|
1875
2582
|
var init_confirm_prompt = __esm({
|
|
@@ -1878,20 +2585,146 @@ var init_confirm_prompt = __esm({
|
|
|
1878
2585
|
}
|
|
1879
2586
|
});
|
|
1880
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
|
+
|
|
1881
2707
|
// src/board/components/create-issue-form.tsx
|
|
1882
2708
|
import { TextInput as TextInput2 } from "@inkjs/ui";
|
|
1883
|
-
import { Box as
|
|
1884
|
-
import { useState as
|
|
1885
|
-
import { jsx as
|
|
1886
|
-
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
|
+
}) {
|
|
1887
2719
|
const defaultRepoIdx = defaultRepo ? Math.max(
|
|
1888
2720
|
0,
|
|
1889
2721
|
repos.findIndex((r) => r.name === defaultRepo)
|
|
1890
2722
|
) : 0;
|
|
1891
|
-
const [repoIdx, setRepoIdx] =
|
|
1892
|
-
const [title, setTitle] =
|
|
1893
|
-
const [field, setField] =
|
|
1894
|
-
|
|
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;
|
|
1895
2728
|
if (key.escape) return onCancel();
|
|
1896
2729
|
if (field === "repo") {
|
|
1897
2730
|
if (input2 === "j" || key.downArrow) {
|
|
@@ -1905,12 +2738,40 @@ function CreateIssueForm({ repos, defaultRepo, onSubmit, onCancel }) {
|
|
|
1905
2738
|
}
|
|
1906
2739
|
});
|
|
1907
2740
|
const selectedRepo = repos[repoIdx];
|
|
1908
|
-
|
|
1909
|
-
/* @__PURE__ */
|
|
1910
|
-
|
|
1911
|
-
/* @__PURE__ */
|
|
1912
|
-
|
|
1913
|
-
|
|
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,
|
|
1914
2775
|
{
|
|
1915
2776
|
...i === repoIdx ? { color: "cyan", bold: true } : {},
|
|
1916
2777
|
dimColor: field !== "repo",
|
|
@@ -1918,215 +2779,53 @@ function CreateIssueForm({ repos, defaultRepo, onSubmit, onCancel }) {
|
|
|
1918
2779
|
},
|
|
1919
2780
|
r.name
|
|
1920
2781
|
)),
|
|
1921
|
-
field === "repo" ? /* @__PURE__ */
|
|
2782
|
+
field === "repo" ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " j/k:select Tab:next" }) : null
|
|
1922
2783
|
] }),
|
|
1923
|
-
/* @__PURE__ */
|
|
1924
|
-
/* @__PURE__ */
|
|
1925
|
-
field === "title" ? /* @__PURE__ */
|
|
2784
|
+
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2785
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: field !== "title", children: "Title: " }),
|
|
2786
|
+
field === "title" ? /* @__PURE__ */ jsx6(
|
|
1926
2787
|
TextInput2,
|
|
1927
2788
|
{
|
|
1928
2789
|
defaultValue: title,
|
|
1929
2790
|
placeholder: "issue title...",
|
|
1930
2791
|
onChange: setTitle,
|
|
1931
2792
|
onSubmit: (text) => {
|
|
1932
|
-
|
|
1933
|
-
|
|
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);
|
|
1934
2800
|
}
|
|
1935
2801
|
}
|
|
1936
2802
|
}
|
|
1937
|
-
) : /* @__PURE__ */
|
|
2803
|
+
) : /* @__PURE__ */ jsx6(Text6, { children: title || "(empty)" })
|
|
1938
2804
|
] }),
|
|
1939
|
-
/* @__PURE__ */
|
|
2805
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "Tab:switch fields Enter:next Esc:cancel" })
|
|
1940
2806
|
] });
|
|
1941
2807
|
}
|
|
1942
2808
|
var init_create_issue_form = __esm({
|
|
1943
2809
|
"src/board/components/create-issue-form.tsx"() {
|
|
1944
2810
|
"use strict";
|
|
1945
|
-
|
|
1946
|
-
});
|
|
1947
|
-
|
|
1948
|
-
// src/board/components/detail-panel.tsx
|
|
1949
|
-
import { Box as Box5, Text as Text5 } from "ink";
|
|
1950
|
-
import { Fragment, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1951
|
-
function truncateLines(text, maxLines) {
|
|
1952
|
-
const lines = text.split("\n").slice(0, maxLines);
|
|
1953
|
-
return lines.join("\n");
|
|
1954
|
-
}
|
|
1955
|
-
function stripMarkdown(text) {
|
|
1956
|
-
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();
|
|
1957
|
-
}
|
|
1958
|
-
function formatBody(body, maxLines) {
|
|
1959
|
-
const plain = stripMarkdown(body);
|
|
1960
|
-
const lines = plain.split("\n");
|
|
1961
|
-
const truncated = lines.slice(0, maxLines).join("\n");
|
|
1962
|
-
return { text: truncated, remaining: Math.max(0, lines.length - maxLines) };
|
|
1963
|
-
}
|
|
1964
|
-
function countSlackLinks(body) {
|
|
1965
|
-
if (!body) return 0;
|
|
1966
|
-
return (body.match(SLACK_URL_RE) ?? []).length;
|
|
1967
|
-
}
|
|
1968
|
-
function BodySection({
|
|
1969
|
-
body,
|
|
1970
|
-
issueNumber
|
|
1971
|
-
}) {
|
|
1972
|
-
const { text, remaining } = formatBody(body, 15);
|
|
1973
|
-
return /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
1974
|
-
/* @__PURE__ */ jsx5(Text5, { children: "" }),
|
|
1975
|
-
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "--- Description ---" }),
|
|
1976
|
-
/* @__PURE__ */ jsx5(Text5, { wrap: "wrap", children: text }),
|
|
1977
|
-
remaining > 0 ? /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
1978
|
-
"... (",
|
|
1979
|
-
remaining,
|
|
1980
|
-
" more lines \u2014 gh issue view ",
|
|
1981
|
-
issueNumber,
|
|
1982
|
-
" for full)"
|
|
1983
|
-
] }) : null
|
|
1984
|
-
] });
|
|
1985
|
-
}
|
|
1986
|
-
function DetailPanel({ issue, task: task2, width }) {
|
|
1987
|
-
if (!(issue || task2)) {
|
|
1988
|
-
return /* @__PURE__ */ jsx5(
|
|
1989
|
-
Box5,
|
|
1990
|
-
{
|
|
1991
|
-
width,
|
|
1992
|
-
borderStyle: "single",
|
|
1993
|
-
borderColor: "gray",
|
|
1994
|
-
flexDirection: "column",
|
|
1995
|
-
paddingX: 1,
|
|
1996
|
-
children: /* @__PURE__ */ jsx5(Text5, { color: "gray", children: "No item selected" })
|
|
1997
|
-
}
|
|
1998
|
-
);
|
|
1999
|
-
}
|
|
2000
|
-
if (issue) {
|
|
2001
|
-
return /* @__PURE__ */ jsxs5(
|
|
2002
|
-
Box5,
|
|
2003
|
-
{
|
|
2004
|
-
width,
|
|
2005
|
-
borderStyle: "single",
|
|
2006
|
-
borderColor: "cyan",
|
|
2007
|
-
flexDirection: "column",
|
|
2008
|
-
paddingX: 1,
|
|
2009
|
-
children: [
|
|
2010
|
-
/* @__PURE__ */ jsxs5(Text5, { color: "cyan", bold: true, children: [
|
|
2011
|
-
"#",
|
|
2012
|
-
issue.number,
|
|
2013
|
-
" ",
|
|
2014
|
-
issue.title
|
|
2015
|
-
] }),
|
|
2016
|
-
/* @__PURE__ */ jsx5(Text5, { children: "" }),
|
|
2017
|
-
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
2018
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "State: " }),
|
|
2019
|
-
/* @__PURE__ */ jsx5(Text5, { color: issue.state === "open" ? "green" : "red", children: issue.state })
|
|
2020
|
-
] }),
|
|
2021
|
-
(issue.assignees ?? []).length > 0 ? /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
2022
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Assignees: " }),
|
|
2023
|
-
/* @__PURE__ */ jsx5(Text5, { children: (issue.assignees ?? []).map((a) => a.login).join(", ") })
|
|
2024
|
-
] }) : null,
|
|
2025
|
-
issue.labels.length > 0 ? /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
2026
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Labels: " }),
|
|
2027
|
-
/* @__PURE__ */ jsx5(Text5, { children: issue.labels.map((l) => l.name).join(", ") })
|
|
2028
|
-
] }) : null,
|
|
2029
|
-
issue.projectStatus ? /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
2030
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Status: " }),
|
|
2031
|
-
/* @__PURE__ */ jsx5(Text5, { color: "magenta", children: issue.projectStatus })
|
|
2032
|
-
] }) : null,
|
|
2033
|
-
issue.targetDate ? /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
2034
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Target: " }),
|
|
2035
|
-
/* @__PURE__ */ jsx5(Text5, { children: issue.targetDate })
|
|
2036
|
-
] }) : null,
|
|
2037
|
-
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
2038
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Updated: " }),
|
|
2039
|
-
/* @__PURE__ */ jsx5(Text5, { children: new Date(issue.updatedAt).toLocaleString() })
|
|
2040
|
-
] }),
|
|
2041
|
-
issue.slackThreadUrl ? /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
2042
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Slack: " }),
|
|
2043
|
-
/* @__PURE__ */ jsx5(Text5, { color: "blue", children: countSlackLinks(issue.body) > 1 ? `${countSlackLinks(issue.body)} links (s opens first)` : "thread (s to open)" })
|
|
2044
|
-
] }) : null,
|
|
2045
|
-
issue.body ? /* @__PURE__ */ jsx5(BodySection, { body: issue.body, issueNumber: issue.number }) : /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
2046
|
-
/* @__PURE__ */ jsx5(Text5, { children: "" }),
|
|
2047
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "(no description)" })
|
|
2048
|
-
] }),
|
|
2049
|
-
/* @__PURE__ */ jsx5(Text5, { children: "" }),
|
|
2050
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", dimColor: true, children: issue.url })
|
|
2051
|
-
]
|
|
2052
|
-
}
|
|
2053
|
-
);
|
|
2054
|
-
}
|
|
2055
|
-
const t = task2;
|
|
2056
|
-
return /* @__PURE__ */ jsxs5(
|
|
2057
|
-
Box5,
|
|
2058
|
-
{
|
|
2059
|
-
width,
|
|
2060
|
-
borderStyle: "single",
|
|
2061
|
-
borderColor: "yellow",
|
|
2062
|
-
flexDirection: "column",
|
|
2063
|
-
paddingX: 1,
|
|
2064
|
-
children: [
|
|
2065
|
-
/* @__PURE__ */ jsx5(Text5, { color: "yellow", bold: true, children: t.title }),
|
|
2066
|
-
/* @__PURE__ */ jsx5(Text5, { children: "" }),
|
|
2067
|
-
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
2068
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Priority: " }),
|
|
2069
|
-
/* @__PURE__ */ jsx5(Text5, { children: PRIORITY_LABELS2[t.priority] ?? "None" })
|
|
2070
|
-
] }),
|
|
2071
|
-
t.dueDate ? /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
2072
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Due: " }),
|
|
2073
|
-
/* @__PURE__ */ jsx5(Text5, { children: new Date(t.dueDate).toLocaleDateString() })
|
|
2074
|
-
] }) : null,
|
|
2075
|
-
(t.tags ?? []).length > 0 ? /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
2076
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Tags: " }),
|
|
2077
|
-
/* @__PURE__ */ jsx5(Text5, { children: t.tags.join(", ") })
|
|
2078
|
-
] }) : null,
|
|
2079
|
-
t.content ? /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
2080
|
-
/* @__PURE__ */ jsx5(Text5, { children: "" }),
|
|
2081
|
-
/* @__PURE__ */ jsx5(Text5, { children: truncateLines(t.content, 8) })
|
|
2082
|
-
] }) : null,
|
|
2083
|
-
(t.items ?? []).length > 0 ? /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
2084
|
-
/* @__PURE__ */ jsx5(Text5, { children: "" }),
|
|
2085
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Checklist:" }),
|
|
2086
|
-
t.items.slice(0, 5).map((item) => /* @__PURE__ */ jsxs5(Text5, { children: [
|
|
2087
|
-
item.status === 2 ? "\u2611" : "\u2610",
|
|
2088
|
-
" ",
|
|
2089
|
-
item.title
|
|
2090
|
-
] }, item.id)),
|
|
2091
|
-
t.items.length > 5 ? /* @__PURE__ */ jsxs5(Text5, { color: "gray", children: [
|
|
2092
|
-
"...and ",
|
|
2093
|
-
t.items.length - 5,
|
|
2094
|
-
" more"
|
|
2095
|
-
] }) : null
|
|
2096
|
-
] }) : null
|
|
2097
|
-
]
|
|
2098
|
-
}
|
|
2099
|
-
);
|
|
2100
|
-
}
|
|
2101
|
-
var SLACK_URL_RE, PRIORITY_LABELS2;
|
|
2102
|
-
var init_detail_panel = __esm({
|
|
2103
|
-
"src/board/components/detail-panel.tsx"() {
|
|
2104
|
-
"use strict";
|
|
2105
|
-
init_types();
|
|
2106
|
-
SLACK_URL_RE = /https:\/\/[^/]+\.slack\.com\/archives\/[A-Z0-9]+\/p[0-9]+/gi;
|
|
2107
|
-
PRIORITY_LABELS2 = {
|
|
2108
|
-
[5 /* High */]: "High",
|
|
2109
|
-
[3 /* Medium */]: "Medium",
|
|
2110
|
-
[1 /* Low */]: "Low",
|
|
2111
|
-
[0 /* None */]: "None"
|
|
2112
|
-
};
|
|
2811
|
+
init_label_picker();
|
|
2113
2812
|
}
|
|
2114
2813
|
});
|
|
2115
2814
|
|
|
2116
2815
|
// src/board/components/focus-mode.tsx
|
|
2117
|
-
import { Box as
|
|
2118
|
-
import { useCallback as
|
|
2119
|
-
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";
|
|
2120
2819
|
function formatTime(secs) {
|
|
2121
2820
|
const m = Math.floor(secs / 60);
|
|
2122
2821
|
const s = secs % 60;
|
|
2123
2822
|
return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
|
2124
2823
|
}
|
|
2125
2824
|
function FocusMode({ label, durationSec, onExit, onEndAction }) {
|
|
2126
|
-
const [remaining, setRemaining] =
|
|
2127
|
-
const [timerDone, setTimerDone] =
|
|
2128
|
-
const bellSentRef =
|
|
2129
|
-
|
|
2825
|
+
const [remaining, setRemaining] = useState8(durationSec);
|
|
2826
|
+
const [timerDone, setTimerDone] = useState8(false);
|
|
2827
|
+
const bellSentRef = useRef8(false);
|
|
2828
|
+
useEffect4(() => {
|
|
2130
2829
|
if (timerDone) return;
|
|
2131
2830
|
const interval = setInterval(() => {
|
|
2132
2831
|
setRemaining((prev) => {
|
|
@@ -2140,13 +2839,13 @@ function FocusMode({ label, durationSec, onExit, onEndAction }) {
|
|
|
2140
2839
|
}, 1e3);
|
|
2141
2840
|
return () => clearInterval(interval);
|
|
2142
2841
|
}, [timerDone]);
|
|
2143
|
-
|
|
2842
|
+
useEffect4(() => {
|
|
2144
2843
|
if (timerDone && !bellSentRef.current) {
|
|
2145
2844
|
bellSentRef.current = true;
|
|
2146
2845
|
process.stdout.write("\x07");
|
|
2147
2846
|
}
|
|
2148
2847
|
}, [timerDone]);
|
|
2149
|
-
const handleInput =
|
|
2848
|
+
const handleInput = useCallback8(
|
|
2150
2849
|
(input2, key) => {
|
|
2151
2850
|
if (key.escape) {
|
|
2152
2851
|
if (timerDone) {
|
|
@@ -2171,25 +2870,25 @@ function FocusMode({ label, durationSec, onExit, onEndAction }) {
|
|
|
2171
2870
|
},
|
|
2172
2871
|
[timerDone, onExit, onEndAction]
|
|
2173
2872
|
);
|
|
2174
|
-
|
|
2873
|
+
useInput7(handleInput);
|
|
2175
2874
|
if (timerDone) {
|
|
2176
|
-
return /* @__PURE__ */
|
|
2177
|
-
/* @__PURE__ */
|
|
2178
|
-
/* @__PURE__ */
|
|
2179
|
-
/* @__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: [
|
|
2180
2879
|
" ",
|
|
2181
2880
|
label
|
|
2182
2881
|
] })
|
|
2183
2882
|
] }),
|
|
2184
|
-
/* @__PURE__ */
|
|
2185
|
-
/* @__PURE__ */
|
|
2186
|
-
/* @__PURE__ */
|
|
2187
|
-
/* @__PURE__ */
|
|
2188
|
-
/* @__PURE__ */
|
|
2189
|
-
/* @__PURE__ */
|
|
2190
|
-
/* @__PURE__ */
|
|
2191
|
-
/* @__PURE__ */
|
|
2192
|
-
/* @__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" })
|
|
2193
2892
|
] })
|
|
2194
2893
|
] });
|
|
2195
2894
|
}
|
|
@@ -2197,21 +2896,21 @@ function FocusMode({ label, durationSec, onExit, onEndAction }) {
|
|
|
2197
2896
|
const barWidth = 20;
|
|
2198
2897
|
const filled = Math.round(progress * barWidth);
|
|
2199
2898
|
const bar = "\u2588".repeat(filled) + "\u2591".repeat(barWidth - filled);
|
|
2200
|
-
return /* @__PURE__ */
|
|
2201
|
-
/* @__PURE__ */
|
|
2202
|
-
/* @__PURE__ */
|
|
2899
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
2900
|
+
/* @__PURE__ */ jsxs7(Box7, { children: [
|
|
2901
|
+
/* @__PURE__ */ jsxs7(Text7, { color: "magenta", bold: true, children: [
|
|
2203
2902
|
"Focus:",
|
|
2204
2903
|
" "
|
|
2205
2904
|
] }),
|
|
2206
|
-
/* @__PURE__ */
|
|
2905
|
+
/* @__PURE__ */ jsx7(Text7, { children: label })
|
|
2207
2906
|
] }),
|
|
2208
|
-
/* @__PURE__ */
|
|
2209
|
-
/* @__PURE__ */
|
|
2210
|
-
/* @__PURE__ */
|
|
2211
|
-
/* @__PURE__ */
|
|
2212
|
-
/* @__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" })
|
|
2213
2912
|
] }),
|
|
2214
|
-
/* @__PURE__ */
|
|
2913
|
+
/* @__PURE__ */ jsx7(Text7, { color: "gray", dimColor: true, children: "Esc to exit focus" })
|
|
2215
2914
|
] });
|
|
2216
2915
|
}
|
|
2217
2916
|
var init_focus_mode = __esm({
|
|
@@ -2221,29 +2920,29 @@ var init_focus_mode = __esm({
|
|
|
2221
2920
|
});
|
|
2222
2921
|
|
|
2223
2922
|
// src/board/components/help-overlay.tsx
|
|
2224
|
-
import { Box as
|
|
2225
|
-
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";
|
|
2226
2925
|
function HelpOverlay({ currentMode, onClose }) {
|
|
2227
|
-
|
|
2926
|
+
useInput8((_input, key) => {
|
|
2228
2927
|
if (key.escape) onClose();
|
|
2229
2928
|
});
|
|
2230
|
-
return /* @__PURE__ */
|
|
2231
|
-
/* @__PURE__ */
|
|
2232
|
-
/* @__PURE__ */
|
|
2233
|
-
/* @__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: [
|
|
2234
2933
|
"mode: ",
|
|
2235
2934
|
currentMode
|
|
2236
2935
|
] })
|
|
2237
2936
|
] }),
|
|
2238
|
-
/* @__PURE__ */
|
|
2239
|
-
SHORTCUTS.map((group) => /* @__PURE__ */
|
|
2240
|
-
/* @__PURE__ */
|
|
2241
|
-
group.items.map((item) => /* @__PURE__ */
|
|
2242
|
-
/* @__PURE__ */
|
|
2243
|
-
/* @__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 })
|
|
2244
2943
|
] }, item.key))
|
|
2245
2944
|
] }, group.category)),
|
|
2246
|
-
/* @__PURE__ */
|
|
2945
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Press ? or Esc to close" })
|
|
2247
2946
|
] });
|
|
2248
2947
|
}
|
|
2249
2948
|
var SHORTCUTS;
|
|
@@ -2271,31 +2970,447 @@ var init_help_overlay = __esm({
|
|
|
2271
2970
|
]
|
|
2272
2971
|
},
|
|
2273
2972
|
{
|
|
2274
|
-
category: "Actions",
|
|
2275
|
-
items: [
|
|
2276
|
-
{ key: "p", desc: "Pick issue (assign + TickTick)" },
|
|
2277
|
-
{ key: "a", desc: "Assign to self" },
|
|
2278
|
-
{ key: "u", desc: "Unassign self" },
|
|
2279
|
-
{ key: "c", desc: "Comment on issue" },
|
|
2280
|
-
{ key: "m", desc: "Move status" },
|
|
2281
|
-
{ key: "s", desc: "Open Slack thread" },
|
|
2282
|
-
{ key: "
|
|
2283
|
-
|
|
2973
|
+
category: "Actions",
|
|
2974
|
+
items: [
|
|
2975
|
+
{ key: "p", desc: "Pick issue (assign + TickTick)" },
|
|
2976
|
+
{ key: "a", desc: "Assign to self" },
|
|
2977
|
+
{ key: "u", desc: "Unassign self" },
|
|
2978
|
+
{ key: "c", desc: "Comment on issue" },
|
|
2979
|
+
{ key: "m", desc: "Move status" },
|
|
2980
|
+
{ key: "s", desc: "Open Slack thread" },
|
|
2981
|
+
{ key: "y", desc: "Copy issue link to clipboard" },
|
|
2982
|
+
{ key: "n", desc: "Create new issue" }
|
|
2983
|
+
]
|
|
2984
|
+
},
|
|
2985
|
+
{
|
|
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
|
|
2284
3357
|
},
|
|
3358
|
+
focusKey
|
|
3359
|
+
) : null,
|
|
3360
|
+
mode === "overlay:label" && selectedIssue && defaultRepo ? /* @__PURE__ */ jsx12(
|
|
3361
|
+
LabelPicker,
|
|
2285
3362
|
{
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
3363
|
+
repo: defaultRepo,
|
|
3364
|
+
currentLabels: selectedIssue.labels.map((l) => l.name),
|
|
3365
|
+
labelCache,
|
|
3366
|
+
onConfirm: onLabelConfirm,
|
|
3367
|
+
onCancel: onExitOverlay,
|
|
3368
|
+
onError: onLabelError
|
|
2291
3369
|
}
|
|
2292
|
-
|
|
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();
|
|
2293
3408
|
}
|
|
2294
3409
|
});
|
|
2295
3410
|
|
|
2296
3411
|
// src/board/components/issue-row.tsx
|
|
2297
|
-
import { Box as
|
|
2298
|
-
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";
|
|
2299
3414
|
function truncate(s, max) {
|
|
2300
3415
|
return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
|
|
2301
3416
|
}
|
|
@@ -2336,30 +3451,30 @@ function IssueRow({ issue, selfLogin, isSelected }) {
|
|
|
2336
3451
|
const labels = (issue.labels ?? []).slice(0, 2);
|
|
2337
3452
|
const target = formatTargetDate(issue.targetDate);
|
|
2338
3453
|
const titleStr = truncate(issue.title, 42).padEnd(42);
|
|
2339
|
-
return /* @__PURE__ */
|
|
2340
|
-
isSelected ? /* @__PURE__ */
|
|
2341
|
-
/* @__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: [
|
|
2342
3457
|
"#",
|
|
2343
3458
|
String(issue.number).padEnd(5)
|
|
2344
3459
|
] }),
|
|
2345
|
-
/* @__PURE__ */
|
|
2346
|
-
isSelected ? /* @__PURE__ */
|
|
2347
|
-
/* @__PURE__ */
|
|
2348
|
-
/* @__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: [
|
|
2349
3464
|
i > 0 ? " " : "",
|
|
2350
|
-
/* @__PURE__ */
|
|
3465
|
+
/* @__PURE__ */ jsxs13(Text12, { color: labelColor(l.name), children: [
|
|
2351
3466
|
"[",
|
|
2352
3467
|
truncate(l.name, 12),
|
|
2353
3468
|
"]"
|
|
2354
3469
|
] })
|
|
2355
3470
|
] }, l.name)) }),
|
|
2356
|
-
/* @__PURE__ */
|
|
2357
|
-
/* @__PURE__ */
|
|
2358
|
-
/* @__PURE__ */
|
|
2359
|
-
/* @__PURE__ */
|
|
2360
|
-
target.text ? /* @__PURE__ */
|
|
2361
|
-
/* @__PURE__ */
|
|
2362
|
-
/* @__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 })
|
|
2363
3478
|
] }) : null
|
|
2364
3479
|
] });
|
|
2365
3480
|
}
|
|
@@ -2382,90 +3497,13 @@ var init_issue_row = __esm({
|
|
|
2382
3497
|
}
|
|
2383
3498
|
});
|
|
2384
3499
|
|
|
2385
|
-
// src/board/components/search-bar.tsx
|
|
2386
|
-
import { TextInput as TextInput3 } from "@inkjs/ui";
|
|
2387
|
-
import { Box as Box9, Text as Text9 } from "ink";
|
|
2388
|
-
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2389
|
-
function SearchBar({ defaultValue, onChange, onSubmit }) {
|
|
2390
|
-
return /* @__PURE__ */ jsxs9(Box9, { children: [
|
|
2391
|
-
/* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "/" }),
|
|
2392
|
-
/* @__PURE__ */ jsx9(
|
|
2393
|
-
TextInput3,
|
|
2394
|
-
{
|
|
2395
|
-
defaultValue,
|
|
2396
|
-
placeholder: "search...",
|
|
2397
|
-
onChange,
|
|
2398
|
-
onSubmit
|
|
2399
|
-
}
|
|
2400
|
-
)
|
|
2401
|
-
] });
|
|
2402
|
-
}
|
|
2403
|
-
var init_search_bar = __esm({
|
|
2404
|
-
"src/board/components/search-bar.tsx"() {
|
|
2405
|
-
"use strict";
|
|
2406
|
-
}
|
|
2407
|
-
});
|
|
2408
|
-
|
|
2409
|
-
// src/board/components/status-picker.tsx
|
|
2410
|
-
import { Box as Box10, Text as Text10, useInput as useInput7 } from "ink";
|
|
2411
|
-
import { useState as useState8 } from "react";
|
|
2412
|
-
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2413
|
-
function StatusPicker({ options, currentStatus, onSelect, onCancel }) {
|
|
2414
|
-
const [selectedIdx, setSelectedIdx] = useState8(() => {
|
|
2415
|
-
const idx = options.findIndex((o) => o.name === currentStatus);
|
|
2416
|
-
return idx >= 0 ? idx : 0;
|
|
2417
|
-
});
|
|
2418
|
-
useInput7((input2, key) => {
|
|
2419
|
-
if (key.escape) return onCancel();
|
|
2420
|
-
if (key.return) {
|
|
2421
|
-
const opt = options[selectedIdx];
|
|
2422
|
-
if (opt) onSelect(opt.id);
|
|
2423
|
-
return;
|
|
2424
|
-
}
|
|
2425
|
-
if (input2 === "j" || key.downArrow) {
|
|
2426
|
-
setSelectedIdx((i) => Math.min(i + 1, options.length - 1));
|
|
2427
|
-
}
|
|
2428
|
-
if (input2 === "k" || key.upArrow) {
|
|
2429
|
-
setSelectedIdx((i) => Math.max(i - 1, 0));
|
|
2430
|
-
}
|
|
2431
|
-
});
|
|
2432
|
-
return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
|
|
2433
|
-
/* @__PURE__ */ jsx10(Text10, { color: "cyan", bold: true, children: "Move to status:" }),
|
|
2434
|
-
options.map((opt, i) => {
|
|
2435
|
-
const isCurrent = opt.name === currentStatus;
|
|
2436
|
-
const isSelected = i === selectedIdx;
|
|
2437
|
-
const prefix = isSelected ? "> " : " ";
|
|
2438
|
-
const suffix = isCurrent ? " (current)" : "";
|
|
2439
|
-
return /* @__PURE__ */ jsxs10(
|
|
2440
|
-
Text10,
|
|
2441
|
-
{
|
|
2442
|
-
...isSelected ? { color: "cyan" } : {},
|
|
2443
|
-
dimColor: isCurrent,
|
|
2444
|
-
children: [
|
|
2445
|
-
prefix,
|
|
2446
|
-
opt.name,
|
|
2447
|
-
suffix
|
|
2448
|
-
]
|
|
2449
|
-
},
|
|
2450
|
-
opt.id
|
|
2451
|
-
);
|
|
2452
|
-
}),
|
|
2453
|
-
/* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "j/k:navigate Enter:select Esc:cancel" })
|
|
2454
|
-
] });
|
|
2455
|
-
}
|
|
2456
|
-
var init_status_picker = __esm({
|
|
2457
|
-
"src/board/components/status-picker.tsx"() {
|
|
2458
|
-
"use strict";
|
|
2459
|
-
}
|
|
2460
|
-
});
|
|
2461
|
-
|
|
2462
3500
|
// src/board/components/task-row.tsx
|
|
2463
|
-
import { Box as
|
|
2464
|
-
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";
|
|
2465
3503
|
function truncate2(s, max) {
|
|
2466
3504
|
return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
|
|
2467
3505
|
}
|
|
2468
|
-
function
|
|
3506
|
+
function formatDue2(dateStr) {
|
|
2469
3507
|
if (!dateStr) return { text: "", color: "gray" };
|
|
2470
3508
|
const d = new Date(dateStr);
|
|
2471
3509
|
const days = Math.ceil((d.getTime() - Date.now()) / 864e5);
|
|
@@ -2480,15 +3518,15 @@ function formatDue(dateStr) {
|
|
|
2480
3518
|
}
|
|
2481
3519
|
function TaskRow({ task: task2, isSelected }) {
|
|
2482
3520
|
const pri = PRIORITY_INDICATORS[task2.priority] ?? DEFAULT_PRIORITY;
|
|
2483
|
-
const due =
|
|
3521
|
+
const due = formatDue2(task2.dueDate);
|
|
2484
3522
|
const titleStr = truncate2(task2.title, 45).padEnd(45);
|
|
2485
|
-
return /* @__PURE__ */
|
|
2486
|
-
isSelected ? /* @__PURE__ */
|
|
2487
|
-
/* @__PURE__ */
|
|
2488
|
-
/* @__PURE__ */
|
|
2489
|
-
isSelected ? /* @__PURE__ */
|
|
2490
|
-
/* @__PURE__ */
|
|
2491
|
-
/* @__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 })
|
|
2492
3530
|
] });
|
|
2493
3531
|
}
|
|
2494
3532
|
var PRIORITY_INDICATORS, DEFAULT_PRIORITY;
|
|
@@ -2506,23 +3544,128 @@ var init_task_row = __esm({
|
|
|
2506
3544
|
}
|
|
2507
3545
|
});
|
|
2508
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
|
+
|
|
2509
3652
|
// src/board/components/toast-container.tsx
|
|
2510
|
-
import { Spinner } from "@inkjs/ui";
|
|
2511
|
-
import { Box as
|
|
2512
|
-
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";
|
|
2513
3656
|
function ToastContainer({ toasts }) {
|
|
2514
3657
|
if (toasts.length === 0) return null;
|
|
2515
|
-
return /* @__PURE__ */
|
|
2516
|
-
/* @__PURE__ */
|
|
2517
|
-
/* @__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: [
|
|
2518
3661
|
" ",
|
|
2519
3662
|
t.message
|
|
2520
3663
|
] })
|
|
2521
|
-
] }) : /* @__PURE__ */
|
|
3664
|
+
] }) : /* @__PURE__ */ jsxs16(Text15, { color: TYPE_COLORS[t.type], children: [
|
|
2522
3665
|
TYPE_PREFIXES[t.type],
|
|
2523
3666
|
" ",
|
|
2524
3667
|
t.message,
|
|
2525
|
-
t.type === "error" ? /* @__PURE__ */
|
|
3668
|
+
t.type === "error" ? /* @__PURE__ */ jsx16(Text15, { color: "gray", children: t.retry ? " [r]etry [d]ismiss" : " [d]ismiss" }) : null
|
|
2526
3669
|
] }) }, t.id)) });
|
|
2527
3670
|
}
|
|
2528
3671
|
var TYPE_COLORS, TYPE_PREFIXES;
|
|
@@ -2544,13 +3687,13 @@ var init_toast_container = __esm({
|
|
|
2544
3687
|
});
|
|
2545
3688
|
|
|
2546
3689
|
// src/board/components/dashboard.tsx
|
|
2547
|
-
import { execFileSync as execFileSync3 } from "child_process";
|
|
2548
|
-
import { Spinner as
|
|
2549
|
-
import { Box as
|
|
2550
|
-
import { useCallback as
|
|
2551
|
-
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";
|
|
2552
3695
|
function isTerminalStatus(status) {
|
|
2553
|
-
return
|
|
3696
|
+
return TERMINAL_STATUS_RE3.test(status);
|
|
2554
3697
|
}
|
|
2555
3698
|
function resolveStatusGroups(statusOptions, configuredGroups) {
|
|
2556
3699
|
if (configuredGroups && configuredGroups.length > 0) {
|
|
@@ -2773,7 +3916,7 @@ function buildFlatRows(repos, tasks, activity, isCollapsed) {
|
|
|
2773
3916
|
}
|
|
2774
3917
|
return rows;
|
|
2775
3918
|
}
|
|
2776
|
-
function
|
|
3919
|
+
function timeAgo3(date) {
|
|
2777
3920
|
const seconds = Math.floor((Date.now() - date.getTime()) / 1e3);
|
|
2778
3921
|
if (seconds < 10) return "just now";
|
|
2779
3922
|
if (seconds < 60) return `${seconds}s ago`;
|
|
@@ -2786,114 +3929,27 @@ function openInBrowser(url) {
|
|
|
2786
3929
|
} catch {
|
|
2787
3930
|
}
|
|
2788
3931
|
}
|
|
2789
|
-
function findSelectedUrl(repos, selectedId) {
|
|
2790
|
-
if (!selectedId?.startsWith("gh:")) return null;
|
|
2791
|
-
for (const rd of repos) {
|
|
2792
|
-
for (const issue of rd.issues) {
|
|
2793
|
-
if (`gh:${rd.repo.name}:${issue.number}` === selectedId) return issue.url;
|
|
2794
|
-
}
|
|
2795
|
-
}
|
|
2796
|
-
return null;
|
|
2797
|
-
}
|
|
2798
|
-
function findSelectedIssueWithRepo(repos, selectedId) {
|
|
2799
|
-
if (!selectedId?.startsWith("gh:")) return null;
|
|
2800
|
-
for (const rd of repos) {
|
|
2801
|
-
for (const issue of rd.issues) {
|
|
2802
|
-
if (`gh:${rd.repo.name}:${issue.number}` === selectedId)
|
|
2803
|
-
return { issue, repoName: rd.repo.name };
|
|
2804
|
-
}
|
|
2805
|
-
}
|
|
2806
|
-
return null;
|
|
2807
|
-
}
|
|
2808
|
-
function isHeaderId(id) {
|
|
2809
|
-
return id != null && (id.startsWith("header:") || id.startsWith("sub:"));
|
|
2810
|
-
}
|
|
2811
|
-
function RowRenderer({ row, selectedId, selfLogin, isMultiSelected }) {
|
|
2812
|
-
switch (row.type) {
|
|
2813
|
-
case "sectionHeader": {
|
|
2814
|
-
const arrow = row.isCollapsed ? "\u25B6" : "\u25BC";
|
|
2815
|
-
const isSel = selectedId === row.navId;
|
|
2816
|
-
return /* @__PURE__ */ jsxs13(Box13, { children: [
|
|
2817
|
-
/* @__PURE__ */ jsxs13(Text13, { color: isSel ? "cyan" : "white", bold: true, children: [
|
|
2818
|
-
arrow,
|
|
2819
|
-
" ",
|
|
2820
|
-
row.label
|
|
2821
|
-
] }),
|
|
2822
|
-
/* @__PURE__ */ jsxs13(Text13, { color: "gray", children: [
|
|
2823
|
-
" ",
|
|
2824
|
-
"(",
|
|
2825
|
-
row.count,
|
|
2826
|
-
" ",
|
|
2827
|
-
row.countLabel,
|
|
2828
|
-
")"
|
|
2829
|
-
] })
|
|
2830
|
-
] });
|
|
2831
|
-
}
|
|
2832
|
-
case "subHeader": {
|
|
2833
|
-
if (row.navId) {
|
|
2834
|
-
const arrow = row.isCollapsed ? "\u25B6" : "\u25BC";
|
|
2835
|
-
const isSel = selectedId === row.navId;
|
|
2836
|
-
return /* @__PURE__ */ jsxs13(Box13, { children: [
|
|
2837
|
-
/* @__PURE__ */ jsxs13(Text13, { color: isSel ? "cyan" : "gray", children: [
|
|
2838
|
-
" ",
|
|
2839
|
-
arrow,
|
|
2840
|
-
" ",
|
|
2841
|
-
row.text
|
|
2842
|
-
] }),
|
|
2843
|
-
/* @__PURE__ */ jsxs13(Text13, { color: "gray", children: [
|
|
2844
|
-
" (",
|
|
2845
|
-
row.count,
|
|
2846
|
-
")"
|
|
2847
|
-
] })
|
|
2848
|
-
] });
|
|
2849
|
-
}
|
|
2850
|
-
return /* @__PURE__ */ jsxs13(Text13, { color: "gray", children: [
|
|
2851
|
-
" ",
|
|
2852
|
-
row.text
|
|
2853
|
-
] });
|
|
2854
|
-
}
|
|
2855
|
-
case "issue": {
|
|
2856
|
-
const checkbox2 = isMultiSelected != null ? isMultiSelected ? "\u2611 " : "\u2610 " : "";
|
|
2857
|
-
return /* @__PURE__ */ jsxs13(Box13, { children: [
|
|
2858
|
-
checkbox2 ? /* @__PURE__ */ jsx13(Text13, { color: isMultiSelected ? "cyan" : "gray", children: checkbox2 }) : null,
|
|
2859
|
-
/* @__PURE__ */ jsx13(IssueRow, { issue: row.issue, selfLogin, isSelected: selectedId === row.navId })
|
|
2860
|
-
] });
|
|
2861
|
-
}
|
|
2862
|
-
case "task": {
|
|
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(TaskRow, { task: row.task, isSelected: selectedId === row.navId })
|
|
2867
|
-
] });
|
|
3932
|
+
function findSelectedUrl(repos, selectedId) {
|
|
3933
|
+
if (!selectedId?.startsWith("gh:")) return null;
|
|
3934
|
+
for (const rd of repos) {
|
|
3935
|
+
for (const issue of rd.issues) {
|
|
3936
|
+
if (`gh:${rd.repo.name}:${issue.number}` === selectedId) return issue.url;
|
|
2868
3937
|
}
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
] }),
|
|
2879
|
-
" ",
|
|
2880
|
-
row.event.summary,
|
|
2881
|
-
" ",
|
|
2882
|
-
/* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
|
|
2883
|
-
"(",
|
|
2884
|
-
row.event.repoShortName,
|
|
2885
|
-
")"
|
|
2886
|
-
] })
|
|
2887
|
-
] });
|
|
3938
|
+
}
|
|
3939
|
+
return null;
|
|
3940
|
+
}
|
|
3941
|
+
function findSelectedIssueWithRepo(repos, selectedId) {
|
|
3942
|
+
if (!selectedId?.startsWith("gh:")) return null;
|
|
3943
|
+
for (const rd of repos) {
|
|
3944
|
+
for (const issue of rd.issues) {
|
|
3945
|
+
if (`gh:${rd.repo.name}:${issue.number}` === selectedId)
|
|
3946
|
+
return { issue, repoName: rd.repo.name };
|
|
2888
3947
|
}
|
|
2889
|
-
case "error":
|
|
2890
|
-
return /* @__PURE__ */ jsxs13(Text13, { color: "red", children: [
|
|
2891
|
-
" Error: ",
|
|
2892
|
-
row.text
|
|
2893
|
-
] });
|
|
2894
|
-
case "gap":
|
|
2895
|
-
return /* @__PURE__ */ jsx13(Text13, { children: "" });
|
|
2896
3948
|
}
|
|
3949
|
+
return null;
|
|
3950
|
+
}
|
|
3951
|
+
function isHeaderId2(id) {
|
|
3952
|
+
return id != null && (id.startsWith("header:") || id.startsWith("sub:"));
|
|
2897
3953
|
}
|
|
2898
3954
|
function Dashboard({ config: config2, options, activeProfile }) {
|
|
2899
3955
|
const { exit } = useApp();
|
|
@@ -2907,7 +3963,9 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
2907
3963
|
consecutiveFailures,
|
|
2908
3964
|
autoRefreshPaused,
|
|
2909
3965
|
refresh,
|
|
2910
|
-
mutateData
|
|
3966
|
+
mutateData,
|
|
3967
|
+
pauseAutoRefresh,
|
|
3968
|
+
resumeAutoRefresh
|
|
2911
3969
|
} = useData(config2, options, refreshMs);
|
|
2912
3970
|
const allRepos = useMemo2(() => data?.repos ?? [], [data?.repos]);
|
|
2913
3971
|
const allTasks = useMemo2(
|
|
@@ -2916,10 +3974,10 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
2916
3974
|
);
|
|
2917
3975
|
const allActivity = useMemo2(() => data?.activity ?? [], [data?.activity]);
|
|
2918
3976
|
const ui = useUIState();
|
|
2919
|
-
const [searchQuery, setSearchQuery] =
|
|
3977
|
+
const [searchQuery, setSearchQuery] = useState11("");
|
|
2920
3978
|
const { toasts, toast, handleErrorAction } = useToast();
|
|
2921
|
-
const [, setTick] =
|
|
2922
|
-
|
|
3979
|
+
const [, setTick] = useState11(0);
|
|
3980
|
+
useEffect6(() => {
|
|
2923
3981
|
const id = setInterval(() => setTick((t) => t + 1), 1e4);
|
|
2924
3982
|
return () => clearInterval(id);
|
|
2925
3983
|
}, []);
|
|
@@ -2938,7 +3996,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
2938
3996
|
[repos, tasks, allActivity.length]
|
|
2939
3997
|
);
|
|
2940
3998
|
const nav = useNavigation(navItems);
|
|
2941
|
-
const getRepoForId =
|
|
3999
|
+
const getRepoForId = useCallback10((id) => {
|
|
2942
4000
|
if (id.startsWith("gh:")) {
|
|
2943
4001
|
const parts = id.split(":");
|
|
2944
4002
|
return parts.length >= 3 ? `${parts[1]}` : null;
|
|
@@ -2947,7 +4005,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
2947
4005
|
return null;
|
|
2948
4006
|
}, []);
|
|
2949
4007
|
const multiSelect = useMultiSelect(getRepoForId);
|
|
2950
|
-
|
|
4008
|
+
useEffect6(() => {
|
|
2951
4009
|
if (multiSelect.count === 0) return;
|
|
2952
4010
|
const validIds = new Set(navItems.map((i) => i.id));
|
|
2953
4011
|
multiSelect.prune(validIds);
|
|
@@ -2961,8 +4019,9 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
2961
4019
|
mutateData,
|
|
2962
4020
|
onOverlayDone: ui.exitOverlay
|
|
2963
4021
|
});
|
|
2964
|
-
const pendingPickRef =
|
|
2965
|
-
const
|
|
4022
|
+
const pendingPickRef = useRef11(null);
|
|
4023
|
+
const labelCacheRef = useRef11({});
|
|
4024
|
+
const handleCreateIssueWithPrompt = useCallback10(
|
|
2966
4025
|
(repo, title, labels) => {
|
|
2967
4026
|
actions.handleCreateIssue(repo, title, labels).then((result) => {
|
|
2968
4027
|
if (result) {
|
|
@@ -2973,7 +4032,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
2973
4032
|
},
|
|
2974
4033
|
[actions, ui]
|
|
2975
4034
|
);
|
|
2976
|
-
const handleConfirmPick =
|
|
4035
|
+
const handleConfirmPick = useCallback10(() => {
|
|
2977
4036
|
const pending = pendingPickRef.current;
|
|
2978
4037
|
pendingPickRef.current = null;
|
|
2979
4038
|
ui.exitOverlay();
|
|
@@ -2991,14 +4050,14 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
2991
4050
|
})
|
|
2992
4051
|
);
|
|
2993
4052
|
}, [config2, toast, refresh, ui]);
|
|
2994
|
-
const handleCancelPick =
|
|
4053
|
+
const handleCancelPick = useCallback10(() => {
|
|
2995
4054
|
pendingPickRef.current = null;
|
|
2996
4055
|
ui.exitOverlay();
|
|
2997
4056
|
}, [ui]);
|
|
2998
|
-
const [focusLabel, setFocusLabel] =
|
|
2999
|
-
const handleEnterFocus =
|
|
4057
|
+
const [focusLabel, setFocusLabel] = useState11(null);
|
|
4058
|
+
const handleEnterFocus = useCallback10(() => {
|
|
3000
4059
|
const id = nav.selectedId;
|
|
3001
|
-
if (!id ||
|
|
4060
|
+
if (!id || isHeaderId2(id)) return;
|
|
3002
4061
|
let label = "";
|
|
3003
4062
|
if (id.startsWith("gh:")) {
|
|
3004
4063
|
const found = findSelectedIssueWithRepo(repos, id);
|
|
@@ -3015,11 +4074,11 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3015
4074
|
setFocusLabel(label);
|
|
3016
4075
|
ui.enterFocus();
|
|
3017
4076
|
}, [nav.selectedId, repos, tasks, config2.repos, ui]);
|
|
3018
|
-
const handleFocusExit =
|
|
4077
|
+
const handleFocusExit = useCallback10(() => {
|
|
3019
4078
|
setFocusLabel(null);
|
|
3020
4079
|
ui.exitToNormal();
|
|
3021
4080
|
}, [ui]);
|
|
3022
|
-
const handleFocusEndAction =
|
|
4081
|
+
const handleFocusEndAction = useCallback10(
|
|
3023
4082
|
(action) => {
|
|
3024
4083
|
switch (action) {
|
|
3025
4084
|
case "restart":
|
|
@@ -3045,13 +4104,13 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3045
4104
|
},
|
|
3046
4105
|
[toast, ui]
|
|
3047
4106
|
);
|
|
3048
|
-
const [focusKey, setFocusKey] =
|
|
4107
|
+
const [focusKey, setFocusKey] = useState11(0);
|
|
3049
4108
|
const { stdout } = useStdout();
|
|
3050
|
-
const [termSize, setTermSize] =
|
|
4109
|
+
const [termSize, setTermSize] = useState11({
|
|
3051
4110
|
cols: stdout?.columns ?? 80,
|
|
3052
4111
|
rows: stdout?.rows ?? 24
|
|
3053
4112
|
});
|
|
3054
|
-
|
|
4113
|
+
useEffect6(() => {
|
|
3055
4114
|
if (!stdout) return;
|
|
3056
4115
|
const onResize = () => setTermSize({ cols: stdout.columns, rows: stdout.rows });
|
|
3057
4116
|
stdout.on("resize", onResize);
|
|
@@ -3068,7 +4127,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3068
4127
|
() => buildFlatRows(repos, tasks, allActivity, nav.isCollapsed),
|
|
3069
4128
|
[repos, tasks, allActivity, nav.isCollapsed]
|
|
3070
4129
|
);
|
|
3071
|
-
const scrollRef =
|
|
4130
|
+
const scrollRef = useRef11(0);
|
|
3072
4131
|
const selectedRowIdx = flatRows.findIndex((r) => r.navId === nav.selectedId);
|
|
3073
4132
|
if (selectedRowIdx >= 0) {
|
|
3074
4133
|
if (selectedRowIdx < scrollRef.current) {
|
|
@@ -3086,7 +4145,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3086
4145
|
const belowCount = flatRows.length - scrollRef.current - viewportHeight;
|
|
3087
4146
|
const selectedItem = useMemo2(() => {
|
|
3088
4147
|
const id = nav.selectedId;
|
|
3089
|
-
if (!id ||
|
|
4148
|
+
if (!id || isHeaderId2(id)) return { issue: null, task: null, repoName: null };
|
|
3090
4149
|
if (id.startsWith("gh:")) {
|
|
3091
4150
|
for (const rd of repos) {
|
|
3092
4151
|
for (const issue of rd.issues) {
|
|
@@ -3106,17 +4165,42 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3106
4165
|
const repoName = multiSelect.count > 0 ? multiSelect.constrainedRepo : selectedItem.repoName;
|
|
3107
4166
|
if (!repoName || repoName === "ticktick") return [];
|
|
3108
4167
|
const rd = repos.find((r) => r.repo.name === repoName);
|
|
3109
|
-
return rd?.statusOptions
|
|
4168
|
+
return rd?.statusOptions ?? [];
|
|
3110
4169
|
}, [selectedItem.repoName, repos, multiSelect.count, multiSelect.constrainedRepo]);
|
|
3111
|
-
const handleOpen =
|
|
4170
|
+
const handleOpen = useCallback10(() => {
|
|
3112
4171
|
const url = findSelectedUrl(repos, nav.selectedId);
|
|
3113
4172
|
if (url) openInBrowser(url);
|
|
3114
4173
|
}, [repos, nav.selectedId]);
|
|
3115
|
-
const handleSlack =
|
|
4174
|
+
const handleSlack = useCallback10(() => {
|
|
3116
4175
|
const found = findSelectedIssueWithRepo(repos, nav.selectedId);
|
|
3117
4176
|
if (!found?.issue.slackThreadUrl) return;
|
|
3118
4177
|
openInBrowser(found.issue.slackThreadUrl);
|
|
3119
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]);
|
|
3120
4204
|
const multiSelectType = useMemo2(() => {
|
|
3121
4205
|
let hasGh = false;
|
|
3122
4206
|
let hasTt = false;
|
|
@@ -3128,7 +4212,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3128
4212
|
if (hasTt) return "ticktick";
|
|
3129
4213
|
return "github";
|
|
3130
4214
|
}, [multiSelect.selected]);
|
|
3131
|
-
const handleBulkAction =
|
|
4215
|
+
const handleBulkAction = useCallback10(
|
|
3132
4216
|
(action) => {
|
|
3133
4217
|
const ids = multiSelect.selected;
|
|
3134
4218
|
switch (action.type) {
|
|
@@ -3172,7 +4256,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3172
4256
|
},
|
|
3173
4257
|
[multiSelect, actions, ui, toast]
|
|
3174
4258
|
);
|
|
3175
|
-
const handleBulkStatusSelect =
|
|
4259
|
+
const handleBulkStatusSelect = useCallback10(
|
|
3176
4260
|
(optionId) => {
|
|
3177
4261
|
const ids = multiSelect.selected;
|
|
3178
4262
|
ui.exitOverlay();
|
|
@@ -3188,168 +4272,35 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3188
4272
|
},
|
|
3189
4273
|
[multiSelect, actions, ui]
|
|
3190
4274
|
);
|
|
3191
|
-
const
|
|
3192
|
-
(
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
return;
|
|
3203
|
-
}
|
|
3204
|
-
if (ui.canNavigate) {
|
|
3205
|
-
if (input2 === "j" || key.downArrow) {
|
|
3206
|
-
nav.moveDown();
|
|
3207
|
-
return;
|
|
3208
|
-
}
|
|
3209
|
-
if (input2 === "k" || key.upArrow) {
|
|
3210
|
-
nav.moveUp();
|
|
3211
|
-
return;
|
|
3212
|
-
}
|
|
3213
|
-
if (key.tab) {
|
|
3214
|
-
if (ui.state.mode === "multiSelect") {
|
|
3215
|
-
multiSelect.clear();
|
|
3216
|
-
ui.clearMultiSelect();
|
|
3217
|
-
}
|
|
3218
|
-
key.shift ? nav.prevSection() : nav.nextSection();
|
|
3219
|
-
return;
|
|
3220
|
-
}
|
|
3221
|
-
}
|
|
3222
|
-
if (ui.state.mode === "multiSelect") {
|
|
3223
|
-
if (input2 === " ") {
|
|
3224
|
-
const id = nav.selectedId;
|
|
3225
|
-
if (id && !isHeaderId(id)) {
|
|
3226
|
-
multiSelect.toggle(id);
|
|
3227
|
-
}
|
|
3228
|
-
return;
|
|
3229
|
-
}
|
|
3230
|
-
if (key.return) {
|
|
3231
|
-
if (multiSelect.count > 0) {
|
|
3232
|
-
ui.enterBulkAction();
|
|
3233
|
-
}
|
|
3234
|
-
return;
|
|
3235
|
-
}
|
|
3236
|
-
if (input2 === "m" && multiSelect.count > 0) {
|
|
3237
|
-
ui.enterBulkAction();
|
|
3238
|
-
return;
|
|
3239
|
-
}
|
|
3240
|
-
return;
|
|
3241
|
-
}
|
|
3242
|
-
if (input2 === "d") {
|
|
3243
|
-
if (handleErrorAction("dismiss")) return;
|
|
3244
|
-
}
|
|
3245
|
-
if (input2 === "r" && handleErrorAction("retry")) return;
|
|
3246
|
-
if (ui.canAct) {
|
|
3247
|
-
if (input2 === "/") {
|
|
3248
|
-
multiSelect.clear();
|
|
3249
|
-
ui.enterSearch();
|
|
3250
|
-
return;
|
|
3251
|
-
}
|
|
3252
|
-
if (input2 === "q") {
|
|
3253
|
-
exit();
|
|
3254
|
-
return;
|
|
3255
|
-
}
|
|
3256
|
-
if (input2 === "r" || input2 === "R") {
|
|
3257
|
-
multiSelect.clear();
|
|
3258
|
-
refresh();
|
|
3259
|
-
return;
|
|
3260
|
-
}
|
|
3261
|
-
if (input2 === "s") {
|
|
3262
|
-
handleSlack();
|
|
3263
|
-
return;
|
|
3264
|
-
}
|
|
3265
|
-
if (input2 === "p") {
|
|
3266
|
-
actions.handlePick();
|
|
3267
|
-
return;
|
|
3268
|
-
}
|
|
3269
|
-
if (input2 === "a") {
|
|
3270
|
-
actions.handleAssign();
|
|
3271
|
-
return;
|
|
3272
|
-
}
|
|
3273
|
-
if (input2 === "u") {
|
|
3274
|
-
actions.handleUnassign();
|
|
3275
|
-
return;
|
|
3276
|
-
}
|
|
3277
|
-
if (input2 === "c") {
|
|
3278
|
-
if (selectedItem.issue) {
|
|
3279
|
-
multiSelect.clear();
|
|
3280
|
-
ui.enterComment();
|
|
3281
|
-
}
|
|
3282
|
-
return;
|
|
3283
|
-
}
|
|
3284
|
-
if (input2 === "m") {
|
|
3285
|
-
if (selectedItem.issue && selectedRepoStatusOptions.length > 0) {
|
|
3286
|
-
multiSelect.clear();
|
|
3287
|
-
ui.enterStatus();
|
|
3288
|
-
} else if (selectedItem.issue) {
|
|
3289
|
-
toast.info("Issue not in a project board");
|
|
3290
|
-
}
|
|
3291
|
-
return;
|
|
3292
|
-
}
|
|
3293
|
-
if (input2 === "n") {
|
|
3294
|
-
multiSelect.clear();
|
|
3295
|
-
ui.enterCreate();
|
|
3296
|
-
return;
|
|
3297
|
-
}
|
|
3298
|
-
if (input2 === "f") {
|
|
3299
|
-
handleEnterFocus();
|
|
3300
|
-
return;
|
|
3301
|
-
}
|
|
3302
|
-
if (input2 === " ") {
|
|
3303
|
-
const id = nav.selectedId;
|
|
3304
|
-
if (id && !isHeaderId(id)) {
|
|
3305
|
-
multiSelect.toggle(id);
|
|
3306
|
-
ui.enterMultiSelect();
|
|
3307
|
-
} else if (isHeaderId(nav.selectedId)) {
|
|
3308
|
-
nav.toggleSection();
|
|
3309
|
-
}
|
|
3310
|
-
return;
|
|
3311
|
-
}
|
|
3312
|
-
if (key.return) {
|
|
3313
|
-
if (isHeaderId(nav.selectedId)) {
|
|
3314
|
-
nav.toggleSection();
|
|
3315
|
-
return;
|
|
3316
|
-
}
|
|
3317
|
-
handleOpen();
|
|
3318
|
-
return;
|
|
3319
|
-
}
|
|
3320
|
-
}
|
|
3321
|
-
},
|
|
3322
|
-
[
|
|
3323
|
-
ui,
|
|
3324
|
-
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: {
|
|
3325
4286
|
exit,
|
|
3326
4287
|
refresh,
|
|
3327
4288
|
handleSlack,
|
|
4289
|
+
handleCopyLink,
|
|
3328
4290
|
handleOpen,
|
|
3329
|
-
actions,
|
|
3330
|
-
selectedItem.issue,
|
|
3331
|
-
selectedRepoStatusOptions.length,
|
|
3332
|
-
toast,
|
|
3333
|
-
nav.selectedId,
|
|
3334
|
-
multiSelect,
|
|
3335
4291
|
handleEnterFocus,
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
if (key.escape) {
|
|
3344
|
-
ui.exitOverlay();
|
|
3345
|
-
setSearchQuery("");
|
|
3346
|
-
}
|
|
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
|
|
3347
4299
|
},
|
|
3348
|
-
|
|
3349
|
-
);
|
|
3350
|
-
useInput8(handleSearchEscape, { isActive: ui.state.mode === "search" });
|
|
4300
|
+
onSearchEscape
|
|
4301
|
+
});
|
|
3351
4302
|
if (status === "loading" && !data) {
|
|
3352
|
-
return /* @__PURE__ */
|
|
4303
|
+
return /* @__PURE__ */ jsx17(Box16, { flexDirection: "column", padding: 1, children: /* @__PURE__ */ jsx17(Spinner4, { label: "Loading dashboard..." }) });
|
|
3353
4304
|
}
|
|
3354
4305
|
const now = data?.fetchedAt ?? /* @__PURE__ */ new Date();
|
|
3355
4306
|
const dateStr = now.toLocaleDateString("en-US", {
|
|
@@ -3357,93 +4308,81 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3357
4308
|
day: "numeric",
|
|
3358
4309
|
year: "numeric"
|
|
3359
4310
|
});
|
|
3360
|
-
return /* @__PURE__ */
|
|
3361
|
-
/* @__PURE__ */
|
|
3362
|
-
/* @__PURE__ */
|
|
3363
|
-
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: [
|
|
3364
4315
|
" [",
|
|
3365
4316
|
activeProfile,
|
|
3366
4317
|
"]"
|
|
3367
4318
|
] }) : null,
|
|
3368
|
-
/* @__PURE__ */
|
|
4319
|
+
/* @__PURE__ */ jsxs17(Text16, { color: "gray", children: [
|
|
3369
4320
|
" ",
|
|
3370
4321
|
"\u2014",
|
|
3371
4322
|
" ",
|
|
3372
4323
|
dateStr
|
|
3373
4324
|
] }),
|
|
3374
|
-
/* @__PURE__ */
|
|
3375
|
-
isRefreshing ? /* @__PURE__ */
|
|
3376
|
-
/* @__PURE__ */
|
|
3377
|
-
/* @__PURE__ */
|
|
3378
|
-
] }) : lastRefresh ? /* @__PURE__ */
|
|
3379
|
-
/* @__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: [
|
|
3380
4331
|
"Updated ",
|
|
3381
|
-
|
|
4332
|
+
timeAgo3(lastRefresh)
|
|
3382
4333
|
] }),
|
|
3383
|
-
consecutiveFailures > 0 ? /* @__PURE__ */
|
|
4334
|
+
consecutiveFailures > 0 ? /* @__PURE__ */ jsx17(Text16, { color: "red", children: " (!)" }) : null
|
|
3384
4335
|
] }) : null,
|
|
3385
|
-
autoRefreshPaused ? /* @__PURE__ */
|
|
4336
|
+
autoRefreshPaused ? /* @__PURE__ */ jsx17(Text16, { color: "yellow", children: " Auto-refresh paused \u2014 press r to retry" }) : null
|
|
3386
4337
|
] }),
|
|
3387
|
-
error ? /* @__PURE__ */
|
|
4338
|
+
error ? /* @__PURE__ */ jsxs17(Text16, { color: "red", children: [
|
|
3388
4339
|
"Error: ",
|
|
3389
4340
|
error
|
|
3390
4341
|
] }) : null,
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
StatusPicker,
|
|
4342
|
+
/* @__PURE__ */ jsx17(
|
|
4343
|
+
OverlayRenderer,
|
|
3394
4344
|
{
|
|
3395
|
-
|
|
4345
|
+
uiState: ui.state,
|
|
4346
|
+
config: config2,
|
|
4347
|
+
selectedRepoStatusOptions,
|
|
3396
4348
|
currentStatus: multiSelect.count > 0 ? void 0 : selectedItem.issue?.projectStatus,
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
}
|
|
3400
|
-
) : null,
|
|
3401
|
-
ui.state.mode === "overlay:create" ? /* @__PURE__ */ jsx13(
|
|
3402
|
-
CreateIssueForm,
|
|
3403
|
-
{
|
|
3404
|
-
repos: config2.repos,
|
|
4349
|
+
onStatusSelect: multiSelect.count > 0 ? handleBulkStatusSelect : actions.handleStatusChange,
|
|
4350
|
+
onExitOverlay: ui.exitOverlay,
|
|
3405
4351
|
defaultRepo: selectedItem.repoName,
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
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)
|
|
3425
4374
|
}
|
|
3426
|
-
)
|
|
3427
|
-
ui.state.mode
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
label: focusLabel,
|
|
3431
|
-
durationSec: config2.board.focusDuration ?? 1500,
|
|
3432
|
-
onExit: handleFocusExit,
|
|
3433
|
-
onEndAction: handleFocusEndAction
|
|
3434
|
-
},
|
|
3435
|
-
focusKey
|
|
3436
|
-
) : null,
|
|
3437
|
-
!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: [
|
|
3438
|
-
/* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", flexGrow: 1, children: [
|
|
3439
|
-
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: [
|
|
3440
4379
|
" ",
|
|
3441
4380
|
"\u25B2",
|
|
3442
4381
|
" ",
|
|
3443
4382
|
aboveCount,
|
|
3444
4383
|
" more above"
|
|
3445
4384
|
] }) : null,
|
|
3446
|
-
visibleRows.map((row) => /* @__PURE__ */
|
|
4385
|
+
visibleRows.map((row) => /* @__PURE__ */ jsx17(
|
|
3447
4386
|
RowRenderer,
|
|
3448
4387
|
{
|
|
3449
4388
|
row,
|
|
@@ -3453,7 +4392,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3453
4392
|
},
|
|
3454
4393
|
row.key
|
|
3455
4394
|
)),
|
|
3456
|
-
hasMoreBelow ? /* @__PURE__ */
|
|
4395
|
+
hasMoreBelow ? /* @__PURE__ */ jsxs17(Text16, { color: "gray", dimColor: true, children: [
|
|
3457
4396
|
" ",
|
|
3458
4397
|
"\u25BC",
|
|
3459
4398
|
" ",
|
|
@@ -3461,7 +4400,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3461
4400
|
" more below"
|
|
3462
4401
|
] }) : null
|
|
3463
4402
|
] }),
|
|
3464
|
-
showDetailPanel ? /* @__PURE__ */
|
|
4403
|
+
showDetailPanel ? /* @__PURE__ */ jsx17(Box16, { marginLeft: 1, width: detailPanelWidth, children: /* @__PURE__ */ jsx17(
|
|
3465
4404
|
DetailPanel,
|
|
3466
4405
|
{
|
|
3467
4406
|
issue: selectedItem.issue,
|
|
@@ -3470,25 +4409,16 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3470
4409
|
}
|
|
3471
4410
|
) }) : null
|
|
3472
4411
|
] }) : null,
|
|
3473
|
-
|
|
3474
|
-
ui.state.mode === "
|
|
3475
|
-
|
|
3476
|
-
{
|
|
3477
|
-
issueNumber: selectedItem.issue.number,
|
|
3478
|
-
onSubmit: actions.handleComment,
|
|
3479
|
-
onCancel: ui.exitOverlay
|
|
3480
|
-
}
|
|
3481
|
-
) : null,
|
|
3482
|
-
/* @__PURE__ */ jsx13(ToastContainer, { toasts }),
|
|
3483
|
-
/* @__PURE__ */ jsx13(Box13, { children: ui.state.mode === "multiSelect" ? /* @__PURE__ */ jsxs13(Fragment4, { children: [
|
|
3484
|
-
/* @__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: [
|
|
3485
4415
|
multiSelect.count,
|
|
3486
4416
|
" selected"
|
|
3487
4417
|
] }),
|
|
3488
|
-
/* @__PURE__ */
|
|
3489
|
-
] }) : ui.state.mode === "focus" ? /* @__PURE__ */
|
|
3490
|
-
/* @__PURE__ */
|
|
3491
|
-
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: [
|
|
3492
4422
|
' filter: "',
|
|
3493
4423
|
searchQuery,
|
|
3494
4424
|
'"'
|
|
@@ -3496,29 +4426,23 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3496
4426
|
] }) })
|
|
3497
4427
|
] });
|
|
3498
4428
|
}
|
|
3499
|
-
var
|
|
4429
|
+
var TERMINAL_STATUS_RE3, PRIORITY_RANK, CHROME_ROWS;
|
|
3500
4430
|
var init_dashboard = __esm({
|
|
3501
4431
|
"src/board/components/dashboard.tsx"() {
|
|
3502
4432
|
"use strict";
|
|
4433
|
+
init_clipboard();
|
|
3503
4434
|
init_use_actions();
|
|
3504
4435
|
init_use_data();
|
|
4436
|
+
init_use_keyboard();
|
|
3505
4437
|
init_use_multi_select();
|
|
3506
4438
|
init_use_navigation();
|
|
3507
4439
|
init_use_toast();
|
|
3508
4440
|
init_use_ui_state();
|
|
3509
|
-
init_bulk_action_menu();
|
|
3510
|
-
init_comment_input();
|
|
3511
|
-
init_confirm_prompt();
|
|
3512
|
-
init_create_issue_form();
|
|
3513
4441
|
init_detail_panel();
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
init_issue_row();
|
|
3517
|
-
init_search_bar();
|
|
3518
|
-
init_status_picker();
|
|
3519
|
-
init_task_row();
|
|
4442
|
+
init_overlay_renderer();
|
|
4443
|
+
init_row_renderer();
|
|
3520
4444
|
init_toast_container();
|
|
3521
|
-
|
|
4445
|
+
TERMINAL_STATUS_RE3 = /^(done|shipped|won't|wont|closed|complete|completed)$/i;
|
|
3522
4446
|
PRIORITY_RANK = {
|
|
3523
4447
|
"priority:critical": 0,
|
|
3524
4448
|
"priority:high": 1,
|
|
@@ -3535,17 +4459,19 @@ __export(live_exports, {
|
|
|
3535
4459
|
runLiveDashboard: () => runLiveDashboard
|
|
3536
4460
|
});
|
|
3537
4461
|
import { render } from "ink";
|
|
3538
|
-
import { jsx as
|
|
4462
|
+
import { jsx as jsx18 } from "react/jsx-runtime";
|
|
3539
4463
|
async function runLiveDashboard(config2, options, activeProfile) {
|
|
3540
|
-
const
|
|
3541
|
-
/* @__PURE__ */
|
|
4464
|
+
const instance = render(
|
|
4465
|
+
/* @__PURE__ */ jsx18(Dashboard, { config: config2, options, activeProfile: activeProfile ?? null })
|
|
3542
4466
|
);
|
|
3543
|
-
|
|
4467
|
+
setInkInstance(instance);
|
|
4468
|
+
await instance.waitUntilExit();
|
|
3544
4469
|
}
|
|
3545
4470
|
var init_live = __esm({
|
|
3546
4471
|
"src/board/live.tsx"() {
|
|
3547
4472
|
"use strict";
|
|
3548
4473
|
init_dashboard();
|
|
4474
|
+
init_ink_instance();
|
|
3549
4475
|
}
|
|
3550
4476
|
});
|
|
3551
4477
|
|
|
@@ -3911,8 +4837,10 @@ var init_format_static = __esm({
|
|
|
3911
4837
|
});
|
|
3912
4838
|
|
|
3913
4839
|
// src/cli.ts
|
|
4840
|
+
init_ai();
|
|
3914
4841
|
init_api();
|
|
3915
4842
|
init_config();
|
|
4843
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
3916
4844
|
import { Command } from "commander";
|
|
3917
4845
|
|
|
3918
4846
|
// src/init.ts
|
|
@@ -4582,7 +5510,7 @@ function resolveProjectId(projectId) {
|
|
|
4582
5510
|
process.exit(1);
|
|
4583
5511
|
}
|
|
4584
5512
|
var program = new Command();
|
|
4585
|
-
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) => {
|
|
4586
5514
|
const opts = thisCommand.opts();
|
|
4587
5515
|
if (opts.json) setFormat("json");
|
|
4588
5516
|
if (opts.human) setFormat("human");
|
|
@@ -4931,6 +5859,53 @@ config.command("profile:default [name]").description("Set or show the default bo
|
|
|
4931
5859
|
printSuccess(`Default profile set to "${name}".`);
|
|
4932
5860
|
}
|
|
4933
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);
|
|
4934
5909
|
program.parseAsync().catch((err) => {
|
|
4935
5910
|
const message = err instanceof Error ? err.message : String(err);
|
|
4936
5911
|
console.error(`Error: ${message}`);
|