clairo 1.2.3 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -5
- package/dist/cli.js +1981 -254
- package/package.json +3 -1
package/dist/cli.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
import meow from "meow";
|
|
5
5
|
|
|
6
6
|
// src/app.tsx
|
|
7
|
-
import { useCallback as
|
|
8
|
-
import { Box as
|
|
7
|
+
import { useCallback as useCallback11, useMemo as useMemo4, useState as useState21 } from "react";
|
|
8
|
+
import { Box as Box22, Text as Text19, useApp, useInput as useInput18 } from "ink";
|
|
9
9
|
|
|
10
10
|
// src/components/github/GitHubView.tsx
|
|
11
11
|
import { useCallback as useCallback8, useEffect as useEffect8, useRef as useRef5, useState as useState11 } from "react";
|
|
@@ -19,8 +19,8 @@ import { useCallback, useEffect as useEffect2, useMemo, useState as useState2 }
|
|
|
19
19
|
// src/lib/duckEvents.ts
|
|
20
20
|
var listeners = /* @__PURE__ */ new Set();
|
|
21
21
|
var duckEvents = {
|
|
22
|
-
emit: (event) => {
|
|
23
|
-
listeners.forEach((fn) => fn(event));
|
|
22
|
+
emit: (event, payload) => {
|
|
23
|
+
listeners.forEach((fn) => fn(event, payload));
|
|
24
24
|
},
|
|
25
25
|
subscribe: (fn) => {
|
|
26
26
|
listeners.add(fn);
|
|
@@ -579,6 +579,16 @@ function usePullRequests() {
|
|
|
579
579
|
if (result.success) {
|
|
580
580
|
setPrDetails(result.data);
|
|
581
581
|
setErrors((prev) => ({ ...prev, details: void 0 }));
|
|
582
|
+
const payload = { prNumber: result.data.number, prTitle: result.data.title };
|
|
583
|
+
if (result.data.state === "MERGED") {
|
|
584
|
+
duckEvents.emit("pr:merged", payload);
|
|
585
|
+
} else if (result.data.reviewDecision === "APPROVED") {
|
|
586
|
+
duckEvents.emit("pr:approved", payload);
|
|
587
|
+
} else if (result.data.reviewDecision === "CHANGES_REQUESTED") {
|
|
588
|
+
duckEvents.emit("pr:changes-requested", payload);
|
|
589
|
+
} else if (result.data.reviews.length > 0) {
|
|
590
|
+
duckEvents.emit("pr:reviewed", payload);
|
|
591
|
+
}
|
|
582
592
|
} else {
|
|
583
593
|
setErrors((prev) => ({ ...prev, details: result.error }));
|
|
584
594
|
}
|
|
@@ -866,7 +876,8 @@ function createAuthHeader(email, apiToken) {
|
|
|
866
876
|
return `Basic ${credentials}`;
|
|
867
877
|
}
|
|
868
878
|
async function jiraFetch(auth, endpoint, options) {
|
|
869
|
-
const
|
|
879
|
+
const prefix = (options == null ? void 0 : options.apiPrefix) ?? "/rest/api/3";
|
|
880
|
+
const url = `${auth.siteUrl}${prefix}${endpoint}`;
|
|
870
881
|
const method = (options == null ? void 0 : options.method) ?? "GET";
|
|
871
882
|
try {
|
|
872
883
|
const headers = {
|
|
@@ -911,6 +922,21 @@ async function validateCredentials(auth) {
|
|
|
911
922
|
}
|
|
912
923
|
return { success: true, data: result.data };
|
|
913
924
|
}
|
|
925
|
+
async function getCurrentUser(auth) {
|
|
926
|
+
const result = await jiraFetch(auth, "/myself");
|
|
927
|
+
if (!result.ok) {
|
|
928
|
+
if (result.status === 401 || result.status === 403) {
|
|
929
|
+
return { success: false, error: "Authentication failed", errorType: "auth_error" };
|
|
930
|
+
}
|
|
931
|
+
return {
|
|
932
|
+
success: false,
|
|
933
|
+
error: result.error ?? "Failed to fetch current user",
|
|
934
|
+
errorType: "api_error"
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
const data = result.data;
|
|
938
|
+
return { success: true, data: { accountId: data.accountId, displayName: data.displayName } };
|
|
939
|
+
}
|
|
914
940
|
async function getIssue(auth, ticketKey) {
|
|
915
941
|
const result = await jiraFetch(auth, `/issue/${ticketKey}?fields=summary,status`);
|
|
916
942
|
if (!result.ok) {
|
|
@@ -990,6 +1016,380 @@ async function applyTransition(auth, ticketKey, transitionId) {
|
|
|
990
1016
|
}
|
|
991
1017
|
return { success: true, data: null };
|
|
992
1018
|
}
|
|
1019
|
+
async function getIssueDetail(auth, issueKey) {
|
|
1020
|
+
const result = await jiraFetch(
|
|
1021
|
+
auth,
|
|
1022
|
+
`/issue/${issueKey}?fields=summary,status,description,assignee,reporter,comment`
|
|
1023
|
+
);
|
|
1024
|
+
if (!result.ok) {
|
|
1025
|
+
if (result.status === 401 || result.status === 403) {
|
|
1026
|
+
return { success: false, error: "Authentication failed", errorType: "auth_error" };
|
|
1027
|
+
}
|
|
1028
|
+
if (result.status === 404) {
|
|
1029
|
+
return { success: false, error: `Issue ${issueKey} not found`, errorType: "invalid_ticket" };
|
|
1030
|
+
}
|
|
1031
|
+
return {
|
|
1032
|
+
success: false,
|
|
1033
|
+
error: result.error ?? "Failed to fetch issue details",
|
|
1034
|
+
errorType: "api_error"
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
return { success: true, data: result.data };
|
|
1038
|
+
}
|
|
1039
|
+
async function assignIssue(auth, issueKey, accountId) {
|
|
1040
|
+
const result = await jiraFetch(auth, `/issue/${issueKey}/assignee`, {
|
|
1041
|
+
method: "PUT",
|
|
1042
|
+
body: { accountId }
|
|
1043
|
+
});
|
|
1044
|
+
if (!result.ok) {
|
|
1045
|
+
if (result.status === 401 || result.status === 403) {
|
|
1046
|
+
return { success: false, error: "Authentication failed", errorType: "auth_error" };
|
|
1047
|
+
}
|
|
1048
|
+
if (result.status === 404) {
|
|
1049
|
+
return { success: false, error: `Issue ${issueKey} not found`, errorType: "invalid_ticket" };
|
|
1050
|
+
}
|
|
1051
|
+
return {
|
|
1052
|
+
success: false,
|
|
1053
|
+
error: result.error ?? "Failed to assign issue",
|
|
1054
|
+
errorType: "api_error"
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
return { success: true, data: null };
|
|
1058
|
+
}
|
|
1059
|
+
async function unassignIssue(auth, issueKey) {
|
|
1060
|
+
const result = await jiraFetch(auth, `/issue/${issueKey}/assignee`, {
|
|
1061
|
+
method: "PUT",
|
|
1062
|
+
body: { accountId: null }
|
|
1063
|
+
});
|
|
1064
|
+
if (!result.ok) {
|
|
1065
|
+
if (result.status === 401 || result.status === 403) {
|
|
1066
|
+
return { success: false, error: "Authentication failed", errorType: "auth_error" };
|
|
1067
|
+
}
|
|
1068
|
+
if (result.status === 404) {
|
|
1069
|
+
return { success: false, error: `Issue ${issueKey} not found`, errorType: "invalid_ticket" };
|
|
1070
|
+
}
|
|
1071
|
+
return {
|
|
1072
|
+
success: false,
|
|
1073
|
+
error: result.error ?? "Failed to unassign issue",
|
|
1074
|
+
errorType: "api_error"
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
return { success: true, data: null };
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// src/lib/jira/search.ts
|
|
1081
|
+
async function searchIssues(auth, jql, opts) {
|
|
1082
|
+
const params = new URLSearchParams({
|
|
1083
|
+
jql,
|
|
1084
|
+
fields: "summary,status,assignee,priority,issuetype,sprint,closedSprints",
|
|
1085
|
+
startAt: String((opts == null ? void 0 : opts.startAt) ?? 0),
|
|
1086
|
+
maxResults: String((opts == null ? void 0 : opts.maxResults) ?? 50)
|
|
1087
|
+
});
|
|
1088
|
+
const result = await jiraFetch(auth, `/search?${params.toString()}`);
|
|
1089
|
+
if (!result.ok) {
|
|
1090
|
+
if (result.status === 401 || result.status === 403) {
|
|
1091
|
+
return { success: false, error: "Authentication failed", errorType: "auth_error" };
|
|
1092
|
+
}
|
|
1093
|
+
return {
|
|
1094
|
+
success: false,
|
|
1095
|
+
error: result.error ?? "Failed to search issues",
|
|
1096
|
+
errorType: "api_error"
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
return { success: true, data: result.data };
|
|
1100
|
+
}
|
|
1101
|
+
async function getFilterJql(auth, filterId) {
|
|
1102
|
+
const result = await jiraFetch(auth, `/filter/${filterId}`);
|
|
1103
|
+
if (!result.ok) {
|
|
1104
|
+
if (result.status === 401 || result.status === 403) {
|
|
1105
|
+
return { success: false, error: "Authentication failed", errorType: "auth_error" };
|
|
1106
|
+
}
|
|
1107
|
+
if (result.status === 404) {
|
|
1108
|
+
return { success: false, error: `Filter ${filterId} not found`, errorType: "api_error" };
|
|
1109
|
+
}
|
|
1110
|
+
return {
|
|
1111
|
+
success: false,
|
|
1112
|
+
error: result.error ?? "Failed to fetch filter",
|
|
1113
|
+
errorType: "api_error"
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
const data = result.data;
|
|
1117
|
+
return { success: true, data: data.jql };
|
|
1118
|
+
}
|
|
1119
|
+
async function getBoardIssues(auth, boardId, opts) {
|
|
1120
|
+
const params = new URLSearchParams({
|
|
1121
|
+
fields: "summary,status,assignee,priority,issuetype,sprint,closedSprints",
|
|
1122
|
+
startAt: String((opts == null ? void 0 : opts.startAt) ?? 0),
|
|
1123
|
+
maxResults: String((opts == null ? void 0 : opts.maxResults) ?? 50)
|
|
1124
|
+
});
|
|
1125
|
+
if (opts == null ? void 0 : opts.jql) {
|
|
1126
|
+
params.set("jql", opts.jql);
|
|
1127
|
+
}
|
|
1128
|
+
const result = await jiraFetch(auth, `/board/${boardId}/issue?${params.toString()}`, {
|
|
1129
|
+
apiPrefix: "/rest/agile/1.0"
|
|
1130
|
+
});
|
|
1131
|
+
if (!result.ok) {
|
|
1132
|
+
if (result.status === 401 || result.status === 403) {
|
|
1133
|
+
return { success: false, error: "Authentication failed", errorType: "auth_error" };
|
|
1134
|
+
}
|
|
1135
|
+
if (result.status === 404) {
|
|
1136
|
+
return { success: false, error: `Board ${boardId} not found`, errorType: "api_error" };
|
|
1137
|
+
}
|
|
1138
|
+
return {
|
|
1139
|
+
success: false,
|
|
1140
|
+
error: result.error ?? "Failed to fetch board issues",
|
|
1141
|
+
errorType: "api_error"
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
return { success: true, data: result.data };
|
|
1145
|
+
}
|
|
1146
|
+
function escapeJql(text) {
|
|
1147
|
+
return text.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
1148
|
+
}
|
|
1149
|
+
function appendTextSearch(jql, searchText) {
|
|
1150
|
+
const escaped = escapeJql(searchText);
|
|
1151
|
+
return `(${jql}) AND text ~ "${escaped}"`;
|
|
1152
|
+
}
|
|
1153
|
+
async function fetchViewIssues(auth, view, opts) {
|
|
1154
|
+
const { searchText, ...pageOpts } = opts ?? {};
|
|
1155
|
+
switch (view.source.type) {
|
|
1156
|
+
case "jql": {
|
|
1157
|
+
const jql = searchText ? appendTextSearch(view.source.jql, searchText) : view.source.jql;
|
|
1158
|
+
return searchIssues(auth, jql, pageOpts);
|
|
1159
|
+
}
|
|
1160
|
+
case "filter": {
|
|
1161
|
+
const filterResult = await getFilterJql(auth, view.source.filterId);
|
|
1162
|
+
if (!filterResult.success) return filterResult;
|
|
1163
|
+
const jql = searchText ? appendTextSearch(filterResult.data, searchText) : filterResult.data;
|
|
1164
|
+
return searchIssues(auth, jql, pageOpts);
|
|
1165
|
+
}
|
|
1166
|
+
case "board": {
|
|
1167
|
+
const jql = searchText ? `text ~ "${escapeJql(searchText)}"` : void 0;
|
|
1168
|
+
return getBoardIssues(auth, view.source.boardId, { ...pageOpts, jql });
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
// src/lib/jira/views.ts
|
|
1174
|
+
function getSavedViews(repoPath) {
|
|
1175
|
+
const config = getRepoConfig(repoPath);
|
|
1176
|
+
return config.savedJiraViews ?? [];
|
|
1177
|
+
}
|
|
1178
|
+
function addSavedView(repoPath, name, url, source) {
|
|
1179
|
+
const views = getSavedViews(repoPath);
|
|
1180
|
+
const view = {
|
|
1181
|
+
id: Date.now().toString(36),
|
|
1182
|
+
name,
|
|
1183
|
+
url,
|
|
1184
|
+
source,
|
|
1185
|
+
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1186
|
+
};
|
|
1187
|
+
updateRepoConfig(repoPath, { savedJiraViews: [...views, view] });
|
|
1188
|
+
return view;
|
|
1189
|
+
}
|
|
1190
|
+
function renameSavedView(repoPath, viewId, newName) {
|
|
1191
|
+
const views = getSavedViews(repoPath);
|
|
1192
|
+
updateRepoConfig(repoPath, {
|
|
1193
|
+
savedJiraViews: views.map((v) => v.id === viewId ? { ...v, name: newName } : v)
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
function removeSavedView(repoPath, viewId) {
|
|
1197
|
+
const views = getSavedViews(repoPath);
|
|
1198
|
+
updateRepoConfig(repoPath, {
|
|
1199
|
+
savedJiraViews: views.filter((v) => v.id !== viewId)
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
// src/lib/jira/url-parser.ts
|
|
1204
|
+
function parseJiraUrl(input) {
|
|
1205
|
+
const trimmed = input.trim();
|
|
1206
|
+
let url;
|
|
1207
|
+
try {
|
|
1208
|
+
url = new URL(trimmed);
|
|
1209
|
+
} catch {
|
|
1210
|
+
return null;
|
|
1211
|
+
}
|
|
1212
|
+
const filterId = url.searchParams.get("filter");
|
|
1213
|
+
if (filterId && /^\d+$/.test(filterId)) {
|
|
1214
|
+
return { type: "filter", filterId };
|
|
1215
|
+
}
|
|
1216
|
+
const jql = url.searchParams.get("jql");
|
|
1217
|
+
if (jql) {
|
|
1218
|
+
return { type: "jql", jql };
|
|
1219
|
+
}
|
|
1220
|
+
const boardMatch = url.pathname.match(/\/boards\/(\d+)/);
|
|
1221
|
+
if (boardMatch) {
|
|
1222
|
+
return { type: "board", boardId: boardMatch[1] };
|
|
1223
|
+
}
|
|
1224
|
+
return null;
|
|
1225
|
+
}
|
|
1226
|
+
function generateViewName(input) {
|
|
1227
|
+
const source = parseJiraUrl(input);
|
|
1228
|
+
if (!source) return "Jira View";
|
|
1229
|
+
switch (source.type) {
|
|
1230
|
+
case "filter":
|
|
1231
|
+
return `Filter #${source.filterId}`;
|
|
1232
|
+
case "jql":
|
|
1233
|
+
return truncate(source.jql, 40);
|
|
1234
|
+
case "board":
|
|
1235
|
+
return `Board #${source.boardId}`;
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
function truncate(str, maxLen) {
|
|
1239
|
+
if (str.length <= maxLen) return str;
|
|
1240
|
+
return str.slice(0, maxLen - 1) + "\u2026";
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
// src/lib/jira/adf-to-markdown.ts
|
|
1244
|
+
function adfToMarkdown(node) {
|
|
1245
|
+
if (!node || typeof node !== "object") return "";
|
|
1246
|
+
return processNode(node).trimEnd();
|
|
1247
|
+
}
|
|
1248
|
+
function processNode(node, indent = "") {
|
|
1249
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
1250
|
+
switch (node.type) {
|
|
1251
|
+
case "doc":
|
|
1252
|
+
return processChildren(node, indent);
|
|
1253
|
+
case "paragraph":
|
|
1254
|
+
return processChildren(node, indent) + "\n\n";
|
|
1255
|
+
case "heading": {
|
|
1256
|
+
const level = ((_a = node.attrs) == null ? void 0 : _a.level) ?? 1;
|
|
1257
|
+
const prefix = "#".repeat(level);
|
|
1258
|
+
return `${prefix} ${processChildren(node, indent)}
|
|
1259
|
+
|
|
1260
|
+
`;
|
|
1261
|
+
}
|
|
1262
|
+
case "text":
|
|
1263
|
+
return applyMarks(node.text ?? "", node.marks);
|
|
1264
|
+
case "hardBreak":
|
|
1265
|
+
return "\n";
|
|
1266
|
+
case "bulletList":
|
|
1267
|
+
return processList(node, indent, "bullet") + "\n";
|
|
1268
|
+
case "orderedList":
|
|
1269
|
+
return processList(node, indent, "ordered") + "\n";
|
|
1270
|
+
case "listItem":
|
|
1271
|
+
return processChildren(node, indent);
|
|
1272
|
+
case "codeBlock": {
|
|
1273
|
+
const lang = ((_b = node.attrs) == null ? void 0 : _b.language) ?? "";
|
|
1274
|
+
const code = processChildren(node, indent);
|
|
1275
|
+
return `\`\`\`${lang}
|
|
1276
|
+
${code}
|
|
1277
|
+
\`\`\`
|
|
1278
|
+
|
|
1279
|
+
`;
|
|
1280
|
+
}
|
|
1281
|
+
case "blockquote": {
|
|
1282
|
+
const content = processChildren(node, indent);
|
|
1283
|
+
return content.split("\n").map((line) => `> ${line}`).join("\n") + "\n\n";
|
|
1284
|
+
}
|
|
1285
|
+
case "rule":
|
|
1286
|
+
return "---\n\n";
|
|
1287
|
+
case "mention":
|
|
1288
|
+
return `@${((_c = node.attrs) == null ? void 0 : _c.text) ?? "unknown"}`;
|
|
1289
|
+
case "emoji":
|
|
1290
|
+
return ((_d = node.attrs) == null ? void 0 : _d.shortName) ?? ((_e = node.attrs) == null ? void 0 : _e.text) ?? "";
|
|
1291
|
+
case "inlineCard": {
|
|
1292
|
+
const url = (_f = node.attrs) == null ? void 0 : _f.url;
|
|
1293
|
+
return url ? `[${url}](${url})` : "";
|
|
1294
|
+
}
|
|
1295
|
+
case "mediaSingle":
|
|
1296
|
+
case "mediaGroup":
|
|
1297
|
+
return processChildren(node, indent);
|
|
1298
|
+
case "media":
|
|
1299
|
+
return "[Attachment]\n\n";
|
|
1300
|
+
case "table":
|
|
1301
|
+
return processTable(node) + "\n";
|
|
1302
|
+
case "panel": {
|
|
1303
|
+
const panelType = ((_g = node.attrs) == null ? void 0 : _g.panelType) ?? "info";
|
|
1304
|
+
const label = panelType.charAt(0).toUpperCase() + panelType.slice(1);
|
|
1305
|
+
return `[${label}] ${processChildren(node, indent)}
|
|
1306
|
+
`;
|
|
1307
|
+
}
|
|
1308
|
+
default:
|
|
1309
|
+
if (node.content) {
|
|
1310
|
+
return processChildren(node, indent);
|
|
1311
|
+
}
|
|
1312
|
+
return node.text ?? "";
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
function processChildren(node, indent = "") {
|
|
1316
|
+
if (!node.content) return "";
|
|
1317
|
+
return node.content.map((child) => processNode(child, indent)).join("");
|
|
1318
|
+
}
|
|
1319
|
+
function processList(node, indent, style) {
|
|
1320
|
+
if (!node.content) return "";
|
|
1321
|
+
return node.content.map((item, idx) => {
|
|
1322
|
+
const prefix = style === "bullet" ? "- " : `${idx + 1}. `;
|
|
1323
|
+
const childContent = (item.content ?? []).map((child) => {
|
|
1324
|
+
if (child.type === "bulletList" || child.type === "orderedList") {
|
|
1325
|
+
return processList(child, indent + " ", child.type === "bulletList" ? "bullet" : "ordered");
|
|
1326
|
+
}
|
|
1327
|
+
if (child.type === "paragraph") {
|
|
1328
|
+
return processChildren(child, indent);
|
|
1329
|
+
}
|
|
1330
|
+
return processNode(child, indent + " ");
|
|
1331
|
+
}).join("");
|
|
1332
|
+
return `${indent}${prefix}${childContent}`;
|
|
1333
|
+
}).join("\n");
|
|
1334
|
+
}
|
|
1335
|
+
function processTable(node) {
|
|
1336
|
+
if (!node.content) return "";
|
|
1337
|
+
const rows = [];
|
|
1338
|
+
let hasHeader = false;
|
|
1339
|
+
for (const row of node.content) {
|
|
1340
|
+
if (row.type !== "tableRow") continue;
|
|
1341
|
+
const cells = [];
|
|
1342
|
+
for (const cell of row.content ?? []) {
|
|
1343
|
+
if (cell.type === "tableHeader") hasHeader = true;
|
|
1344
|
+
const text = processChildren(cell).replace(/\n/g, " ").trim();
|
|
1345
|
+
cells.push(text);
|
|
1346
|
+
}
|
|
1347
|
+
rows.push(cells);
|
|
1348
|
+
}
|
|
1349
|
+
if (rows.length === 0) return "";
|
|
1350
|
+
const colCount = Math.max(...rows.map((r) => r.length));
|
|
1351
|
+
const lines = [];
|
|
1352
|
+
for (let i = 0; i < rows.length; i++) {
|
|
1353
|
+
const row = rows[i];
|
|
1354
|
+
while (row.length < colCount) row.push("");
|
|
1355
|
+
lines.push(`| ${row.join(" | ")} |`);
|
|
1356
|
+
if (i === 0 && hasHeader) {
|
|
1357
|
+
lines.push(`| ${Array(colCount).fill("---").join(" | ")} |`);
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
return lines.join("\n") + "\n";
|
|
1361
|
+
}
|
|
1362
|
+
function applyMarks(text, marks) {
|
|
1363
|
+
var _a;
|
|
1364
|
+
if (!marks || marks.length === 0) return text;
|
|
1365
|
+
let result = text;
|
|
1366
|
+
for (const mark of marks) {
|
|
1367
|
+
switch (mark.type) {
|
|
1368
|
+
case "strong":
|
|
1369
|
+
result = `**${result}**`;
|
|
1370
|
+
break;
|
|
1371
|
+
case "em":
|
|
1372
|
+
result = `*${result}*`;
|
|
1373
|
+
break;
|
|
1374
|
+
case "code":
|
|
1375
|
+
result = `\`${result}\``;
|
|
1376
|
+
break;
|
|
1377
|
+
case "strike":
|
|
1378
|
+
result = `~~${result}~~`;
|
|
1379
|
+
break;
|
|
1380
|
+
case "link": {
|
|
1381
|
+
const href = (_a = mark.attrs) == null ? void 0 : _a.href;
|
|
1382
|
+
if (href) {
|
|
1383
|
+
result = `[${result}](${href})`;
|
|
1384
|
+
}
|
|
1385
|
+
break;
|
|
1386
|
+
}
|
|
1387
|
+
case "underline":
|
|
1388
|
+
break;
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
return result;
|
|
1392
|
+
}
|
|
993
1393
|
|
|
994
1394
|
// src/lib/logs/index.ts
|
|
995
1395
|
import { spawnSync } from "child_process";
|
|
@@ -1116,6 +1516,18 @@ function logJiraStatusChanged(ticketKey, ticketName, oldStatus, newStatus) {
|
|
|
1116
1516
|
${ticketKey}: ${ticketName}
|
|
1117
1517
|
${oldStatus} \u2192 ${newStatus}
|
|
1118
1518
|
|
|
1519
|
+
`;
|
|
1520
|
+
appendToLog(today, entry);
|
|
1521
|
+
}
|
|
1522
|
+
function logJiraAssigneeChanged(ticketKey, ticketName, action, displayName) {
|
|
1523
|
+
const timestamp = formatTimestamp();
|
|
1524
|
+
const today = getTodayDate();
|
|
1525
|
+
const detail = action === "assigned" && displayName ? `Assigned to ${displayName}` : "Unassigned";
|
|
1526
|
+
const entry = `## ${timestamp} - Updated Jira ticket
|
|
1527
|
+
|
|
1528
|
+
${ticketKey}: ${ticketName}
|
|
1529
|
+
${detail}
|
|
1530
|
+
|
|
1119
1531
|
`;
|
|
1120
1532
|
appendToLog(today, entry);
|
|
1121
1533
|
}
|
|
@@ -1534,7 +1946,7 @@ function useJiraTickets() {
|
|
|
1534
1946
|
const newTickets = getLinkedTickets(repoPath, currentBranch);
|
|
1535
1947
|
setTickets(newTickets);
|
|
1536
1948
|
setJiraState("has_tickets");
|
|
1537
|
-
duckEvents.emit("jira:linked");
|
|
1949
|
+
duckEvents.emit("jira:linked", { ticketKey: result.data.key });
|
|
1538
1950
|
setLoading((prev) => ({ ...prev, link: false }));
|
|
1539
1951
|
return true;
|
|
1540
1952
|
},
|
|
@@ -1659,13 +2071,13 @@ function useLogs() {
|
|
|
1659
2071
|
import { useCallback as useCallback6, useState as useState7 } from "react";
|
|
1660
2072
|
function useModal() {
|
|
1661
2073
|
const [modalType, setModalType] = useState7("none");
|
|
1662
|
-
const
|
|
2074
|
+
const open6 = useCallback6((type) => setModalType(type), []);
|
|
1663
2075
|
const close = useCallback6(() => setModalType("none"), []);
|
|
1664
2076
|
const isOpen = modalType !== "none";
|
|
1665
2077
|
return {
|
|
1666
2078
|
type: modalType,
|
|
1667
2079
|
isOpen,
|
|
1668
|
-
open:
|
|
2080
|
+
open: open6,
|
|
1669
2081
|
close
|
|
1670
2082
|
};
|
|
1671
2083
|
}
|
|
@@ -1741,53 +2153,405 @@ function useListNavigation({
|
|
|
1741
2153
|
|
|
1742
2154
|
// src/hooks/useRubberDuck.ts
|
|
1743
2155
|
import { useCallback as useCallback7, useEffect as useEffect7, useState as useState9 } from "react";
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
"
|
|
2156
|
+
|
|
2157
|
+
// src/constants/duck.ts
|
|
2158
|
+
var MOTIVATIONAL = [
|
|
2159
|
+
"I believe in you!",
|
|
2160
|
+
"You've got this!",
|
|
2161
|
+
"One commit at a time.",
|
|
2162
|
+
"Progress, not perfection.",
|
|
2163
|
+
"Every bug fixed is a victory.",
|
|
2164
|
+
"Ship it when it feels right.",
|
|
2165
|
+
"You're closer than you think.",
|
|
2166
|
+
"The tests will pass. Eventually.",
|
|
2167
|
+
"Future you will thank present you.",
|
|
2168
|
+
"Debugging builds character.",
|
|
2169
|
+
"Trust the process. And the compiler.",
|
|
2170
|
+
"Small steps, big impact."
|
|
2171
|
+
];
|
|
2172
|
+
var SARCASTIC = [
|
|
1748
2173
|
"It's always DNS.",
|
|
1749
|
-
"Did you check the logs?",
|
|
1750
|
-
"Maybe add a console.log?",
|
|
1751
|
-
"Is it plugged in?",
|
|
1752
2174
|
"Works on my machine.",
|
|
1753
|
-
"Have you tried reading the error message?",
|
|
1754
|
-
"I believe in you!",
|
|
1755
|
-
"It's probably a race condition.",
|
|
1756
|
-
"Have you tried turning it off and on again?",
|
|
1757
|
-
"Are you sure it compiled?",
|
|
1758
2175
|
"It's not a bug, it's a feature.",
|
|
2176
|
+
"Have you tried turning it off and on again?",
|
|
2177
|
+
"Maybe the real bug was the friends we made along the way.",
|
|
2178
|
+
"Ah yes, the classic off-by-one error. Or is it off-by-two?",
|
|
2179
|
+
"git blame says it was you.",
|
|
2180
|
+
"404: Solution not found. Just kidding, you'll get it.",
|
|
2181
|
+
"Ah yes, undefined is not a function. Classic.",
|
|
2182
|
+
"I'm sure the tests pass locally.",
|
|
2183
|
+
"Looks like a skill issue.",
|
|
2184
|
+
"This is what Stack Overflow was made for.",
|
|
2185
|
+
"Surely this won't break prod.",
|
|
2186
|
+
"git push --force and pray.",
|
|
2187
|
+
"Ah, another meeting that could have been a Slack message.",
|
|
2188
|
+
"The deadline was yesterday. No pressure."
|
|
2189
|
+
];
|
|
2190
|
+
var TECHNICAL = [
|
|
2191
|
+
"Did you check the logs?",
|
|
2192
|
+
"Maybe add a console.log?",
|
|
1759
2193
|
"Did you clear the cache?",
|
|
1760
2194
|
"Try deleting node_modules.",
|
|
2195
|
+
"Are you sure it compiled?",
|
|
2196
|
+
"Have you tried reading the error message?",
|
|
2197
|
+
"It's probably a race condition.",
|
|
2198
|
+
"Is it plugged in?",
|
|
2199
|
+
"Have you checked the network tab?",
|
|
2200
|
+
"Maybe it needs a type assertion.",
|
|
2201
|
+
"Sounds like a missing await.",
|
|
2202
|
+
"Is that dependency up to date?",
|
|
2203
|
+
"Try adding a .catch() to that promise.",
|
|
2204
|
+
"Check if it works in an incognito tab.",
|
|
2205
|
+
"Did you remember to save the file?",
|
|
2206
|
+
"Is the environment variable set?",
|
|
2207
|
+
"Have you tried a fresh git clone?",
|
|
2208
|
+
"Maybe it's a circular dependency.",
|
|
2209
|
+
"Check the types. Always check the types."
|
|
2210
|
+
];
|
|
2211
|
+
var QUACKS = [
|
|
2212
|
+
"Quack.",
|
|
2213
|
+
"Quack quack quack.",
|
|
1761
2214
|
"That's quackers!",
|
|
2215
|
+
"*supportive quacking*",
|
|
1762
2216
|
"Rubber duck debugging, activate!",
|
|
1763
|
-
"
|
|
2217
|
+
"Have you tried explaining it out loud?",
|
|
2218
|
+
"*listens intently*",
|
|
2219
|
+
"*nods in duck*",
|
|
2220
|
+
"*quacks thoughtfully*",
|
|
2221
|
+
"*tilts head*",
|
|
2222
|
+
"Tell me more about this bug...",
|
|
2223
|
+
"*floats beside you*",
|
|
2224
|
+
"*waddles encouragingly*",
|
|
2225
|
+
"Quack? Quack.",
|
|
2226
|
+
"*preens feathers while you think*",
|
|
2227
|
+
"I'm all ears. Well, I'm all beak.",
|
|
2228
|
+
"*splashes in your terminal*",
|
|
2229
|
+
"Go on, I quack \u2014 I mean, I listen.",
|
|
2230
|
+
"*adjusts tiny rubber duck glasses*"
|
|
1764
2231
|
];
|
|
2232
|
+
var DUCK_MESSAGES = [...MOTIVATIONAL, ...SARCASTIC, ...TECHNICAL, ...QUACKS];
|
|
1765
2233
|
var REACTION_MESSAGES = {
|
|
1766
|
-
"pr:merged": [
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
2234
|
+
"pr:merged": [
|
|
2235
|
+
"Quack! It shipped!",
|
|
2236
|
+
"Merged! Time to celebrate.",
|
|
2237
|
+
"To production we go!",
|
|
2238
|
+
"Another one bites the dust.",
|
|
2239
|
+
"Merge commit secured.",
|
|
2240
|
+
"*celebratory quacking*",
|
|
2241
|
+
"The green button has been pressed!",
|
|
2242
|
+
"One less branch in the world.",
|
|
2243
|
+
"*does a little victory waddle*",
|
|
2244
|
+
"And just like that, it was done.",
|
|
2245
|
+
"git branch -d, you served us well.",
|
|
2246
|
+
"Main just got a little better.",
|
|
2247
|
+
"*throws confetti feathers*",
|
|
2248
|
+
"That's what I call a clean merge.",
|
|
2249
|
+
"The CI gods have smiled upon us.",
|
|
2250
|
+
"Deployed and unbothered.",
|
|
2251
|
+
{ template: "PR #{prNumber} merged! Ship it!", requires: ["prNumber"] },
|
|
2252
|
+
{ template: "{prTitle} is live!", requires: ["prTitle"] },
|
|
2253
|
+
{ template: "PR #{prNumber} is now part of history.", requires: ["prNumber"] },
|
|
2254
|
+
{ template: "#{prNumber} has entered the chat (and main).", requires: ["prNumber"] },
|
|
2255
|
+
{ template: "Farewell, #{prNumber}. You were a good branch.", requires: ["prNumber"] },
|
|
2256
|
+
{ template: '"{prTitle}" \u2014 merged and deployed.', requires: ["prTitle"] },
|
|
2257
|
+
{ template: "#{prNumber}: from draft to main. What a journey.", requires: ["prNumber"] },
|
|
2258
|
+
{ template: "RIP #{prNumber}'s branch. Long live main.", requires: ["prNumber"] },
|
|
2259
|
+
{ template: '"{prTitle}" is in the books.', requires: ["prTitle"] },
|
|
2260
|
+
{ template: "#{prNumber} just graduated to production.", requires: ["prNumber"] }
|
|
2261
|
+
],
|
|
2262
|
+
"pr:opened": [
|
|
2263
|
+
"A new PR! Exciting!",
|
|
2264
|
+
"Time for review!",
|
|
2265
|
+
"Fresh code incoming!",
|
|
2266
|
+
"PR opened! Now we wait...",
|
|
2267
|
+
"Let's hope CI passes.",
|
|
2268
|
+
"*optimistic quacking*",
|
|
2269
|
+
"Into the review queue it goes!",
|
|
2270
|
+
"Bold move. I like it.",
|
|
2271
|
+
"And so it begins...",
|
|
2272
|
+
"*quacks in anticipation*",
|
|
2273
|
+
"The diff looks great from here.",
|
|
2274
|
+
"Reviewers, assemble!",
|
|
2275
|
+
"Don't forget to self-review first.",
|
|
2276
|
+
"May your CI be green and your conflicts few.",
|
|
2277
|
+
"*refreshes PR page nervously*",
|
|
2278
|
+
"The hardest part is waiting for reviews.",
|
|
2279
|
+
{ template: "PR #{prNumber} is out there!", requires: ["prNumber"] },
|
|
2280
|
+
{ template: '"{prTitle}" \u2014 sounds promising!', requires: ["prTitle"] },
|
|
2281
|
+
{ template: "#{prNumber} is ready for eyeballs.", requires: ["prNumber"] },
|
|
2282
|
+
{ template: "#{prNumber} just dropped. Reviews welcome.", requires: ["prNumber"] },
|
|
2283
|
+
{ template: '"{prTitle}" \u2014 I would approve that.', requires: ["prTitle"] },
|
|
2284
|
+
{ template: "#{prNumber} has entered the arena.", requires: ["prNumber"] },
|
|
2285
|
+
{ template: '"{prTitle}" \u2014 the diff speaks for itself.', requires: ["prTitle"] },
|
|
2286
|
+
{ template: "Good luck, #{prNumber}. You're gonna need it.", requires: ["prNumber"] },
|
|
2287
|
+
{ template: '"{prTitle}": chapter one.', requires: ["prTitle"] },
|
|
2288
|
+
{ template: "#{prNumber} is seeking approval. Aren't we all?", requires: ["prNumber"] }
|
|
2289
|
+
],
|
|
2290
|
+
"pr:reviewed": [
|
|
2291
|
+
"Feedback time!",
|
|
2292
|
+
"Reviews are in!",
|
|
2293
|
+
"*attentive quacking*",
|
|
2294
|
+
"Someone looked at your code!",
|
|
2295
|
+
"The verdict is in...",
|
|
2296
|
+
"Review received. Deep breaths.",
|
|
2297
|
+
"A fresh pair of eyes!",
|
|
2298
|
+
"*peeks at the comments*",
|
|
2299
|
+
"Code review: the great equalizer.",
|
|
2300
|
+
"Opinions incoming!",
|
|
2301
|
+
"At least someone is reading your code.",
|
|
2302
|
+
"*braces for impact*",
|
|
2303
|
+
"Comments are just spicy suggestions.",
|
|
2304
|
+
"Time to read between the lines.",
|
|
2305
|
+
"Let's see what they think...",
|
|
2306
|
+
{ template: "#{prNumber} has been reviewed!", requires: ["prNumber"] },
|
|
2307
|
+
{ template: 'Someone has opinions about "{prTitle}".', requires: ["prTitle"] },
|
|
2308
|
+
{ template: "#{prNumber} caught someone's attention.", requires: ["prNumber"] },
|
|
2309
|
+
{ template: 'Eyes on "{prTitle}".', requires: ["prTitle"] },
|
|
2310
|
+
{ template: "#{prNumber}: reviewed. The suspense builds.", requires: ["prNumber"] },
|
|
2311
|
+
{ template: '"{prTitle}" is under the microscope.', requires: ["prTitle"] },
|
|
2312
|
+
{ template: "#{prNumber} got some feedback. Brace yourself.", requires: ["prNumber"] }
|
|
2313
|
+
],
|
|
2314
|
+
"pr:approved": [
|
|
2315
|
+
"Approved!",
|
|
2316
|
+
"LGTM!",
|
|
2317
|
+
"Ship it!",
|
|
2318
|
+
"Green across the board!",
|
|
2319
|
+
"*excited quacking*",
|
|
2320
|
+
"The stamp of approval!",
|
|
2321
|
+
"You earned that checkmark.",
|
|
2322
|
+
"*quacks with pride*",
|
|
2323
|
+
"Merge when ready, captain.",
|
|
2324
|
+
"No notes. Literally.",
|
|
2325
|
+
"They couldn't find a single nit. Impressive.",
|
|
2326
|
+
"*standing ovation quacking*",
|
|
2327
|
+
"The approver has spoken.",
|
|
2328
|
+
"All systems go.",
|
|
2329
|
+
"Permission to merge, granted.",
|
|
2330
|
+
"Clean code wins again.",
|
|
2331
|
+
{ template: "PR #{prNumber} approved!", requires: ["prNumber"] },
|
|
2332
|
+
{ template: "#{prNumber} got the green light!", requires: ["prNumber"] },
|
|
2333
|
+
{ template: '"{prTitle}" has been blessed.', requires: ["prTitle"] },
|
|
2334
|
+
{ template: "#{prNumber}: approved. Go time.", requires: ["prNumber"] },
|
|
2335
|
+
{ template: '"{prTitle}" passed the vibe check.', requires: ["prTitle"] },
|
|
2336
|
+
{ template: "#{prNumber} is merge-ready. Your move.", requires: ["prNumber"] },
|
|
2337
|
+
{ template: '"{prTitle}": approved. No objections.', requires: ["prTitle"] },
|
|
2338
|
+
{ template: "#{prNumber} cleared for takeoff.", requires: ["prNumber"] },
|
|
2339
|
+
{ template: "#{prNumber}: zero nits. A rare achievement.", requires: ["prNumber"] }
|
|
2340
|
+
],
|
|
2341
|
+
"pr:changes-requested": [
|
|
2342
|
+
"Some changes needed...",
|
|
2343
|
+
"Back to the drawing board!",
|
|
2344
|
+
"Iterate iterate!",
|
|
2345
|
+
"Feedback is a gift. A slightly annoying gift.",
|
|
2346
|
+
"Almost there. Almost.",
|
|
2347
|
+
"*encouraging quack*",
|
|
2348
|
+
"It's not a rejection, it's a suggestion.",
|
|
2349
|
+
"Round two. Ding ding!",
|
|
2350
|
+
"*passes you a tissue*",
|
|
2351
|
+
"They're just nits. Probably.",
|
|
2352
|
+
"You'll get 'em next push.",
|
|
2353
|
+
"Every great PR has a revision arc.",
|
|
2354
|
+
"They're helping you write better code. That's the story.",
|
|
2355
|
+
"*quacks supportively*",
|
|
2356
|
+
"Nothing a force push can't fix. (Don't actually.)",
|
|
2357
|
+
"The best code is rewritten code. Or something.",
|
|
2358
|
+
"Take a breath. Then address the comments.",
|
|
2359
|
+
{ template: "PR #{prNumber} needs some love.", requires: ["prNumber"] },
|
|
2360
|
+
{ template: "#{prNumber} is almost there \u2014 just a few tweaks.", requires: ["prNumber"] },
|
|
2361
|
+
{ template: '"{prTitle}" needs another pass.', requires: ["prTitle"] },
|
|
2362
|
+
{ template: "#{prNumber}: so close, yet so far.", requires: ["prNumber"] },
|
|
2363
|
+
{ template: "#{prNumber} got sent back. It happens to the best of us.", requires: ["prNumber"] },
|
|
2364
|
+
{ template: '"{prTitle}" has notes. Address them and come back stronger.', requires: ["prTitle"] },
|
|
2365
|
+
{ template: "#{prNumber}: revision requested. You know the drill.", requires: ["prNumber"] },
|
|
2366
|
+
{ template: '"{prTitle}" \u2014 round two incoming.', requires: ["prTitle"] },
|
|
2367
|
+
{ template: "#{prNumber} will be even better after this.", requires: ["prNumber"] }
|
|
2368
|
+
],
|
|
2369
|
+
error: [
|
|
2370
|
+
"Uh oh...",
|
|
2371
|
+
"There there...",
|
|
2372
|
+
"*concerned quacking*",
|
|
2373
|
+
"Quack... not good.",
|
|
2374
|
+
"Errors happen. That's what undo is for.",
|
|
2375
|
+
"Have you tried quacking at it?",
|
|
2376
|
+
"It's fine. Everything's fine.",
|
|
2377
|
+
"*pats you with wing*",
|
|
2378
|
+
"This too shall pass.",
|
|
2379
|
+
"Even senior devs get errors. It's fine.",
|
|
2380
|
+
"*hides behind your monitor*",
|
|
2381
|
+
"That was... unexpected.",
|
|
2382
|
+
"The stack trace knows what happened. You'll figure it out.",
|
|
2383
|
+
"If at first you fail, try try again.",
|
|
2384
|
+
"*nervous quacking*",
|
|
2385
|
+
"Look on the bright side: at least it failed loudly.",
|
|
2386
|
+
"I've seen worse. I think.",
|
|
2387
|
+
"Error is just another word for learning opportunity.",
|
|
2388
|
+
"*puts on hard hat*",
|
|
2389
|
+
"That's a feature request from the error gods.",
|
|
2390
|
+
"Have you considered that maybe the error is right?",
|
|
2391
|
+
"Errors are just the code being honest with you.",
|
|
2392
|
+
"*offers you a rubber duck*",
|
|
2393
|
+
"At least it errored before production. ...Right?"
|
|
2394
|
+
],
|
|
2395
|
+
"jira:transition": [
|
|
2396
|
+
"Ticket moving!",
|
|
2397
|
+
"Progress!",
|
|
2398
|
+
"Workflow in motion!",
|
|
2399
|
+
"Status updated!",
|
|
2400
|
+
"*productive quacking*",
|
|
2401
|
+
"The board looks better already.",
|
|
2402
|
+
"Kanban dreams coming true.",
|
|
2403
|
+
"*drags ticket across the board*",
|
|
2404
|
+
"One column closer to done.",
|
|
2405
|
+
"The sprint board thanks you.",
|
|
2406
|
+
"Velocity: increasing.",
|
|
2407
|
+
"Your scrum master would be proud.",
|
|
2408
|
+
"*updates the burndown chart mentally*",
|
|
2409
|
+
"Flow state: achieved.",
|
|
2410
|
+
"The backlog trembles.",
|
|
2411
|
+
{ template: "{ticketKey} \u2192 {status}!", requires: ["ticketKey", "status"] },
|
|
2412
|
+
{ template: "{ticketKey} is moving along!", requires: ["ticketKey"] },
|
|
2413
|
+
{ template: "{ticketKey} leveled up to {status}.", requires: ["ticketKey", "status"] },
|
|
2414
|
+
{ template: "{ticketKey} has places to be.", requires: ["ticketKey"] },
|
|
2415
|
+
{ template: "{ticketKey}: {status}. Onward!", requires: ["ticketKey", "status"] },
|
|
2416
|
+
{ template: "{ticketKey} just moved to {status}. Progress!", requires: ["ticketKey", "status"] },
|
|
2417
|
+
{ template: "{ticketKey} is now {status}. The board rejoices.", requires: ["ticketKey", "status"] },
|
|
2418
|
+
{ template: "{ticketKey}: status updated. Your standup just got easier.", requires: ["ticketKey"] },
|
|
2419
|
+
{ template: "{ticketKey} changed lanes to {status}.", requires: ["ticketKey", "status"] },
|
|
2420
|
+
{ template: "{ticketKey} is on the move!", requires: ["ticketKey"] }
|
|
2421
|
+
],
|
|
2422
|
+
"jira:linked": [
|
|
2423
|
+
"Ticket linked!",
|
|
2424
|
+
"Jira connection made!",
|
|
2425
|
+
"Tracking enabled!",
|
|
2426
|
+
"Code meets ticket!",
|
|
2427
|
+
"Traceability achieved.",
|
|
2428
|
+
"*organized quacking*",
|
|
2429
|
+
"Branch and ticket, sitting in a tree.",
|
|
2430
|
+
"Connected the dots!",
|
|
2431
|
+
"*files everything neatly*",
|
|
2432
|
+
"Your PM will be proud.",
|
|
2433
|
+
"Context: preserved.",
|
|
2434
|
+
"Now you can prove you did the work.",
|
|
2435
|
+
"Audit trail established.",
|
|
2436
|
+
"The code has a story now.",
|
|
2437
|
+
{ template: "{ticketKey} linked to this branch!", requires: ["ticketKey"] },
|
|
2438
|
+
{ template: "{ticketKey} now has a home.", requires: ["ticketKey"] },
|
|
2439
|
+
{ template: "{ticketKey} and this branch are besties now.", requires: ["ticketKey"] },
|
|
2440
|
+
{ template: "{ticketKey}: tracked and accounted for.", requires: ["ticketKey"] },
|
|
2441
|
+
{ template: "{ticketKey} is now part of the story.", requires: ["ticketKey"] },
|
|
2442
|
+
{ template: "{ticketKey} linked. Your commits have meaning now.", requires: ["ticketKey"] },
|
|
2443
|
+
{ template: "{ticketKey}: connected. No more mystery commits.", requires: ["ticketKey"] }
|
|
2444
|
+
],
|
|
2445
|
+
"jira:configured": [
|
|
2446
|
+
"Jira ready!",
|
|
2447
|
+
"Integration complete!",
|
|
2448
|
+
"Connected to Jira!",
|
|
2449
|
+
"The pipeline is flowing!",
|
|
2450
|
+
"*impressed quacking*",
|
|
2451
|
+
"Jira and terminal, together at last.",
|
|
2452
|
+
"Setup complete. Time to be productive.",
|
|
2453
|
+
"*quacks approvingly*",
|
|
2454
|
+
"The tools are connected. The workflow is whole.",
|
|
2455
|
+
"Now the real fun begins.",
|
|
2456
|
+
"Terminal + Jira = unstoppable.",
|
|
2457
|
+
"One less browser tab needed.",
|
|
2458
|
+
"You just leveled up your workflow.",
|
|
2459
|
+
"*nods in DevOps*"
|
|
2460
|
+
],
|
|
2461
|
+
"jira:assigned": [
|
|
2462
|
+
"Assigned!",
|
|
2463
|
+
"On it!",
|
|
2464
|
+
"Claimed!",
|
|
2465
|
+
"It's yours now!",
|
|
2466
|
+
"*dutiful quacking*",
|
|
2467
|
+
"Ownership established.",
|
|
2468
|
+
"Responsibility accepted.",
|
|
2469
|
+
"*salutes*",
|
|
2470
|
+
"A ticket has found its champion.",
|
|
2471
|
+
"Challenge accepted.",
|
|
2472
|
+
"With great tickets comes great responsibility.",
|
|
2473
|
+
"The chosen one.",
|
|
2474
|
+
"*pins ticket to your desk*",
|
|
2475
|
+
"It's dangerous to go alone \u2014 take this ticket.",
|
|
2476
|
+
{ template: "{ticketKey} assigned to {assignee}!", requires: ["ticketKey", "assignee"] },
|
|
2477
|
+
{ template: "{ticketKey} has an owner now!", requires: ["ticketKey"] },
|
|
2478
|
+
{ template: "{assignee} is on the case!", requires: ["assignee"] },
|
|
2479
|
+
{ template: "{assignee} accepted the quest: {ticketKey}.", requires: ["ticketKey", "assignee"] },
|
|
2480
|
+
{ template: "{ticketKey} found its person.", requires: ["ticketKey"] },
|
|
2481
|
+
{ template: "{assignee} picked up {ticketKey}. Bold.", requires: ["ticketKey", "assignee"] },
|
|
2482
|
+
{ template: "{ticketKey} \u2192 {assignee}. Good luck!", requires: ["ticketKey", "assignee"] },
|
|
2483
|
+
{ template: "{assignee} volunteered as tribute for {ticketKey}.", requires: ["ticketKey", "assignee"] },
|
|
2484
|
+
{ template: "{ticketKey} is in {assignee}'s hands now.", requires: ["ticketKey", "assignee"] },
|
|
2485
|
+
{ template: "{assignee}: you own {ticketKey}. No takebacks.", requires: ["ticketKey", "assignee"] }
|
|
2486
|
+
],
|
|
2487
|
+
"jira:unassigned": [
|
|
2488
|
+
"Unassigned!",
|
|
2489
|
+
"Free agent!",
|
|
2490
|
+
"Released!",
|
|
2491
|
+
"Back to the pool.",
|
|
2492
|
+
"*liberated quacking*",
|
|
2493
|
+
"Ticket is up for grabs.",
|
|
2494
|
+
"Back to the backlog wilderness.",
|
|
2495
|
+
"*waves goodbye*",
|
|
2496
|
+
"An orphan ticket in the wild.",
|
|
2497
|
+
"Someone will pick it up. Probably.",
|
|
2498
|
+
"Into the void it goes.",
|
|
2499
|
+
"Not my ticket, not my problem.",
|
|
2500
|
+
"*whistles innocently*",
|
|
2501
|
+
"Free as a bird. Well, a duck.",
|
|
2502
|
+
{ template: "{ticketKey} is unassigned.", requires: ["ticketKey"] },
|
|
2503
|
+
{ template: "{ticketKey} needs a new hero.", requires: ["ticketKey"] },
|
|
2504
|
+
{ template: "{ticketKey} is looking for a volunteer.", requires: ["ticketKey"] },
|
|
2505
|
+
{ template: "Who wants {ticketKey}? Anyone?", requires: ["ticketKey"] },
|
|
2506
|
+
{ template: "{ticketKey} has been released into the wild.", requires: ["ticketKey"] },
|
|
2507
|
+
{ template: "{ticketKey}: abandoned. *sad quack*", requires: ["ticketKey"] },
|
|
2508
|
+
{ template: "{ticketKey} is available. First come, first served.", requires: ["ticketKey"] },
|
|
2509
|
+
{ template: "{ticketKey} is free. Like a duck on a pond.", requires: ["ticketKey"] }
|
|
2510
|
+
]
|
|
1775
2511
|
};
|
|
2512
|
+
function fillTemplate(template, payload) {
|
|
2513
|
+
return template.replace(/\{(\w+)\}/g, (_, key) => {
|
|
2514
|
+
const val = payload[key];
|
|
2515
|
+
return val !== void 0 ? String(val) : `{${key}}`;
|
|
2516
|
+
});
|
|
2517
|
+
}
|
|
2518
|
+
function pickReactionMessage(event, payload) {
|
|
2519
|
+
const entries = REACTION_MESSAGES[event];
|
|
2520
|
+
const candidates = [];
|
|
2521
|
+
for (const entry of entries) {
|
|
2522
|
+
if (typeof entry === "string") {
|
|
2523
|
+
candidates.push(entry);
|
|
2524
|
+
} else if (payload && entry.requires.every((k) => payload[k] !== void 0)) {
|
|
2525
|
+
candidates.push(fillTemplate(entry.template, payload));
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
return candidates[Math.floor(Math.random() * candidates.length)];
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
// src/hooks/useRubberDuck.ts
|
|
1776
2532
|
function useRubberDuck() {
|
|
1777
|
-
const [state, setState] = useState9({
|
|
1778
|
-
|
|
1779
|
-
|
|
2533
|
+
const [state, setState] = useState9(() => {
|
|
2534
|
+
const config = loadConfig();
|
|
2535
|
+
return {
|
|
2536
|
+
visible: config.duckVisible ?? false,
|
|
2537
|
+
message: DUCK_MESSAGES[Math.floor(Math.random() * DUCK_MESSAGES.length)]
|
|
2538
|
+
};
|
|
1780
2539
|
});
|
|
1781
2540
|
const getRandomMessage = useCallback7(() => {
|
|
1782
2541
|
const index = Math.floor(Math.random() * DUCK_MESSAGES.length);
|
|
1783
2542
|
return DUCK_MESSAGES[index];
|
|
1784
2543
|
}, []);
|
|
1785
2544
|
const toggleDuck = useCallback7(() => {
|
|
1786
|
-
setState((prev) =>
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
2545
|
+
setState((prev) => {
|
|
2546
|
+
const newVisible = !prev.visible;
|
|
2547
|
+
const config = loadConfig();
|
|
2548
|
+
saveConfig({ ...config, duckVisible: newVisible });
|
|
2549
|
+
return {
|
|
2550
|
+
...prev,
|
|
2551
|
+
visible: newVisible,
|
|
2552
|
+
message: newVisible ? getRandomMessage() : prev.message
|
|
2553
|
+
};
|
|
2554
|
+
});
|
|
1791
2555
|
}, [getRandomMessage]);
|
|
1792
2556
|
const quack = useCallback7(() => {
|
|
1793
2557
|
if (state.visible) {
|
|
@@ -1797,20 +2561,16 @@ function useRubberDuck() {
|
|
|
1797
2561
|
}));
|
|
1798
2562
|
}
|
|
1799
2563
|
}, [state.visible, getRandomMessage]);
|
|
1800
|
-
const getReactionMessage = useCallback7((event) => {
|
|
1801
|
-
const messages = REACTION_MESSAGES[event];
|
|
1802
|
-
return messages[Math.floor(Math.random() * messages.length)];
|
|
1803
|
-
}, []);
|
|
1804
2564
|
useEffect7(() => {
|
|
1805
|
-
const unsubscribe = duckEvents.subscribe((event) => {
|
|
2565
|
+
const unsubscribe = duckEvents.subscribe((event, payload) => {
|
|
1806
2566
|
setState((prev) => ({
|
|
1807
2567
|
...prev,
|
|
1808
2568
|
visible: true,
|
|
1809
|
-
message:
|
|
2569
|
+
message: pickReactionMessage(event, payload)
|
|
1810
2570
|
}));
|
|
1811
2571
|
});
|
|
1812
2572
|
return unsubscribe;
|
|
1813
|
-
}, [
|
|
2573
|
+
}, []);
|
|
1814
2574
|
return {
|
|
1815
2575
|
visible: state.visible,
|
|
1816
2576
|
message: state.message,
|
|
@@ -2095,7 +2855,7 @@ ${body}`;
|
|
|
2095
2855
|
var _a;
|
|
2096
2856
|
const ticketKeys = repoPath && branch ? getLinkedTickets(repoPath, branch).map((t) => t.key) : [];
|
|
2097
2857
|
logPRCreated(newPR.number, newPR.title, ticketKeys);
|
|
2098
|
-
duckEvents.emit("pr:opened");
|
|
2858
|
+
duckEvents.emit("pr:opened", { prNumber: newPR.number, prTitle: newPR.title });
|
|
2099
2859
|
(_a = onLogUpdatedRef.current) == null ? void 0 : _a.call(onLogUpdatedRef);
|
|
2100
2860
|
}
|
|
2101
2861
|
});
|
|
@@ -2131,7 +2891,7 @@ ${body}`;
|
|
|
2131
2891
|
var _a;
|
|
2132
2892
|
const tickets = repoPath && branch ? getLinkedTickets(repoPath, branch).map((t) => t.key) : [];
|
|
2133
2893
|
logPRCreated(newPR.number, newPR.title, tickets);
|
|
2134
|
-
duckEvents.emit("pr:opened");
|
|
2894
|
+
duckEvents.emit("pr:opened", { prNumber: newPR.number, prTitle: newPR.title });
|
|
2135
2895
|
(_a = onLogUpdatedRef.current) == null ? void 0 : _a.call(onLogUpdatedRef);
|
|
2136
2896
|
}
|
|
2137
2897
|
});
|
|
@@ -2244,11 +3004,11 @@ ${body}`;
|
|
|
2244
3004
|
] });
|
|
2245
3005
|
}
|
|
2246
3006
|
|
|
2247
|
-
// src/components/jira/
|
|
2248
|
-
import
|
|
2249
|
-
import {
|
|
3007
|
+
// src/components/jira-browser/JiraBrowserView.tsx
|
|
3008
|
+
import { useCallback as useCallback10, useEffect as useEffect12, useMemo as useMemo3, useRef as useRef7, useState as useState16 } from "react";
|
|
3009
|
+
import { Box as Box12, useInput as useInput10 } from "ink";
|
|
2250
3010
|
|
|
2251
|
-
// src/components/jira/
|
|
3011
|
+
// src/components/jira-browser/AddViewModal.tsx
|
|
2252
3012
|
import { useState as useState12 } from "react";
|
|
2253
3013
|
import { Box as Box8, Text as Text7, useInput as useInput6 } from "ink";
|
|
2254
3014
|
|
|
@@ -2267,8 +3027,11 @@ function TextInput({ value, onChange, placeholder, isActive, mask }) {
|
|
|
2267
3027
|
if (key.return || key.escape || key.upArrow || key.downArrow || key.tab) {
|
|
2268
3028
|
return;
|
|
2269
3029
|
}
|
|
2270
|
-
if (input && input.length
|
|
2271
|
-
|
|
3030
|
+
if (input && input.length > 0) {
|
|
3031
|
+
const printable = input.replace(/[^\x20-\x7E\u00A0-\uFFFF]/g, "");
|
|
3032
|
+
if (printable.length > 0) {
|
|
3033
|
+
onChange(value + printable);
|
|
3034
|
+
}
|
|
2272
3035
|
}
|
|
2273
3036
|
},
|
|
2274
3037
|
{ isActive }
|
|
@@ -2281,11 +3044,13 @@ function TextInput({ value, onChange, placeholder, isActive, mask }) {
|
|
|
2281
3044
|
] }) });
|
|
2282
3045
|
}
|
|
2283
3046
|
|
|
2284
|
-
// src/components/jira/
|
|
3047
|
+
// src/components/jira-browser/AddViewModal.tsx
|
|
2285
3048
|
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2286
|
-
function
|
|
2287
|
-
const [
|
|
2288
|
-
const
|
|
3049
|
+
function AddViewModal({ onSubmit, onCancel, loading, error }) {
|
|
3050
|
+
const [url, setUrl] = useState12("");
|
|
3051
|
+
const [name, setName] = useState12("");
|
|
3052
|
+
const [activeField, setActiveField] = useState12("url");
|
|
3053
|
+
const canSubmit = url.trim().length > 0;
|
|
2289
3054
|
useInput6(
|
|
2290
3055
|
(_input, key) => {
|
|
2291
3056
|
if (loading) return;
|
|
@@ -2293,52 +3058,948 @@ function LinkTicketModal({ onSubmit, onCancel, loading, error }) {
|
|
|
2293
3058
|
onCancel();
|
|
2294
3059
|
return;
|
|
2295
3060
|
}
|
|
3061
|
+
if (key.tab) {
|
|
3062
|
+
setActiveField((f) => f === "url" ? "name" : "url");
|
|
3063
|
+
return;
|
|
3064
|
+
}
|
|
2296
3065
|
if (key.return && canSubmit) {
|
|
2297
|
-
|
|
3066
|
+
const viewName = name.trim() || generateViewName(url.trim());
|
|
3067
|
+
onSubmit(url.trim(), viewName);
|
|
2298
3068
|
}
|
|
2299
3069
|
},
|
|
2300
3070
|
{ isActive: !loading }
|
|
2301
3071
|
);
|
|
2302
3072
|
return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, paddingY: 1, children: [
|
|
2303
|
-
/* @__PURE__ */ jsx8(Text7, { bold: true, color: "yellow", children: "
|
|
2304
|
-
/* @__PURE__ */ jsx8(Text7, { dimColor: true, children: "
|
|
3073
|
+
/* @__PURE__ */ jsx8(Text7, { bold: true, color: "yellow", children: "Add Jira View" }),
|
|
3074
|
+
/* @__PURE__ */ jsx8(Text7, { dimColor: true, children: "Tab to switch fields, Enter to save, Esc to cancel" }),
|
|
2305
3075
|
/* @__PURE__ */ jsx8(Box8, { marginTop: 1 }),
|
|
2306
3076
|
error && /* @__PURE__ */ jsx8(Box8, { marginBottom: 1, children: /* @__PURE__ */ jsx8(Text7, { color: "red", children: error }) }),
|
|
2307
3077
|
/* @__PURE__ */ jsxs7(Box8, { children: [
|
|
2308
|
-
/* @__PURE__ */ jsx8(Text7, { color: "blue", children: "
|
|
2309
|
-
/* @__PURE__ */ jsx8(
|
|
3078
|
+
/* @__PURE__ */ jsx8(Text7, { color: "blue", children: "URL: " }),
|
|
3079
|
+
/* @__PURE__ */ jsx8(
|
|
3080
|
+
TextInput,
|
|
3081
|
+
{
|
|
3082
|
+
value: url,
|
|
3083
|
+
onChange: setUrl,
|
|
3084
|
+
placeholder: "https://company.atlassian.net/issues/?filter=12345",
|
|
3085
|
+
isActive: !loading && activeField === "url"
|
|
3086
|
+
}
|
|
3087
|
+
)
|
|
3088
|
+
] }),
|
|
3089
|
+
/* @__PURE__ */ jsxs7(Box8, { marginTop: 1, children: [
|
|
3090
|
+
/* @__PURE__ */ jsx8(Text7, { color: "blue", children: "Name: " }),
|
|
3091
|
+
/* @__PURE__ */ jsx8(
|
|
3092
|
+
TextInput,
|
|
3093
|
+
{
|
|
3094
|
+
value: name,
|
|
3095
|
+
onChange: setName,
|
|
3096
|
+
placeholder: "(auto-generated from URL)",
|
|
3097
|
+
isActive: !loading && activeField === "name"
|
|
3098
|
+
}
|
|
3099
|
+
)
|
|
2310
3100
|
] }),
|
|
2311
|
-
loading && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text7, { color: "yellow", children: "
|
|
2312
|
-
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text7, { dimColor: true, children: "
|
|
3101
|
+
loading && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text7, { color: "yellow", children: "Validating view..." }) }),
|
|
3102
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text7, { dimColor: true, children: "Supports: filter URLs, JQL URLs, board URLs" }) })
|
|
2313
3103
|
] });
|
|
2314
3104
|
}
|
|
2315
3105
|
|
|
2316
|
-
// src/components/jira/
|
|
3106
|
+
// src/components/jira-browser/JiraSavedViewBrowserBox.tsx
|
|
3107
|
+
import open4 from "open";
|
|
3108
|
+
import { useCallback as useCallback9, useEffect as useEffect10, useMemo as useMemo2, useState as useState14 } from "react";
|
|
2317
3109
|
import { TitledBox as TitledBox4 } from "@mishieck/ink-titled-box";
|
|
2318
|
-
import { Box as
|
|
3110
|
+
import { Box as Box10, Text as Text9, useInput as useInput8 } from "ink";
|
|
3111
|
+
import { ScrollView as ScrollView6 } from "ink-scroll-view";
|
|
3112
|
+
import Spinner3 from "ink-spinner";
|
|
2319
3113
|
|
|
2320
|
-
// src/components/jira/
|
|
2321
|
-
import
|
|
3114
|
+
// src/components/jira-browser/JiraIssueDetailView.tsx
|
|
3115
|
+
import open3 from "open";
|
|
3116
|
+
import { useEffect as useEffect9, useRef as useRef6, useState as useState13 } from "react";
|
|
2322
3117
|
import { Box as Box9, Text as Text8, useInput as useInput7 } from "ink";
|
|
3118
|
+
import { ScrollView as ScrollView5 } from "ink-scroll-view";
|
|
2323
3119
|
import SelectInput from "ink-select-input";
|
|
3120
|
+
import Spinner2 from "ink-spinner";
|
|
2324
3121
|
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2325
|
-
function
|
|
2326
|
-
|
|
3122
|
+
function JiraIssueDetailView({
|
|
3123
|
+
issueKey,
|
|
3124
|
+
issueSummary,
|
|
3125
|
+
auth,
|
|
3126
|
+
myAccountId,
|
|
3127
|
+
myDisplayName,
|
|
3128
|
+
isActive,
|
|
3129
|
+
onClose,
|
|
3130
|
+
onIssueUpdated,
|
|
3131
|
+
onLogUpdated
|
|
3132
|
+
}) {
|
|
3133
|
+
var _a, _b;
|
|
3134
|
+
const scrollRef = useRef6(null);
|
|
3135
|
+
const [detail, setDetail] = useState13(null);
|
|
2327
3136
|
const [loading, setLoading] = useState13(true);
|
|
2328
|
-
const [applying, setApplying] = useState13(false);
|
|
2329
3137
|
const [error, setError] = useState13(null);
|
|
3138
|
+
const [mode, setMode] = useState13("normal");
|
|
3139
|
+
const [transitions, setTransitions] = useState13([]);
|
|
3140
|
+
const [transitionsLoading, setTransitionsLoading] = useState13(false);
|
|
3141
|
+
const [actionLoading, setActionLoading] = useState13(null);
|
|
3142
|
+
const [actionError, setActionError] = useState13(null);
|
|
2330
3143
|
useEffect9(() => {
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
if (
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
3144
|
+
setLoading(true);
|
|
3145
|
+
setError(null);
|
|
3146
|
+
getIssueDetail(auth, issueKey).then((result) => {
|
|
3147
|
+
if (result.success) {
|
|
3148
|
+
setDetail(result.data);
|
|
3149
|
+
} else {
|
|
3150
|
+
setError(result.error);
|
|
3151
|
+
}
|
|
3152
|
+
setLoading(false);
|
|
3153
|
+
});
|
|
3154
|
+
}, [issueKey, auth.siteUrl]);
|
|
3155
|
+
const getIssueUrl = () => `${auth.siteUrl}/browse/${issueKey}`;
|
|
3156
|
+
const openTransitionPicker = async () => {
|
|
3157
|
+
setTransitionsLoading(true);
|
|
3158
|
+
setActionError(null);
|
|
3159
|
+
const result = await getTransitions(auth, issueKey);
|
|
3160
|
+
if (result.success) {
|
|
3161
|
+
setTransitions(result.data);
|
|
3162
|
+
setMode("transitions");
|
|
3163
|
+
} else {
|
|
3164
|
+
setActionError(result.error);
|
|
3165
|
+
}
|
|
3166
|
+
setTransitionsLoading(false);
|
|
3167
|
+
};
|
|
3168
|
+
const handleTransitionSelect = async (item) => {
|
|
3169
|
+
setMode("normal");
|
|
3170
|
+
setActionLoading("Updating status...");
|
|
3171
|
+
setActionError(null);
|
|
3172
|
+
const result = await applyTransition(auth, issueKey, item.value);
|
|
3173
|
+
if (result.success) {
|
|
3174
|
+
const transition = transitions.find((t) => t.id === item.value);
|
|
3175
|
+
const newStatus = (transition == null ? void 0 : transition.to.name) ?? item.label;
|
|
3176
|
+
const oldStatus = (detail == null ? void 0 : detail.fields.status.name) ?? "Unknown";
|
|
3177
|
+
setDetail((prev) => prev ? { ...prev, fields: { ...prev.fields, status: { name: newStatus } } } : prev);
|
|
3178
|
+
onIssueUpdated(issueKey, { status: newStatus });
|
|
3179
|
+
duckEvents.emit("jira:transition", { ticketKey: issueKey, status: newStatus });
|
|
3180
|
+
logJiraStatusChanged(issueKey, issueSummary, oldStatus, newStatus);
|
|
3181
|
+
onLogUpdated == null ? void 0 : onLogUpdated();
|
|
3182
|
+
} else {
|
|
3183
|
+
setActionError(result.error);
|
|
3184
|
+
duckEvents.emit("error");
|
|
3185
|
+
}
|
|
3186
|
+
setActionLoading(null);
|
|
3187
|
+
};
|
|
3188
|
+
const handleAssignToMe = async () => {
|
|
3189
|
+
if (!myAccountId || !myDisplayName) return;
|
|
3190
|
+
setActionLoading("Assigning...");
|
|
3191
|
+
setActionError(null);
|
|
3192
|
+
const result = await assignIssue(auth, issueKey, myAccountId);
|
|
3193
|
+
if (result.success) {
|
|
3194
|
+
const assignee = { accountId: myAccountId, displayName: myDisplayName };
|
|
3195
|
+
setDetail((prev) => prev ? { ...prev, fields: { ...prev.fields, assignee } } : prev);
|
|
3196
|
+
onIssueUpdated(issueKey, { assignee });
|
|
3197
|
+
duckEvents.emit("jira:assigned", { ticketKey: issueKey, assignee: myDisplayName });
|
|
3198
|
+
logJiraAssigneeChanged(issueKey, issueSummary, "assigned", myDisplayName);
|
|
3199
|
+
onLogUpdated == null ? void 0 : onLogUpdated();
|
|
3200
|
+
} else {
|
|
3201
|
+
setActionError(result.error);
|
|
3202
|
+
duckEvents.emit("error");
|
|
3203
|
+
}
|
|
3204
|
+
setActionLoading(null);
|
|
3205
|
+
};
|
|
3206
|
+
const handleUnassign = async () => {
|
|
3207
|
+
setActionLoading("Unassigning...");
|
|
3208
|
+
setActionError(null);
|
|
3209
|
+
const result = await unassignIssue(auth, issueKey);
|
|
3210
|
+
if (result.success) {
|
|
3211
|
+
setDetail((prev) => prev ? { ...prev, fields: { ...prev.fields, assignee: null } } : prev);
|
|
3212
|
+
onIssueUpdated(issueKey, { assignee: null });
|
|
3213
|
+
duckEvents.emit("jira:unassigned", { ticketKey: issueKey });
|
|
3214
|
+
logJiraAssigneeChanged(issueKey, issueSummary, "unassigned");
|
|
3215
|
+
onLogUpdated == null ? void 0 : onLogUpdated();
|
|
3216
|
+
} else {
|
|
3217
|
+
setActionError(result.error);
|
|
3218
|
+
duckEvents.emit("error");
|
|
3219
|
+
}
|
|
3220
|
+
setActionLoading(null);
|
|
3221
|
+
};
|
|
3222
|
+
useInput7(
|
|
3223
|
+
(input, key) => {
|
|
3224
|
+
var _a2, _b2;
|
|
3225
|
+
if (mode === "transitions") {
|
|
3226
|
+
if (key.escape) {
|
|
3227
|
+
setMode("normal");
|
|
3228
|
+
}
|
|
3229
|
+
return;
|
|
3230
|
+
}
|
|
3231
|
+
if (key.escape && !actionLoading) {
|
|
3232
|
+
onClose();
|
|
3233
|
+
return;
|
|
3234
|
+
}
|
|
3235
|
+
if (key.upArrow || input === "k") {
|
|
3236
|
+
(_a2 = scrollRef.current) == null ? void 0 : _a2.scrollBy(-1);
|
|
3237
|
+
}
|
|
3238
|
+
if (key.downArrow || input === "j") {
|
|
3239
|
+
(_b2 = scrollRef.current) == null ? void 0 : _b2.scrollBy(1);
|
|
3240
|
+
}
|
|
3241
|
+
if (input === "o") {
|
|
3242
|
+
open3(getIssueUrl()).catch(() => {
|
|
3243
|
+
});
|
|
3244
|
+
}
|
|
3245
|
+
if (input === "y") {
|
|
3246
|
+
copyToClipboard(getIssueUrl());
|
|
3247
|
+
}
|
|
3248
|
+
if (input === "s" && !actionLoading) {
|
|
3249
|
+
openTransitionPicker();
|
|
3250
|
+
}
|
|
3251
|
+
if (input === "a" && !actionLoading && myAccountId) {
|
|
3252
|
+
handleAssignToMe();
|
|
3253
|
+
}
|
|
3254
|
+
if (input === "A" && !actionLoading) {
|
|
3255
|
+
handleUnassign();
|
|
3256
|
+
}
|
|
3257
|
+
},
|
|
3258
|
+
{ isActive }
|
|
3259
|
+
);
|
|
3260
|
+
const statusColor = getStatusColor((detail == null ? void 0 : detail.fields.status.name) ?? "");
|
|
3261
|
+
const descriptionMd = (detail == null ? void 0 : detail.fields.description) ? adfToMarkdown(detail.fields.description) : null;
|
|
3262
|
+
const comments = (detail == null ? void 0 : detail.fields.comment.comments) ?? [];
|
|
3263
|
+
const totalComments = (detail == null ? void 0 : detail.fields.comment.total) ?? 0;
|
|
3264
|
+
return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", flexGrow: 1, children: [
|
|
3265
|
+
/* @__PURE__ */ jsxs8(Box9, { flexGrow: 1, flexBasis: 0, overflow: "hidden", children: [
|
|
3266
|
+
loading && /* @__PURE__ */ jsx9(Box9, { paddingX: 1, children: /* @__PURE__ */ jsxs8(Text8, { color: "yellow", children: [
|
|
3267
|
+
/* @__PURE__ */ jsx9(Spinner2, { type: "dots" }),
|
|
3268
|
+
" Loading issue details..."
|
|
3269
|
+
] }) }),
|
|
3270
|
+
error && /* @__PURE__ */ jsx9(Box9, { paddingX: 1, children: /* @__PURE__ */ jsx9(Text8, { color: "red", children: error }) }),
|
|
3271
|
+
!loading && !error && detail && /* @__PURE__ */ jsx9(ScrollView5, { ref: scrollRef, children: /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", paddingX: 1, children: [
|
|
3272
|
+
/* @__PURE__ */ jsxs8(Box9, { children: [
|
|
3273
|
+
/* @__PURE__ */ jsx9(Text8, { bold: true, color: "blue", children: detail.key }),
|
|
3274
|
+
/* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
|
|
3275
|
+
" ",
|
|
3276
|
+
detail.fields.summary
|
|
3277
|
+
] })
|
|
3278
|
+
] }),
|
|
3279
|
+
/* @__PURE__ */ jsxs8(Box9, { gap: 1, children: [
|
|
3280
|
+
/* @__PURE__ */ jsx9(Text8, { dimColor: true, children: "Status:" }),
|
|
3281
|
+
/* @__PURE__ */ jsx9(Text8, { color: statusColor, children: detail.fields.status.name }),
|
|
3282
|
+
/* @__PURE__ */ jsx9(Text8, { dimColor: true, children: "Assignee:" }),
|
|
3283
|
+
/* @__PURE__ */ jsx9(Text8, { children: ((_a = detail.fields.assignee) == null ? void 0 : _a.displayName) ?? "Unassigned" }),
|
|
3284
|
+
/* @__PURE__ */ jsx9(Text8, { dimColor: true, children: "Reporter:" }),
|
|
3285
|
+
/* @__PURE__ */ jsx9(Text8, { children: ((_b = detail.fields.reporter) == null ? void 0 : _b.displayName) ?? "Unknown" })
|
|
3286
|
+
] }),
|
|
3287
|
+
/* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Divider, {}) }),
|
|
3288
|
+
/* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "column", children: [
|
|
3289
|
+
/* @__PURE__ */ jsx9(Text8, { dimColor: true, children: "Description:" }),
|
|
3290
|
+
descriptionMd ? /* @__PURE__ */ jsx9(Markdown, { children: descriptionMd }) : /* @__PURE__ */ jsx9(Text8, { dimColor: true, italic: true, children: "No description" })
|
|
3291
|
+
] }),
|
|
3292
|
+
/* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Divider, {}) }),
|
|
3293
|
+
/* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "column", children: [
|
|
3294
|
+
/* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
3295
|
+
"Comments (",
|
|
3296
|
+
totalComments,
|
|
3297
|
+
"):"
|
|
3298
|
+
] }),
|
|
3299
|
+
comments.length === 0 && /* @__PURE__ */ jsx9(Text8, { dimColor: true, italic: true, children: "No comments" }),
|
|
3300
|
+
comments.map((comment) => /* @__PURE__ */ jsx9(CommentBlock, { comment }, comment.id)),
|
|
3301
|
+
comments.length < totalComments && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
3302
|
+
"Showing ",
|
|
3303
|
+
comments.length,
|
|
3304
|
+
" of ",
|
|
3305
|
+
totalComments,
|
|
3306
|
+
" comments. Open in browser to see all."
|
|
3307
|
+
] }) })
|
|
3308
|
+
] })
|
|
3309
|
+
] }) })
|
|
3310
|
+
] }),
|
|
3311
|
+
mode === "transitions" && /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
3312
|
+
/* @__PURE__ */ jsx9(Text8, { bold: true, color: "yellow", children: "Change Status" }),
|
|
3313
|
+
/* @__PURE__ */ jsx9(
|
|
3314
|
+
SelectInput,
|
|
3315
|
+
{
|
|
3316
|
+
items: transitions.map((t) => ({ label: t.name, value: t.id })),
|
|
3317
|
+
onSelect: handleTransitionSelect
|
|
3318
|
+
}
|
|
3319
|
+
),
|
|
3320
|
+
/* @__PURE__ */ jsx9(Text8, { dimColor: true, children: "Esc to cancel" })
|
|
3321
|
+
] }),
|
|
3322
|
+
/* @__PURE__ */ jsxs8(Box9, { paddingX: 1, flexDirection: "column", children: [
|
|
3323
|
+
actionLoading && /* @__PURE__ */ jsxs8(Text8, { color: "yellow", children: [
|
|
3324
|
+
/* @__PURE__ */ jsx9(Spinner2, { type: "dots" }),
|
|
3325
|
+
" ",
|
|
3326
|
+
actionLoading
|
|
3327
|
+
] }),
|
|
3328
|
+
actionError && /* @__PURE__ */ jsx9(Text8, { color: "red", children: actionError }),
|
|
3329
|
+
transitionsLoading && /* @__PURE__ */ jsxs8(Text8, { color: "yellow", children: [
|
|
3330
|
+
/* @__PURE__ */ jsx9(Spinner2, { type: "dots" }),
|
|
3331
|
+
" Loading transitions..."
|
|
3332
|
+
] }),
|
|
3333
|
+
!actionLoading && !transitionsLoading && mode === "normal" && /* @__PURE__ */ jsx9(Text8, { dimColor: true, children: "Esc close \xB7 j/k scroll \xB7 s status \xB7 a assign \xB7 A unassign \xB7 o open \xB7 y copy" })
|
|
3334
|
+
] })
|
|
3335
|
+
] });
|
|
3336
|
+
}
|
|
3337
|
+
function CommentBlock({ comment }) {
|
|
3338
|
+
const bodyMd = adfToMarkdown(comment.body);
|
|
3339
|
+
return /* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "column", children: [
|
|
3340
|
+
/* @__PURE__ */ jsxs8(Box9, { gap: 1, children: [
|
|
3341
|
+
/* @__PURE__ */ jsx9(Text8, { bold: true, children: comment.author.displayName }),
|
|
3342
|
+
/* @__PURE__ */ jsx9(Text8, { dimColor: true, children: timeAgo(comment.created) })
|
|
3343
|
+
] }),
|
|
3344
|
+
bodyMd ? /* @__PURE__ */ jsx9(Markdown, { children: bodyMd }) : /* @__PURE__ */ jsx9(Text8, { dimColor: true, italic: true, children: "Empty comment" })
|
|
3345
|
+
] });
|
|
3346
|
+
}
|
|
3347
|
+
function getStatusColor(status) {
|
|
3348
|
+
const lower = status.toLowerCase();
|
|
3349
|
+
if (lower === "done" || lower === "closed" || lower === "resolved") return "green";
|
|
3350
|
+
if (lower === "in progress" || lower === "in review") return "yellow";
|
|
3351
|
+
return "gray";
|
|
3352
|
+
}
|
|
3353
|
+
|
|
3354
|
+
// src/components/jira-browser/JiraSavedViewBrowserBox.tsx
|
|
3355
|
+
import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
3356
|
+
function groupBySprint(issues) {
|
|
3357
|
+
const groups = /* @__PURE__ */ new Map();
|
|
3358
|
+
for (const issue of issues) {
|
|
3359
|
+
const sprint = issue.fields.sprint ?? null;
|
|
3360
|
+
const key = sprint ? String(sprint.id) : "__backlog__";
|
|
3361
|
+
if (!groups.has(key)) {
|
|
3362
|
+
groups.set(key, { sprint, issues: [] });
|
|
3363
|
+
}
|
|
3364
|
+
groups.get(key).issues.push(issue);
|
|
3365
|
+
}
|
|
3366
|
+
const stateOrder = { active: 0, future: 1, closed: 2 };
|
|
3367
|
+
const entries = [...groups.values()];
|
|
3368
|
+
entries.sort((a, b) => {
|
|
3369
|
+
if (!a.sprint) return 1;
|
|
3370
|
+
if (!b.sprint) return -1;
|
|
3371
|
+
const aOrder = stateOrder[a.sprint.state] ?? 3;
|
|
3372
|
+
const bOrder = stateOrder[b.sprint.state] ?? 3;
|
|
3373
|
+
return aOrder - bOrder;
|
|
3374
|
+
});
|
|
3375
|
+
return entries;
|
|
3376
|
+
}
|
|
3377
|
+
function buildRows(groups) {
|
|
3378
|
+
var _a;
|
|
3379
|
+
const hasSprints = groups.some((g) => g.sprint !== null);
|
|
3380
|
+
if (!hasSprints) {
|
|
3381
|
+
return groups.flatMap((g) => g.issues.map((issue) => ({ type: "issue", issue })));
|
|
3382
|
+
}
|
|
3383
|
+
const rows = [];
|
|
3384
|
+
for (const group of groups) {
|
|
3385
|
+
const label = group.sprint ? group.sprint.name : "Backlog";
|
|
3386
|
+
const state = ((_a = group.sprint) == null ? void 0 : _a.state) ?? null;
|
|
3387
|
+
rows.push({ type: "header", label, state });
|
|
3388
|
+
for (const issue of group.issues) {
|
|
3389
|
+
rows.push({ type: "issue", issue });
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
return rows;
|
|
3393
|
+
}
|
|
3394
|
+
function JiraSavedViewBrowserBox({
|
|
3395
|
+
view,
|
|
3396
|
+
auth,
|
|
3397
|
+
myAccountId,
|
|
3398
|
+
myDisplayName,
|
|
3399
|
+
isActive,
|
|
3400
|
+
onInputModeChange,
|
|
3401
|
+
onLogUpdated
|
|
3402
|
+
}) {
|
|
3403
|
+
const [issues, setIssues] = useState14([]);
|
|
3404
|
+
const [loading, setLoading] = useState14(false);
|
|
3405
|
+
const [error, setError] = useState14(null);
|
|
3406
|
+
const [total, setTotal] = useState14(0);
|
|
3407
|
+
const [highlightedIndex, setHighlightedIndex] = useState14(0);
|
|
3408
|
+
const [inputText, setInputText] = useState14("");
|
|
3409
|
+
const [searchText, setSearchText] = useState14("");
|
|
3410
|
+
const [isFiltering, setIsFiltering] = useState14(false);
|
|
3411
|
+
const [assigneeFilter, setAssigneeFilter] = useState14("all");
|
|
3412
|
+
const [detailIssue, setDetailIssue] = useState14(null);
|
|
3413
|
+
useEffect10(() => {
|
|
3414
|
+
onInputModeChange == null ? void 0 : onInputModeChange(isFiltering || detailIssue !== null);
|
|
3415
|
+
}, [isFiltering, detailIssue, onInputModeChange]);
|
|
3416
|
+
const title = "[6] Issues";
|
|
3417
|
+
const borderColor = isActive ? "yellow" : void 0;
|
|
3418
|
+
const displayTitle = view ? `${title} - ${view.name}` : title;
|
|
3419
|
+
const filteredIssues = useMemo2(() => {
|
|
3420
|
+
if (assigneeFilter === "unassigned") {
|
|
3421
|
+
return issues.filter((issue) => !issue.fields.assignee);
|
|
3422
|
+
}
|
|
3423
|
+
if (assigneeFilter === "me" && myAccountId) {
|
|
3424
|
+
return issues.filter((issue) => {
|
|
3425
|
+
var _a;
|
|
3426
|
+
return ((_a = issue.fields.assignee) == null ? void 0 : _a.accountId) === myAccountId;
|
|
3427
|
+
});
|
|
3428
|
+
}
|
|
3429
|
+
return issues;
|
|
3430
|
+
}, [issues, assigneeFilter, myAccountId]);
|
|
3431
|
+
const rows = useMemo2(() => {
|
|
3432
|
+
const groups = groupBySprint(filteredIssues);
|
|
3433
|
+
return buildRows(groups);
|
|
3434
|
+
}, [filteredIssues]);
|
|
3435
|
+
const navigableIndices = useMemo2(
|
|
3436
|
+
() => rows.map((r, i) => r.type === "issue" ? i : -1).filter((i) => i >= 0),
|
|
3437
|
+
[rows]
|
|
3438
|
+
);
|
|
3439
|
+
const scrollRef = useScrollToIndex(navigableIndices.length > 0 ? navigableIndices[highlightedIndex] ?? 0 : 0);
|
|
3440
|
+
const currentIssue = useMemo2(() => {
|
|
3441
|
+
if (navigableIndices.length === 0) return null;
|
|
3442
|
+
const rowIdx = navigableIndices[highlightedIndex];
|
|
3443
|
+
if (rowIdx === void 0) return null;
|
|
3444
|
+
const row = rows[rowIdx];
|
|
3445
|
+
return (row == null ? void 0 : row.type) === "issue" ? row.issue : null;
|
|
3446
|
+
}, [rows, navigableIndices, highlightedIndex]);
|
|
3447
|
+
const hasMore = issues.length < total;
|
|
3448
|
+
const doFetch = useCallback9(
|
|
3449
|
+
async (search, startAt = 0, append = false) => {
|
|
3450
|
+
if (!view || !auth) return;
|
|
3451
|
+
setLoading(true);
|
|
3452
|
+
setError(null);
|
|
3453
|
+
const result = await fetchViewIssues(auth, view, {
|
|
3454
|
+
startAt,
|
|
3455
|
+
maxResults: 50,
|
|
3456
|
+
searchText: search || void 0
|
|
3457
|
+
});
|
|
3458
|
+
if (result.success) {
|
|
3459
|
+
setIssues((prev) => append ? [...prev, ...result.data.issues] : result.data.issues);
|
|
3460
|
+
setTotal(result.data.total);
|
|
3461
|
+
if (!append) setHighlightedIndex(0);
|
|
3462
|
+
} else {
|
|
3463
|
+
setError(result.error);
|
|
3464
|
+
if (!append) {
|
|
3465
|
+
setIssues([]);
|
|
3466
|
+
setTotal(0);
|
|
3467
|
+
}
|
|
3468
|
+
}
|
|
3469
|
+
setLoading(false);
|
|
3470
|
+
},
|
|
3471
|
+
[view, auth]
|
|
3472
|
+
);
|
|
3473
|
+
useEffect10(() => {
|
|
3474
|
+
if (view && auth) {
|
|
3475
|
+
setSearchText("");
|
|
3476
|
+
setInputText("");
|
|
3477
|
+
doFetch("");
|
|
3478
|
+
} else {
|
|
3479
|
+
setIssues([]);
|
|
3480
|
+
setTotal(0);
|
|
3481
|
+
setError(null);
|
|
3482
|
+
}
|
|
3483
|
+
}, [view == null ? void 0 : view.id, auth == null ? void 0 : auth.siteUrl]);
|
|
3484
|
+
const getIssueUrl = (issue) => {
|
|
3485
|
+
if (!auth) return null;
|
|
3486
|
+
return `${auth.siteUrl}/browse/${issue.key}`;
|
|
3487
|
+
};
|
|
3488
|
+
const handleIssueUpdated = useCallback9(
|
|
3489
|
+
(key, updates) => {
|
|
3490
|
+
setIssues(
|
|
3491
|
+
(prev) => prev.map((issue) => {
|
|
3492
|
+
if (issue.key !== key) return issue;
|
|
3493
|
+
const updated = { ...issue, fields: { ...issue.fields } };
|
|
3494
|
+
if (updates.status !== void 0) {
|
|
3495
|
+
updated.fields.status = { name: updates.status };
|
|
3496
|
+
}
|
|
3497
|
+
if ("assignee" in updates) {
|
|
3498
|
+
updated.fields.assignee = updates.assignee;
|
|
3499
|
+
}
|
|
3500
|
+
return updated;
|
|
3501
|
+
})
|
|
3502
|
+
);
|
|
3503
|
+
},
|
|
3504
|
+
[]
|
|
3505
|
+
);
|
|
3506
|
+
const hasActiveFilters = searchText.length > 0 || assigneeFilter !== "all";
|
|
3507
|
+
useInput8(
|
|
3508
|
+
(input, key) => {
|
|
3509
|
+
if (detailIssue) return;
|
|
3510
|
+
if (isFiltering) {
|
|
3511
|
+
if (key.escape) {
|
|
3512
|
+
setIsFiltering(false);
|
|
3513
|
+
setInputText(searchText);
|
|
3514
|
+
return;
|
|
3515
|
+
}
|
|
3516
|
+
if (key.return) {
|
|
3517
|
+
setIsFiltering(false);
|
|
3518
|
+
const newSearch = inputText.trim();
|
|
3519
|
+
if (newSearch !== searchText) {
|
|
3520
|
+
setSearchText(newSearch);
|
|
3521
|
+
doFetch(newSearch);
|
|
3522
|
+
}
|
|
3523
|
+
return;
|
|
3524
|
+
}
|
|
3525
|
+
if (key.backspace || key.delete) {
|
|
3526
|
+
setInputText((prev) => prev.slice(0, -1));
|
|
3527
|
+
return;
|
|
3528
|
+
}
|
|
3529
|
+
if (input && input.length > 0) {
|
|
3530
|
+
const printable = input.replace(/[^\x20-\x7E\u00A0-\uFFFF]/g, "");
|
|
3531
|
+
if (printable.length > 0) {
|
|
3532
|
+
setInputText((prev) => prev + printable);
|
|
3533
|
+
}
|
|
3534
|
+
return;
|
|
3535
|
+
}
|
|
3536
|
+
return;
|
|
3537
|
+
}
|
|
3538
|
+
if (navigableIndices.length > 0) {
|
|
3539
|
+
if (key.upArrow || input === "k") {
|
|
3540
|
+
setHighlightedIndex((i) => Math.max(0, i - 1));
|
|
3541
|
+
}
|
|
3542
|
+
if (key.downArrow || input === "j") {
|
|
3543
|
+
setHighlightedIndex((i) => Math.min(navigableIndices.length - 1, i + 1));
|
|
3544
|
+
}
|
|
3545
|
+
if (input === "o" && currentIssue) {
|
|
3546
|
+
const url = getIssueUrl(currentIssue);
|
|
3547
|
+
if (url) open4(url).catch(() => {
|
|
3548
|
+
});
|
|
3549
|
+
}
|
|
3550
|
+
if (input === "y" && currentIssue) {
|
|
3551
|
+
const url = getIssueUrl(currentIssue);
|
|
3552
|
+
if (url) copyToClipboard(url);
|
|
3553
|
+
}
|
|
3554
|
+
if (key.return && currentIssue && auth) {
|
|
3555
|
+
setDetailIssue({ key: currentIssue.key, summary: currentIssue.fields.summary });
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
if (input === "/") {
|
|
3559
|
+
setIsFiltering(true);
|
|
3560
|
+
setInputText(searchText);
|
|
3561
|
+
return;
|
|
3562
|
+
}
|
|
3563
|
+
if (input === "u") {
|
|
3564
|
+
setAssigneeFilter((f) => f === "unassigned" ? "all" : "unassigned");
|
|
3565
|
+
setHighlightedIndex(0);
|
|
3566
|
+
return;
|
|
3567
|
+
}
|
|
3568
|
+
if (input === "m") {
|
|
3569
|
+
setAssigneeFilter((f) => f === "me" ? "all" : "me");
|
|
3570
|
+
setHighlightedIndex(0);
|
|
3571
|
+
return;
|
|
3572
|
+
}
|
|
3573
|
+
if (input === "x") {
|
|
3574
|
+
setAssigneeFilter("all");
|
|
3575
|
+
if (searchText) {
|
|
3576
|
+
setSearchText("");
|
|
3577
|
+
setInputText("");
|
|
3578
|
+
doFetch("");
|
|
3579
|
+
}
|
|
3580
|
+
setHighlightedIndex(0);
|
|
3581
|
+
return;
|
|
3582
|
+
}
|
|
3583
|
+
if (input === "l" && hasMore) {
|
|
3584
|
+
doFetch(searchText, issues.length, true);
|
|
3585
|
+
return;
|
|
3586
|
+
}
|
|
3587
|
+
if (input === "r") {
|
|
3588
|
+
doFetch(searchText);
|
|
3589
|
+
}
|
|
3590
|
+
},
|
|
3591
|
+
{ isActive }
|
|
3592
|
+
);
|
|
3593
|
+
const filterParts = [];
|
|
3594
|
+
if (assigneeFilter === "unassigned") filterParts.push("unassigned");
|
|
3595
|
+
if (assigneeFilter === "me") filterParts.push("mine");
|
|
3596
|
+
if (searchText) filterParts.push(`"${searchText}"`);
|
|
3597
|
+
return /* @__PURE__ */ jsx10(TitledBox4, { borderStyle: "round", titles: [displayTitle], borderColor, flexGrow: 1, children: /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", flexGrow: 1, children: detailIssue && auth ? /* @__PURE__ */ jsx10(
|
|
3598
|
+
JiraIssueDetailView,
|
|
3599
|
+
{
|
|
3600
|
+
issueKey: detailIssue.key,
|
|
3601
|
+
issueSummary: detailIssue.summary,
|
|
3602
|
+
auth,
|
|
3603
|
+
myAccountId,
|
|
3604
|
+
myDisplayName,
|
|
3605
|
+
isActive,
|
|
3606
|
+
onClose: () => setDetailIssue(null),
|
|
3607
|
+
onIssueUpdated: handleIssueUpdated,
|
|
3608
|
+
onLogUpdated
|
|
3609
|
+
}
|
|
3610
|
+
) : /* @__PURE__ */ jsxs9(Fragment2, { children: [
|
|
3611
|
+
(isFiltering || hasActiveFilters) && /* @__PURE__ */ jsxs9(Box10, { paddingX: 1, children: [
|
|
3612
|
+
/* @__PURE__ */ jsx10(Text9, { color: "blue", children: "Search: " }),
|
|
3613
|
+
isFiltering ? /* @__PURE__ */ jsxs9(Fragment2, { children: [
|
|
3614
|
+
/* @__PURE__ */ jsx10(Text9, { children: inputText }),
|
|
3615
|
+
/* @__PURE__ */ jsx10(Text9, { backgroundColor: "yellow", children: " " })
|
|
3616
|
+
] }) : /* @__PURE__ */ jsxs9(Fragment2, { children: [
|
|
3617
|
+
/* @__PURE__ */ jsx10(Text9, { children: filterParts.join(" + ") }),
|
|
3618
|
+
/* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
|
|
3619
|
+
" ",
|
|
3620
|
+
"(",
|
|
3621
|
+
filteredIssues.length,
|
|
3622
|
+
"/",
|
|
3623
|
+
total,
|
|
3624
|
+
")"
|
|
3625
|
+
] })
|
|
3626
|
+
] })
|
|
3627
|
+
] }),
|
|
3628
|
+
/* @__PURE__ */ jsxs9(Box10, { flexGrow: 1, flexBasis: 0, overflow: "hidden", children: [
|
|
3629
|
+
!view && /* @__PURE__ */ jsx10(Box10, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: "Select a view to browse issues" }) }),
|
|
3630
|
+
view && loading && issues.length === 0 && /* @__PURE__ */ jsx10(Box10, { paddingX: 1, children: /* @__PURE__ */ jsxs9(Text9, { color: "yellow", children: [
|
|
3631
|
+
/* @__PURE__ */ jsx10(Spinner3, { type: "dots" }),
|
|
3632
|
+
" Loading issues..."
|
|
3633
|
+
] }) }),
|
|
3634
|
+
view && error && /* @__PURE__ */ jsx10(Box10, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { color: "red", children: error }) }),
|
|
3635
|
+
view && !loading && !error && issues.length === 0 && /* @__PURE__ */ jsx10(Box10, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: searchText ? "No issues match search" : "No issues found" }) }),
|
|
3636
|
+
view && !loading && !error && filteredIssues.length === 0 && issues.length > 0 && /* @__PURE__ */ jsx10(Box10, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: "No issues match filter" }) }),
|
|
3637
|
+
rows.length > 0 && /* @__PURE__ */ jsx10(ScrollView6, { ref: scrollRef, children: rows.map((row, rowIdx) => {
|
|
3638
|
+
if (row.type === "header") {
|
|
3639
|
+
const stateLabel = row.state === "active" ? " (active)" : "";
|
|
3640
|
+
return /* @__PURE__ */ jsx10(Box10, { paddingX: 1, marginTop: rowIdx > 0 ? 1 : 0, children: /* @__PURE__ */ jsxs9(Text9, { bold: true, color: "magenta", children: [
|
|
3641
|
+
row.label,
|
|
3642
|
+
stateLabel && /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: stateLabel })
|
|
3643
|
+
] }) }, `header-${row.label}`);
|
|
3644
|
+
}
|
|
3645
|
+
const navIdx = navigableIndices.indexOf(rowIdx);
|
|
3646
|
+
const isHighlighted = navIdx === highlightedIndex;
|
|
3647
|
+
const cursor = isHighlighted ? ">" : " ";
|
|
3648
|
+
const statusColor = getStatusColor2(row.issue.fields.status.name);
|
|
3649
|
+
return /* @__PURE__ */ jsxs9(Box10, { paddingX: 1, children: [
|
|
3650
|
+
/* @__PURE__ */ jsxs9(Text9, { color: isHighlighted ? "yellow" : void 0, children: [
|
|
3651
|
+
cursor,
|
|
3652
|
+
" "
|
|
3653
|
+
] }),
|
|
3654
|
+
/* @__PURE__ */ jsx10(Text9, { bold: true, color: "blue", children: row.issue.key }),
|
|
3655
|
+
/* @__PURE__ */ jsxs9(Text9, { children: [
|
|
3656
|
+
" ",
|
|
3657
|
+
row.issue.fields.summary
|
|
3658
|
+
] }),
|
|
3659
|
+
/* @__PURE__ */ jsxs9(Text9, { color: statusColor, children: [
|
|
3660
|
+
" [",
|
|
3661
|
+
row.issue.fields.status.name,
|
|
3662
|
+
"]"
|
|
3663
|
+
] })
|
|
3664
|
+
] }, row.issue.key);
|
|
3665
|
+
}) })
|
|
3666
|
+
] }),
|
|
3667
|
+
view && !loading && issues.length > 0 && /* @__PURE__ */ jsx10(Box10, { paddingX: 1, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
|
|
3668
|
+
issues.length,
|
|
3669
|
+
" of ",
|
|
3670
|
+
total,
|
|
3671
|
+
" loaded",
|
|
3672
|
+
hasMore && " \xB7 l to load more"
|
|
3673
|
+
] }) }),
|
|
3674
|
+
view && loading && issues.length > 0 && /* @__PURE__ */ jsx10(Box10, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: "Loading more..." }) })
|
|
3675
|
+
] }) }) });
|
|
3676
|
+
}
|
|
3677
|
+
function getStatusColor2(status) {
|
|
3678
|
+
const lower = status.toLowerCase();
|
|
3679
|
+
if (lower === "done" || lower === "closed" || lower === "resolved") return "green";
|
|
3680
|
+
if (lower === "in progress" || lower === "in review") return "yellow";
|
|
3681
|
+
return "gray";
|
|
3682
|
+
}
|
|
3683
|
+
|
|
3684
|
+
// src/components/jira-browser/JiraSavedViewsBox.tsx
|
|
3685
|
+
import { useEffect as useEffect11, useState as useState15 } from "react";
|
|
3686
|
+
import { TitledBox as TitledBox5 } from "@mishieck/ink-titled-box";
|
|
3687
|
+
import { Box as Box11, Text as Text10, useInput as useInput9 } from "ink";
|
|
3688
|
+
import { ScrollView as ScrollView7 } from "ink-scroll-view";
|
|
3689
|
+
import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
3690
|
+
function JiraSavedViewsBox({
|
|
3691
|
+
views,
|
|
3692
|
+
selectedViewId,
|
|
3693
|
+
highlightedIndex,
|
|
3694
|
+
onHighlight,
|
|
3695
|
+
onSelect,
|
|
3696
|
+
onAdd,
|
|
3697
|
+
onDelete,
|
|
3698
|
+
onRename,
|
|
3699
|
+
isActive,
|
|
3700
|
+
onInputModeChange
|
|
3701
|
+
}) {
|
|
3702
|
+
const scrollRef = useScrollToIndex(highlightedIndex);
|
|
3703
|
+
const [renaming, setRenaming] = useState15(null);
|
|
3704
|
+
const [renameValue, setRenameValue] = useState15("");
|
|
3705
|
+
useEffect11(() => {
|
|
3706
|
+
onInputModeChange == null ? void 0 : onInputModeChange(renaming !== null);
|
|
3707
|
+
}, [renaming, onInputModeChange]);
|
|
3708
|
+
const title = "[5] Views";
|
|
3709
|
+
const borderColor = isActive ? "yellow" : void 0;
|
|
3710
|
+
useInput9(
|
|
3711
|
+
(input, key) => {
|
|
3712
|
+
if (renaming) {
|
|
3713
|
+
if (key.escape) {
|
|
3714
|
+
setRenaming(null);
|
|
3715
|
+
setRenameValue("");
|
|
3716
|
+
return;
|
|
3717
|
+
}
|
|
3718
|
+
if (key.return) {
|
|
3719
|
+
const trimmed = renameValue.trim();
|
|
3720
|
+
if (trimmed.length > 0) {
|
|
3721
|
+
onRename(renaming, trimmed);
|
|
3722
|
+
}
|
|
3723
|
+
setRenaming(null);
|
|
3724
|
+
setRenameValue("");
|
|
3725
|
+
return;
|
|
3726
|
+
}
|
|
3727
|
+
return;
|
|
3728
|
+
}
|
|
3729
|
+
if (views.length === 0) {
|
|
3730
|
+
if (input === "a") onAdd();
|
|
3731
|
+
return;
|
|
3732
|
+
}
|
|
3733
|
+
if (key.upArrow || input === "k") {
|
|
3734
|
+
onHighlight(Math.max(0, highlightedIndex - 1));
|
|
3735
|
+
}
|
|
3736
|
+
if (key.downArrow || input === "j") {
|
|
3737
|
+
onHighlight(Math.min(views.length - 1, highlightedIndex + 1));
|
|
3738
|
+
}
|
|
3739
|
+
if (input === " ") {
|
|
3740
|
+
const view = views[highlightedIndex];
|
|
3741
|
+
if (view) onSelect(view.id);
|
|
3742
|
+
}
|
|
3743
|
+
if (input === "a") onAdd();
|
|
3744
|
+
if (input === "e") {
|
|
3745
|
+
const view = views[highlightedIndex];
|
|
3746
|
+
if (view) {
|
|
3747
|
+
setRenaming(view.id);
|
|
3748
|
+
setRenameValue(view.name);
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
if (input === "d") {
|
|
3752
|
+
const view = views[highlightedIndex];
|
|
3753
|
+
if (view) onDelete(view.id);
|
|
3754
|
+
}
|
|
3755
|
+
},
|
|
3756
|
+
{ isActive }
|
|
3757
|
+
);
|
|
3758
|
+
return /* @__PURE__ */ jsx11(TitledBox5, { borderStyle: "round", titles: [title], borderColor, height: 5, children: /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", paddingX: 1, flexGrow: 1, overflow: "hidden", children: [
|
|
3759
|
+
views.length === 0 && /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: "No saved views" }),
|
|
3760
|
+
views.length > 0 && /* @__PURE__ */ jsx11(ScrollView7, { ref: scrollRef, children: views.map((view, idx) => {
|
|
3761
|
+
const isHighlighted = isActive && idx === highlightedIndex;
|
|
3762
|
+
const isSelected = view.id === selectedViewId;
|
|
3763
|
+
const isRenaming = view.id === renaming;
|
|
3764
|
+
const cursor = isHighlighted ? ">" : " ";
|
|
3765
|
+
const nameColor = isSelected ? "green" : void 0;
|
|
3766
|
+
const indicator = isSelected ? " *" : "";
|
|
3767
|
+
return /* @__PURE__ */ jsxs10(Box11, { children: [
|
|
3768
|
+
/* @__PURE__ */ jsxs10(Text10, { color: isHighlighted ? "yellow" : void 0, children: [
|
|
3769
|
+
cursor,
|
|
3770
|
+
" "
|
|
3771
|
+
] }),
|
|
3772
|
+
isRenaming ? /* @__PURE__ */ jsx11(TextInput, { value: renameValue, onChange: setRenameValue, isActive: true }) : /* @__PURE__ */ jsxs10(Fragment3, { children: [
|
|
3773
|
+
/* @__PURE__ */ jsx11(Text10, { color: nameColor, children: view.name }),
|
|
3774
|
+
/* @__PURE__ */ jsx11(Text10, { dimColor: true, children: indicator })
|
|
3775
|
+
] })
|
|
3776
|
+
] }, view.id);
|
|
3777
|
+
}) })
|
|
3778
|
+
] }) });
|
|
3779
|
+
}
|
|
3780
|
+
|
|
3781
|
+
// src/components/jira-browser/JiraBrowserView.tsx
|
|
3782
|
+
import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
3783
|
+
function JiraBrowserView({
|
|
3784
|
+
isActive,
|
|
3785
|
+
focusedBox,
|
|
3786
|
+
onFocusedBoxChange,
|
|
3787
|
+
onModalChange,
|
|
3788
|
+
onLogUpdated
|
|
3789
|
+
}) {
|
|
3790
|
+
const repo = useGitRepo();
|
|
3791
|
+
const modal = useModal();
|
|
3792
|
+
const [views, setViews] = useState16([]);
|
|
3793
|
+
const [selectedViewId, setSelectedViewId] = useState16(null);
|
|
3794
|
+
const [highlightedIndex, setHighlightedIndex] = useState16(0);
|
|
3795
|
+
const [addError, setAddError] = useState16(void 0);
|
|
3796
|
+
const [addLoading, setAddLoading] = useState16(false);
|
|
3797
|
+
const [myAccountId, setMyAccountId] = useState16(null);
|
|
3798
|
+
const [myDisplayName, setMyDisplayName] = useState16(null);
|
|
3799
|
+
const [inputModeActive, setInputModeActive] = useState16(false);
|
|
3800
|
+
const lastRepoRef = useRef7(null);
|
|
3801
|
+
const auth = useMemo3(() => {
|
|
3802
|
+
if (!repo.repoPath || !isJiraConfigured(repo.repoPath)) return null;
|
|
3803
|
+
const siteUrl = getJiraSiteUrl(repo.repoPath);
|
|
3804
|
+
const creds = getJiraCredentials(repo.repoPath);
|
|
3805
|
+
if (!siteUrl || !creds.email || !creds.apiToken) return null;
|
|
3806
|
+
return { siteUrl, email: creds.email, apiToken: creds.apiToken };
|
|
3807
|
+
}, [repo.repoPath]);
|
|
3808
|
+
const selectedView = useMemo3(() => views.find((v) => v.id === selectedViewId) ?? null, [views, selectedViewId]);
|
|
3809
|
+
useEffect12(() => {
|
|
3810
|
+
if (!repo.repoPath || repo.repoPath === lastRepoRef.current) return;
|
|
3811
|
+
lastRepoRef.current = repo.repoPath;
|
|
3812
|
+
const loaded = getSavedViews(repo.repoPath);
|
|
3813
|
+
setViews(loaded);
|
|
3814
|
+
if (loaded.length > 0 && !selectedViewId) {
|
|
3815
|
+
setSelectedViewId(loaded[0].id);
|
|
3816
|
+
}
|
|
3817
|
+
}, [repo.repoPath]);
|
|
3818
|
+
useEffect12(() => {
|
|
3819
|
+
if (!auth) {
|
|
3820
|
+
setMyAccountId(null);
|
|
3821
|
+
return;
|
|
3822
|
+
}
|
|
3823
|
+
getCurrentUser(auth).then((result) => {
|
|
3824
|
+
if (result.success) {
|
|
3825
|
+
setMyAccountId(result.data.accountId);
|
|
3826
|
+
setMyDisplayName(result.data.displayName);
|
|
3827
|
+
}
|
|
3828
|
+
});
|
|
3829
|
+
}, [auth == null ? void 0 : auth.siteUrl, auth == null ? void 0 : auth.email]);
|
|
3830
|
+
useEffect12(() => {
|
|
3831
|
+
onModalChange == null ? void 0 : onModalChange(modal.isOpen || inputModeActive);
|
|
3832
|
+
}, [modal.isOpen, inputModeActive, onModalChange]);
|
|
3833
|
+
useEffect12(() => {
|
|
3834
|
+
if (!isActive) modal.close();
|
|
3835
|
+
}, [isActive, modal.close]);
|
|
3836
|
+
const refreshViews = useCallback10(() => {
|
|
3837
|
+
if (!repo.repoPath) return;
|
|
3838
|
+
setViews(getSavedViews(repo.repoPath));
|
|
3839
|
+
}, [repo.repoPath]);
|
|
3840
|
+
const handleSelectView = useCallback10((viewId) => {
|
|
3841
|
+
setSelectedViewId(viewId);
|
|
3842
|
+
}, []);
|
|
3843
|
+
const handleAddView = useCallback10(
|
|
3844
|
+
async (url, name) => {
|
|
3845
|
+
if (!repo.repoPath) return;
|
|
3846
|
+
const source = parseJiraUrl(url);
|
|
3847
|
+
if (!source) {
|
|
3848
|
+
setAddError("Unrecognized Jira URL format");
|
|
3849
|
+
return;
|
|
3850
|
+
}
|
|
3851
|
+
setAddLoading(true);
|
|
3852
|
+
setAddError(void 0);
|
|
3853
|
+
try {
|
|
3854
|
+
const view = addSavedView(repo.repoPath, name, url, source);
|
|
3855
|
+
refreshViews();
|
|
3856
|
+
setSelectedViewId(view.id);
|
|
3857
|
+
modal.close();
|
|
3858
|
+
} catch {
|
|
3859
|
+
setAddError("Failed to save view");
|
|
3860
|
+
} finally {
|
|
3861
|
+
setAddLoading(false);
|
|
3862
|
+
}
|
|
3863
|
+
},
|
|
3864
|
+
[repo.repoPath, refreshViews, modal.close]
|
|
3865
|
+
);
|
|
3866
|
+
const handleRenameView = useCallback10(
|
|
3867
|
+
(viewId, newName) => {
|
|
3868
|
+
if (!repo.repoPath) return;
|
|
3869
|
+
renameSavedView(repo.repoPath, viewId, newName);
|
|
3870
|
+
refreshViews();
|
|
3871
|
+
},
|
|
3872
|
+
[repo.repoPath, refreshViews]
|
|
3873
|
+
);
|
|
3874
|
+
const handleDeleteView = useCallback10(
|
|
3875
|
+
(viewId) => {
|
|
3876
|
+
if (!repo.repoPath) return;
|
|
3877
|
+
removeSavedView(repo.repoPath, viewId);
|
|
3878
|
+
refreshViews();
|
|
3879
|
+
if (selectedViewId === viewId) {
|
|
3880
|
+
const remaining = getSavedViews(repo.repoPath);
|
|
3881
|
+
setSelectedViewId(remaining.length > 0 ? remaining[0].id : null);
|
|
3882
|
+
}
|
|
3883
|
+
setHighlightedIndex((i) => Math.max(0, i - 1));
|
|
3884
|
+
},
|
|
3885
|
+
[repo.repoPath, selectedViewId, refreshViews]
|
|
3886
|
+
);
|
|
3887
|
+
useInput10(
|
|
3888
|
+
(input) => {
|
|
3889
|
+
if (input === "5") onFocusedBoxChange("saved-views");
|
|
3890
|
+
if (input === "6") onFocusedBoxChange("browser");
|
|
3891
|
+
},
|
|
3892
|
+
{ isActive: isActive && !modal.isOpen }
|
|
3893
|
+
);
|
|
3894
|
+
if (modal.type === "add") {
|
|
3895
|
+
return /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx12(
|
|
3896
|
+
AddViewModal,
|
|
3897
|
+
{
|
|
3898
|
+
onSubmit: handleAddView,
|
|
3899
|
+
onCancel: () => {
|
|
3900
|
+
modal.close();
|
|
3901
|
+
setAddError(void 0);
|
|
3902
|
+
},
|
|
3903
|
+
loading: addLoading,
|
|
3904
|
+
error: addError
|
|
3905
|
+
}
|
|
3906
|
+
) });
|
|
3907
|
+
}
|
|
3908
|
+
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", flexGrow: 1, children: [
|
|
3909
|
+
/* @__PURE__ */ jsx12(
|
|
3910
|
+
JiraSavedViewsBox,
|
|
3911
|
+
{
|
|
3912
|
+
views,
|
|
3913
|
+
selectedViewId,
|
|
3914
|
+
highlightedIndex,
|
|
3915
|
+
onHighlight: setHighlightedIndex,
|
|
3916
|
+
onSelect: handleSelectView,
|
|
3917
|
+
onAdd: () => modal.open("add"),
|
|
3918
|
+
onDelete: handleDeleteView,
|
|
3919
|
+
onRename: handleRenameView,
|
|
3920
|
+
isActive: isActive && focusedBox === "saved-views",
|
|
3921
|
+
onInputModeChange: setInputModeActive
|
|
3922
|
+
}
|
|
3923
|
+
),
|
|
3924
|
+
/* @__PURE__ */ jsx12(
|
|
3925
|
+
JiraSavedViewBrowserBox,
|
|
3926
|
+
{
|
|
3927
|
+
view: selectedView,
|
|
3928
|
+
auth,
|
|
3929
|
+
myAccountId,
|
|
3930
|
+
myDisplayName,
|
|
3931
|
+
isActive: isActive && focusedBox === "browser",
|
|
3932
|
+
onInputModeChange: setInputModeActive,
|
|
3933
|
+
onLogUpdated
|
|
3934
|
+
}
|
|
3935
|
+
)
|
|
3936
|
+
] });
|
|
3937
|
+
}
|
|
3938
|
+
|
|
3939
|
+
// src/components/jira/JiraView.tsx
|
|
3940
|
+
import open5 from "open";
|
|
3941
|
+
import { useEffect as useEffect14, useRef as useRef8 } from "react";
|
|
3942
|
+
|
|
3943
|
+
// src/components/jira/LinkTicketModal.tsx
|
|
3944
|
+
import { useState as useState17 } from "react";
|
|
3945
|
+
import { Box as Box13, Text as Text11, useInput as useInput11 } from "ink";
|
|
3946
|
+
import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3947
|
+
function LinkTicketModal({ onSubmit, onCancel, loading, error }) {
|
|
3948
|
+
const [ticketInput, setTicketInput] = useState17("");
|
|
3949
|
+
const canSubmit = ticketInput.trim().length > 0;
|
|
3950
|
+
useInput11(
|
|
3951
|
+
(_input, key) => {
|
|
3952
|
+
if (loading) return;
|
|
3953
|
+
if (key.escape) {
|
|
3954
|
+
onCancel();
|
|
3955
|
+
return;
|
|
3956
|
+
}
|
|
3957
|
+
if (key.return && canSubmit) {
|
|
3958
|
+
onSubmit(ticketInput.trim());
|
|
3959
|
+
}
|
|
3960
|
+
},
|
|
3961
|
+
{ isActive: !loading }
|
|
3962
|
+
);
|
|
3963
|
+
return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, paddingY: 1, children: [
|
|
3964
|
+
/* @__PURE__ */ jsx13(Text11, { bold: true, color: "yellow", children: "Link Jira Ticket" }),
|
|
3965
|
+
/* @__PURE__ */ jsx13(Text11, { dimColor: true, children: "Type ticket ID, Enter to submit, Esc to cancel" }),
|
|
3966
|
+
/* @__PURE__ */ jsx13(Box13, { marginTop: 1 }),
|
|
3967
|
+
error && /* @__PURE__ */ jsx13(Box13, { marginBottom: 1, children: /* @__PURE__ */ jsx13(Text11, { color: "red", children: error }) }),
|
|
3968
|
+
/* @__PURE__ */ jsxs12(Box13, { children: [
|
|
3969
|
+
/* @__PURE__ */ jsx13(Text11, { color: "blue", children: "Ticket: " }),
|
|
3970
|
+
/* @__PURE__ */ jsx13(TextInput, { value: ticketInput, onChange: setTicketInput, placeholder: "PROJ-123", isActive: !loading })
|
|
3971
|
+
] }),
|
|
3972
|
+
loading && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx13(Text11, { color: "yellow", children: "Fetching ticket..." }) }),
|
|
3973
|
+
/* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: "Examples: PROJ-123 or https://company.atlassian.net/browse/PROJ-123" }) })
|
|
3974
|
+
] });
|
|
3975
|
+
}
|
|
3976
|
+
|
|
3977
|
+
// src/components/jira/JiraView.tsx
|
|
3978
|
+
import { TitledBox as TitledBox6 } from "@mishieck/ink-titled-box";
|
|
3979
|
+
import { Box as Box17, Text as Text15, useInput as useInput14 } from "ink";
|
|
3980
|
+
|
|
3981
|
+
// src/components/jira/ChangeStatusModal.tsx
|
|
3982
|
+
import { useEffect as useEffect13, useState as useState18 } from "react";
|
|
3983
|
+
import { Box as Box14, Text as Text12, useInput as useInput12 } from "ink";
|
|
3984
|
+
import SelectInput2 from "ink-select-input";
|
|
3985
|
+
import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
3986
|
+
function ChangeStatusModal({ repoPath, ticketKey, currentStatus, onComplete, onCancel }) {
|
|
3987
|
+
const [transitions, setTransitions] = useState18([]);
|
|
3988
|
+
const [loading, setLoading] = useState18(true);
|
|
3989
|
+
const [applying, setApplying] = useState18(false);
|
|
3990
|
+
const [error, setError] = useState18(null);
|
|
3991
|
+
useEffect13(() => {
|
|
3992
|
+
const fetchTransitions = async () => {
|
|
3993
|
+
const siteUrl = getJiraSiteUrl(repoPath);
|
|
3994
|
+
const creds = getJiraCredentials(repoPath);
|
|
3995
|
+
if (!siteUrl || !creds.email || !creds.apiToken) {
|
|
3996
|
+
setError("Jira not configured");
|
|
3997
|
+
duckEvents.emit("error");
|
|
3998
|
+
setLoading(false);
|
|
3999
|
+
return;
|
|
4000
|
+
}
|
|
4001
|
+
const auth = { siteUrl, email: creds.email, apiToken: creds.apiToken };
|
|
4002
|
+
const result = await getTransitions(auth, ticketKey);
|
|
2342
4003
|
if (result.success) {
|
|
2343
4004
|
setTransitions(result.data);
|
|
2344
4005
|
} else {
|
|
@@ -2365,7 +4026,7 @@ function ChangeStatusModal({ repoPath, ticketKey, currentStatus, onComplete, onC
|
|
|
2365
4026
|
if (result.success) {
|
|
2366
4027
|
const transition = transitions.find((t) => t.id === item.value);
|
|
2367
4028
|
const newStatus = (transition == null ? void 0 : transition.to.name) ?? item.label;
|
|
2368
|
-
duckEvents.emit("jira:transition");
|
|
4029
|
+
duckEvents.emit("jira:transition", { ticketKey, status: newStatus });
|
|
2369
4030
|
onComplete(newStatus);
|
|
2370
4031
|
} else {
|
|
2371
4032
|
setError(result.error);
|
|
@@ -2373,7 +4034,7 @@ function ChangeStatusModal({ repoPath, ticketKey, currentStatus, onComplete, onC
|
|
|
2373
4034
|
setApplying(false);
|
|
2374
4035
|
}
|
|
2375
4036
|
};
|
|
2376
|
-
|
|
4037
|
+
useInput12(
|
|
2377
4038
|
(_input, key) => {
|
|
2378
4039
|
if (key.escape && !applying) {
|
|
2379
4040
|
onCancel();
|
|
@@ -2389,24 +4050,24 @@ function ChangeStatusModal({ repoPath, ticketKey, currentStatus, onComplete, onC
|
|
|
2389
4050
|
0,
|
|
2390
4051
|
transitions.findIndex((t) => t.to.name === currentStatus)
|
|
2391
4052
|
);
|
|
2392
|
-
return /* @__PURE__ */
|
|
2393
|
-
/* @__PURE__ */
|
|
4053
|
+
return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, paddingY: 1, children: [
|
|
4054
|
+
/* @__PURE__ */ jsxs13(Text12, { bold: true, color: "yellow", children: [
|
|
2394
4055
|
"Change Status: ",
|
|
2395
4056
|
ticketKey
|
|
2396
4057
|
] }),
|
|
2397
|
-
loading && /* @__PURE__ */
|
|
2398
|
-
error && /* @__PURE__ */
|
|
2399
|
-
!loading && !error && transitions.length === 0 && /* @__PURE__ */
|
|
2400
|
-
!loading && !error && transitions.length > 0 && !applying && /* @__PURE__ */
|
|
2401
|
-
applying && /* @__PURE__ */
|
|
2402
|
-
/* @__PURE__ */
|
|
4058
|
+
loading && /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Loading transitions..." }),
|
|
4059
|
+
error && /* @__PURE__ */ jsx14(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { color: "red", children: error }) }),
|
|
4060
|
+
!loading && !error && transitions.length === 0 && /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "No available transitions" }),
|
|
4061
|
+
!loading && !error && transitions.length > 0 && !applying && /* @__PURE__ */ jsx14(Box14, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx14(SelectInput2, { items, initialIndex, onSelect: handleSelect }) }),
|
|
4062
|
+
applying && /* @__PURE__ */ jsx14(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { color: "yellow", children: "Updating status..." }) }),
|
|
4063
|
+
/* @__PURE__ */ jsx14(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Esc to cancel" }) })
|
|
2403
4064
|
] });
|
|
2404
4065
|
}
|
|
2405
4066
|
|
|
2406
4067
|
// src/components/jira/ConfigureJiraSiteModal.tsx
|
|
2407
|
-
import { useState as
|
|
2408
|
-
import { Box as
|
|
2409
|
-
import { ScrollView as
|
|
4068
|
+
import { useState as useState19 } from "react";
|
|
4069
|
+
import { Box as Box15, Text as Text13, useInput as useInput13 } from "ink";
|
|
4070
|
+
import { ScrollView as ScrollView8 } from "ink-scroll-view";
|
|
2410
4071
|
|
|
2411
4072
|
// src/lib/editor.ts
|
|
2412
4073
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
@@ -2437,7 +4098,7 @@ function openInEditor(content, filename) {
|
|
|
2437
4098
|
}
|
|
2438
4099
|
|
|
2439
4100
|
// src/components/jira/ConfigureJiraSiteModal.tsx
|
|
2440
|
-
import { jsx as
|
|
4101
|
+
import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
2441
4102
|
var MAX_VISIBLE_ITEMS = 4;
|
|
2442
4103
|
function ConfigureJiraSiteModal({
|
|
2443
4104
|
initialSiteUrl,
|
|
@@ -2449,17 +4110,17 @@ function ConfigureJiraSiteModal({
|
|
|
2449
4110
|
error
|
|
2450
4111
|
}) {
|
|
2451
4112
|
const hasExisting = existingConfigs.length > 0;
|
|
2452
|
-
const [mode, setMode] =
|
|
2453
|
-
const [selectedExisting, setSelectedExisting] =
|
|
4113
|
+
const [mode, setMode] = useState19(hasExisting ? "choose" : "manual");
|
|
4114
|
+
const [selectedExisting, setSelectedExisting] = useState19(0);
|
|
2454
4115
|
const scrollRef = useScrollToIndex(selectedExisting);
|
|
2455
|
-
const [siteUrl, setSiteUrl] =
|
|
2456
|
-
const [email, setEmail] =
|
|
2457
|
-
const [apiToken, setApiToken] =
|
|
2458
|
-
const [selectedItem, setSelectedItem] =
|
|
4116
|
+
const [siteUrl, setSiteUrl] = useState19(initialSiteUrl ?? "");
|
|
4117
|
+
const [email, setEmail] = useState19(initialEmail ?? "");
|
|
4118
|
+
const [apiToken, setApiToken] = useState19("");
|
|
4119
|
+
const [selectedItem, setSelectedItem] = useState19("siteUrl");
|
|
2459
4120
|
const items = ["siteUrl", "email", "apiToken", "submit"];
|
|
2460
4121
|
const canSubmit = siteUrl.trim() && email.trim() && apiToken.trim();
|
|
2461
4122
|
const chooseItems = existingConfigs.length + 1;
|
|
2462
|
-
|
|
4123
|
+
useInput13(
|
|
2463
4124
|
(input, key) => {
|
|
2464
4125
|
if (loading) return;
|
|
2465
4126
|
if (key.escape) {
|
|
@@ -2531,38 +4192,38 @@ function ConfigureJiraSiteModal({
|
|
|
2531
4192
|
const prefix = isSelected ? "> " : " ";
|
|
2532
4193
|
const color = isSelected ? "yellow" : void 0;
|
|
2533
4194
|
const displayValue = isSensitive && value ? "*".repeat(Math.min(value.length, 20)) : value;
|
|
2534
|
-
return /* @__PURE__ */
|
|
2535
|
-
/* @__PURE__ */
|
|
4195
|
+
return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
|
|
4196
|
+
/* @__PURE__ */ jsxs14(Text13, { color, bold: isSelected, children: [
|
|
2536
4197
|
prefix,
|
|
2537
4198
|
label
|
|
2538
4199
|
] }),
|
|
2539
|
-
value !== void 0 && /* @__PURE__ */
|
|
4200
|
+
value !== void 0 && /* @__PURE__ */ jsx15(Box15, { marginLeft: 4, children: /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: displayValue || "(empty - press Enter to edit)" }) })
|
|
2540
4201
|
] });
|
|
2541
4202
|
};
|
|
2542
4203
|
if (mode === "choose") {
|
|
2543
4204
|
const totalItems = existingConfigs.length + 1;
|
|
2544
4205
|
const listHeight = Math.min(totalItems * 2, MAX_VISIBLE_ITEMS * 2);
|
|
2545
|
-
return /* @__PURE__ */
|
|
2546
|
-
/* @__PURE__ */
|
|
2547
|
-
/* @__PURE__ */
|
|
2548
|
-
/* @__PURE__ */
|
|
2549
|
-
error && /* @__PURE__ */
|
|
2550
|
-
/* @__PURE__ */
|
|
4206
|
+
return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, paddingY: 1, children: [
|
|
4207
|
+
/* @__PURE__ */ jsx15(Text13, { bold: true, color: "cyan", children: "Configure Jira Site" }),
|
|
4208
|
+
/* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "Select an existing configuration or enter new credentials" }),
|
|
4209
|
+
/* @__PURE__ */ jsx15(Box15, { marginTop: 1 }),
|
|
4210
|
+
error && /* @__PURE__ */ jsx15(Box15, { marginBottom: 1, children: /* @__PURE__ */ jsx15(Text13, { color: "red", children: error }) }),
|
|
4211
|
+
/* @__PURE__ */ jsx15(Box15, { height: listHeight, overflow: "hidden", children: /* @__PURE__ */ jsxs14(ScrollView8, { ref: scrollRef, children: [
|
|
2551
4212
|
existingConfigs.map((config, idx) => {
|
|
2552
4213
|
const isSelected = selectedExisting === idx;
|
|
2553
|
-
return /* @__PURE__ */
|
|
2554
|
-
/* @__PURE__ */
|
|
4214
|
+
return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
|
|
4215
|
+
/* @__PURE__ */ jsxs14(Text13, { color: isSelected ? "yellow" : void 0, bold: isSelected, children: [
|
|
2555
4216
|
isSelected ? "> " : " ",
|
|
2556
4217
|
config.siteUrl
|
|
2557
4218
|
] }),
|
|
2558
|
-
/* @__PURE__ */
|
|
4219
|
+
/* @__PURE__ */ jsxs14(Text13, { dimColor: true, children: [
|
|
2559
4220
|
" ",
|
|
2560
4221
|
config.email
|
|
2561
4222
|
] })
|
|
2562
4223
|
] }, config.siteUrl + config.email);
|
|
2563
4224
|
}),
|
|
2564
|
-
/* @__PURE__ */
|
|
2565
|
-
|
|
4225
|
+
/* @__PURE__ */ jsx15(Box15, { children: /* @__PURE__ */ jsxs14(
|
|
4226
|
+
Text13,
|
|
2566
4227
|
{
|
|
2567
4228
|
color: selectedExisting === existingConfigs.length ? "yellow" : void 0,
|
|
2568
4229
|
bold: selectedExisting === existingConfigs.length,
|
|
@@ -2573,44 +4234,44 @@ function ConfigureJiraSiteModal({
|
|
|
2573
4234
|
}
|
|
2574
4235
|
) })
|
|
2575
4236
|
] }) }),
|
|
2576
|
-
loading && /* @__PURE__ */
|
|
4237
|
+
loading && /* @__PURE__ */ jsx15(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text13, { color: "yellow", children: "Validating credentials..." }) })
|
|
2577
4238
|
] });
|
|
2578
4239
|
}
|
|
2579
|
-
return /* @__PURE__ */
|
|
2580
|
-
/* @__PURE__ */
|
|
2581
|
-
/* @__PURE__ */
|
|
4240
|
+
return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, paddingY: 1, children: [
|
|
4241
|
+
/* @__PURE__ */ jsx15(Text13, { bold: true, color: "cyan", children: "Configure Jira Site" }),
|
|
4242
|
+
/* @__PURE__ */ jsxs14(Text13, { dimColor: true, children: [
|
|
2582
4243
|
"Up/Down to select, Enter to edit, Esc to ",
|
|
2583
4244
|
hasExisting ? "go back" : "cancel"
|
|
2584
4245
|
] }),
|
|
2585
|
-
/* @__PURE__ */
|
|
2586
|
-
error && /* @__PURE__ */
|
|
4246
|
+
/* @__PURE__ */ jsx15(Box15, { marginTop: 1 }),
|
|
4247
|
+
error && /* @__PURE__ */ jsx15(Box15, { marginBottom: 1, children: /* @__PURE__ */ jsx15(Text13, { color: "red", children: error }) }),
|
|
2587
4248
|
renderItem("siteUrl", "Site URL (e.g., https://company.atlassian.net)", siteUrl),
|
|
2588
|
-
/* @__PURE__ */
|
|
4249
|
+
/* @__PURE__ */ jsx15(Box15, { marginTop: 1 }),
|
|
2589
4250
|
renderItem("email", "Email", email),
|
|
2590
|
-
/* @__PURE__ */
|
|
4251
|
+
/* @__PURE__ */ jsx15(Box15, { marginTop: 1 }),
|
|
2591
4252
|
renderItem("apiToken", "API Token", apiToken, true),
|
|
2592
|
-
/* @__PURE__ */
|
|
2593
|
-
/* @__PURE__ */
|
|
4253
|
+
/* @__PURE__ */ jsx15(Box15, { marginTop: 1 }),
|
|
4254
|
+
/* @__PURE__ */ jsx15(Box15, { children: /* @__PURE__ */ jsxs14(Text13, { color: selectedItem === "submit" ? "green" : void 0, bold: selectedItem === "submit", children: [
|
|
2594
4255
|
selectedItem === "submit" ? "> " : " ",
|
|
2595
4256
|
canSubmit ? "[Save Configuration]" : "[Fill all fields first]"
|
|
2596
4257
|
] }) }),
|
|
2597
|
-
loading && /* @__PURE__ */
|
|
2598
|
-
/* @__PURE__ */
|
|
4258
|
+
loading && /* @__PURE__ */ jsx15(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text13, { color: "yellow", children: "Validating credentials..." }) }),
|
|
4259
|
+
/* @__PURE__ */ jsx15(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "Get your API token from: https://id.atlassian.com/manage-profile/security/api-tokens" }) })
|
|
2599
4260
|
] });
|
|
2600
4261
|
}
|
|
2601
4262
|
|
|
2602
4263
|
// src/components/jira/TicketItem.tsx
|
|
2603
|
-
import { Box as
|
|
2604
|
-
import { jsx as
|
|
4264
|
+
import { Box as Box16, Text as Text14 } from "ink";
|
|
4265
|
+
import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
2605
4266
|
function TicketItem({ ticketKey, summary, status, isHighlighted, isSelected }) {
|
|
2606
4267
|
const prefix = isHighlighted ? "> " : isSelected ? "\u25CF " : " ";
|
|
2607
4268
|
const textColor = isSelected ? "green" : void 0;
|
|
2608
|
-
return /* @__PURE__ */
|
|
4269
|
+
return /* @__PURE__ */ jsx16(Box16, { children: /* @__PURE__ */ jsxs15(Text14, { color: textColor, children: [
|
|
2609
4270
|
prefix,
|
|
2610
|
-
/* @__PURE__ */
|
|
4271
|
+
/* @__PURE__ */ jsx16(Text14, { bold: true, color: "blue", children: ticketKey }),
|
|
2611
4272
|
" ",
|
|
2612
4273
|
summary,
|
|
2613
|
-
status && /* @__PURE__ */
|
|
4274
|
+
status && /* @__PURE__ */ jsxs15(Text14, { dimColor: true, children: [
|
|
2614
4275
|
" [",
|
|
2615
4276
|
status,
|
|
2616
4277
|
"]"
|
|
@@ -2619,15 +4280,15 @@ function TicketItem({ ticketKey, summary, status, isHighlighted, isSelected }) {
|
|
|
2619
4280
|
}
|
|
2620
4281
|
|
|
2621
4282
|
// src/components/jira/JiraView.tsx
|
|
2622
|
-
import { jsx as
|
|
4283
|
+
import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
2623
4284
|
function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated }) {
|
|
2624
4285
|
const repo = useGitRepo();
|
|
2625
4286
|
const jira = useJiraTickets();
|
|
2626
4287
|
const modal = useModal();
|
|
2627
4288
|
const nav = useListNavigation({ items: jira.tickets });
|
|
2628
4289
|
const currentTicket = jira.tickets[nav.index] ?? null;
|
|
2629
|
-
const lastInitRef =
|
|
2630
|
-
|
|
4290
|
+
const lastInitRef = useRef8(null);
|
|
4291
|
+
useEffect14(() => {
|
|
2631
4292
|
if (repo.loading || !repo.repoPath || !repo.currentBranch) return;
|
|
2632
4293
|
const current = { branch: repo.currentBranch };
|
|
2633
4294
|
const last = lastInitRef.current;
|
|
@@ -2635,17 +4296,17 @@ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated })
|
|
|
2635
4296
|
lastInitRef.current = current;
|
|
2636
4297
|
jira.initializeJiraState(repo.repoPath, repo.currentBranch, repo.currentRepoSlug);
|
|
2637
4298
|
}, [repo.loading, repo.repoPath, repo.currentBranch, repo.currentRepoSlug, jira.initializeJiraState]);
|
|
2638
|
-
|
|
4299
|
+
useEffect14(() => {
|
|
2639
4300
|
if (isActive) {
|
|
2640
4301
|
repo.refreshBranch();
|
|
2641
4302
|
} else {
|
|
2642
4303
|
modal.close();
|
|
2643
4304
|
}
|
|
2644
4305
|
}, [isActive, repo.refreshBranch, modal.close]);
|
|
2645
|
-
|
|
4306
|
+
useEffect14(() => {
|
|
2646
4307
|
onModalChange == null ? void 0 : onModalChange(modal.isOpen);
|
|
2647
4308
|
}, [modal.isOpen, onModalChange]);
|
|
2648
|
-
|
|
4309
|
+
useEffect14(() => {
|
|
2649
4310
|
onJiraStateChange == null ? void 0 : onJiraStateChange(jira.jiraState);
|
|
2650
4311
|
}, [jira.jiraState, onJiraStateChange]);
|
|
2651
4312
|
const handleConfigureSubmit = async (siteUrl, email, apiToken) => {
|
|
@@ -2671,7 +4332,7 @@ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated })
|
|
|
2671
4332
|
};
|
|
2672
4333
|
const handleOpenInBrowser = () => {
|
|
2673
4334
|
const url = getTicketUrl();
|
|
2674
|
-
if (url)
|
|
4335
|
+
if (url) open5(url).catch(() => {
|
|
2675
4336
|
});
|
|
2676
4337
|
};
|
|
2677
4338
|
const handleCopyLink = () => {
|
|
@@ -2691,7 +4352,7 @@ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated })
|
|
|
2691
4352
|
clearJiraConfig(repo.repoPath);
|
|
2692
4353
|
jira.initializeJiraState(repo.repoPath, repo.currentBranch, repo.currentRepoSlug);
|
|
2693
4354
|
};
|
|
2694
|
-
|
|
4355
|
+
useInput14(
|
|
2695
4356
|
(input, key) => {
|
|
2696
4357
|
if (input === "c" && jira.jiraState === "not_configured") {
|
|
2697
4358
|
modal.open("configure");
|
|
@@ -2717,13 +4378,13 @@ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated })
|
|
|
2717
4378
|
{ isActive: isActive && !modal.isOpen }
|
|
2718
4379
|
);
|
|
2719
4380
|
if (repo.isRepo === false) {
|
|
2720
|
-
return /* @__PURE__ */
|
|
4381
|
+
return /* @__PURE__ */ jsx17(TitledBox6, { borderStyle: "round", titles: ["Jira"], flexShrink: 0, children: /* @__PURE__ */ jsx17(Text15, { color: "red", children: "Not a git repository" }) });
|
|
2721
4382
|
}
|
|
2722
4383
|
if (modal.type === "configure") {
|
|
2723
4384
|
const siteUrl = repo.repoPath ? getJiraSiteUrl(repo.repoPath) : void 0;
|
|
2724
4385
|
const creds = repo.repoPath ? getJiraCredentials(repo.repoPath) : { email: null, apiToken: null };
|
|
2725
4386
|
const existingConfigs = getExistingJiraConfigs(repo.repoPath ?? void 0);
|
|
2726
|
-
return /* @__PURE__ */
|
|
4387
|
+
return /* @__PURE__ */ jsx17(Box17, { flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsx17(
|
|
2727
4388
|
ConfigureJiraSiteModal,
|
|
2728
4389
|
{
|
|
2729
4390
|
initialSiteUrl: siteUrl ?? void 0,
|
|
@@ -2740,7 +4401,7 @@ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated })
|
|
|
2740
4401
|
) });
|
|
2741
4402
|
}
|
|
2742
4403
|
if (modal.type === "link") {
|
|
2743
|
-
return /* @__PURE__ */
|
|
4404
|
+
return /* @__PURE__ */ jsx17(Box17, { flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsx17(
|
|
2744
4405
|
LinkTicketModal,
|
|
2745
4406
|
{
|
|
2746
4407
|
onSubmit: handleLinkSubmit,
|
|
@@ -2754,7 +4415,7 @@ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated })
|
|
|
2754
4415
|
) });
|
|
2755
4416
|
}
|
|
2756
4417
|
if (modal.type === "status" && repo.repoPath && repo.currentBranch && currentTicket) {
|
|
2757
|
-
return /* @__PURE__ */
|
|
4418
|
+
return /* @__PURE__ */ jsx17(Box17, { flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsx17(
|
|
2758
4419
|
ChangeStatusModal,
|
|
2759
4420
|
{
|
|
2760
4421
|
repoPath: repo.repoPath,
|
|
@@ -2767,10 +4428,10 @@ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated })
|
|
|
2767
4428
|
}
|
|
2768
4429
|
const title = "[4] Jira";
|
|
2769
4430
|
const borderColor = isActive ? "yellow" : void 0;
|
|
2770
|
-
return /* @__PURE__ */
|
|
2771
|
-
jira.jiraState === "not_configured" && /* @__PURE__ */
|
|
2772
|
-
jira.jiraState === "no_tickets" && /* @__PURE__ */
|
|
2773
|
-
jira.jiraState === "has_tickets" && jira.tickets.map((ticket, idx) => /* @__PURE__ */
|
|
4431
|
+
return /* @__PURE__ */ jsx17(TitledBox6, { borderStyle: "round", titles: [title], borderColor, flexShrink: 0, children: /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", paddingX: 1, children: [
|
|
4432
|
+
jira.jiraState === "not_configured" && /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "No Jira site configured" }),
|
|
4433
|
+
jira.jiraState === "no_tickets" && /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "No tickets linked to this branch" }),
|
|
4434
|
+
jira.jiraState === "has_tickets" && jira.tickets.map((ticket, idx) => /* @__PURE__ */ jsx17(
|
|
2774
4435
|
TicketItem,
|
|
2775
4436
|
{
|
|
2776
4437
|
ticketKey: ticket.key,
|
|
@@ -2784,28 +4445,28 @@ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated })
|
|
|
2784
4445
|
}
|
|
2785
4446
|
|
|
2786
4447
|
// src/components/logs/LogsView.tsx
|
|
2787
|
-
import { useEffect as
|
|
2788
|
-
import { Box as
|
|
4448
|
+
import { useEffect as useEffect16 } from "react";
|
|
4449
|
+
import { Box as Box20, useInput as useInput17 } from "ink";
|
|
2789
4450
|
|
|
2790
4451
|
// src/components/logs/LogViewerBox.tsx
|
|
2791
|
-
import { useEffect as
|
|
2792
|
-
import { TitledBox as
|
|
2793
|
-
import { Box as
|
|
2794
|
-
import { ScrollView as
|
|
2795
|
-
import
|
|
4452
|
+
import { useEffect as useEffect15, useRef as useRef9, useState as useState20 } from "react";
|
|
4453
|
+
import { TitledBox as TitledBox7 } from "@mishieck/ink-titled-box";
|
|
4454
|
+
import { Box as Box18, Text as Text16, useInput as useInput15 } from "ink";
|
|
4455
|
+
import { ScrollView as ScrollView9 } from "ink-scroll-view";
|
|
4456
|
+
import Spinner4 from "ink-spinner";
|
|
2796
4457
|
import TextInput2 from "ink-text-input";
|
|
2797
|
-
import { jsx as
|
|
4458
|
+
import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
2798
4459
|
function LogViewerBox({ date, content, isActive, onRefresh, onLogCreated }) {
|
|
2799
|
-
const scrollRef =
|
|
2800
|
-
const [isInputMode, setIsInputMode] =
|
|
2801
|
-
const [inputValue, setInputValue] =
|
|
2802
|
-
const [isGeneratingStandup, setIsGeneratingStandup] =
|
|
2803
|
-
const [standupResult, setStandupResult] =
|
|
2804
|
-
const claudeProcessRef =
|
|
4460
|
+
const scrollRef = useRef9(null);
|
|
4461
|
+
const [isInputMode, setIsInputMode] = useState20(false);
|
|
4462
|
+
const [inputValue, setInputValue] = useState20("");
|
|
4463
|
+
const [isGeneratingStandup, setIsGeneratingStandup] = useState20(false);
|
|
4464
|
+
const [standupResult, setStandupResult] = useState20(null);
|
|
4465
|
+
const claudeProcessRef = useRef9(null);
|
|
2805
4466
|
const title = "[6] Log Content";
|
|
2806
4467
|
const borderColor = isActive ? "yellow" : void 0;
|
|
2807
4468
|
const displayTitle = date ? `${title} - ${date}.md` : title;
|
|
2808
|
-
|
|
4469
|
+
useInput15(
|
|
2809
4470
|
(input, key) => {
|
|
2810
4471
|
var _a, _b, _c;
|
|
2811
4472
|
if (key.escape && isInputMode) {
|
|
@@ -2867,7 +4528,7 @@ function LogViewerBox({ date, content, isActive, onRefresh, onLogCreated }) {
|
|
|
2867
4528
|
},
|
|
2868
4529
|
{ isActive }
|
|
2869
4530
|
);
|
|
2870
|
-
|
|
4531
|
+
useEffect15(() => {
|
|
2871
4532
|
return () => {
|
|
2872
4533
|
var _a;
|
|
2873
4534
|
(_a = claudeProcessRef.current) == null ? void 0 : _a.cancel();
|
|
@@ -2890,14 +4551,14 @@ ${value.trim()}
|
|
|
2890
4551
|
setIsInputMode(false);
|
|
2891
4552
|
onRefresh();
|
|
2892
4553
|
};
|
|
2893
|
-
return /* @__PURE__ */
|
|
2894
|
-
/* @__PURE__ */
|
|
2895
|
-
!date && /* @__PURE__ */
|
|
2896
|
-
date && content === null && /* @__PURE__ */
|
|
2897
|
-
date && content !== null && content.trim() === "" && /* @__PURE__ */
|
|
2898
|
-
date && content && content.trim() !== "" && /* @__PURE__ */
|
|
4554
|
+
return /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", flexGrow: 1, children: [
|
|
4555
|
+
/* @__PURE__ */ jsx18(TitledBox7, { borderStyle: "round", titles: [displayTitle], borderColor, flexGrow: 1, children: /* @__PURE__ */ jsx18(Box18, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx18(ScrollView9, { ref: scrollRef, children: /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", paddingX: 1, children: [
|
|
4556
|
+
!date && /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Select a log file to view" }),
|
|
4557
|
+
date && content === null && /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Log file not found" }),
|
|
4558
|
+
date && content !== null && content.trim() === "" && /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Empty log file" }),
|
|
4559
|
+
date && content && content.trim() !== "" && /* @__PURE__ */ jsx18(Markdown, { children: content })
|
|
2899
4560
|
] }) }) }) }),
|
|
2900
|
-
isInputMode && /* @__PURE__ */
|
|
4561
|
+
isInputMode && /* @__PURE__ */ jsx18(TitledBox7, { borderStyle: "round", titles: ["Add Entry"], borderColor: "yellow", children: /* @__PURE__ */ jsx18(Box18, { paddingX: 1, children: /* @__PURE__ */ jsx18(
|
|
2901
4562
|
TextInput2,
|
|
2902
4563
|
{
|
|
2903
4564
|
value: inputValue,
|
|
@@ -2905,22 +4566,22 @@ ${value.trim()}
|
|
|
2905
4566
|
onSubmit: handleInputSubmit
|
|
2906
4567
|
}
|
|
2907
4568
|
) }) }),
|
|
2908
|
-
isGeneratingStandup && /* @__PURE__ */
|
|
2909
|
-
/* @__PURE__ */
|
|
2910
|
-
/* @__PURE__ */
|
|
4569
|
+
isGeneratingStandup && /* @__PURE__ */ jsx18(TitledBox7, { borderStyle: "round", titles: ["Standup Notes"], borderColor: "yellow", children: /* @__PURE__ */ jsxs17(Box18, { paddingX: 1, flexDirection: "column", children: [
|
|
4570
|
+
/* @__PURE__ */ jsxs17(Text16, { color: "yellow", children: [
|
|
4571
|
+
/* @__PURE__ */ jsx18(Spinner4, { type: "dots" }),
|
|
2911
4572
|
" Generating standup notes..."
|
|
2912
4573
|
] }),
|
|
2913
|
-
/* @__PURE__ */
|
|
4574
|
+
/* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Press Esc to cancel" })
|
|
2914
4575
|
] }) }),
|
|
2915
|
-
standupResult && /* @__PURE__ */
|
|
2916
|
-
|
|
4576
|
+
standupResult && /* @__PURE__ */ jsx18(
|
|
4577
|
+
TitledBox7,
|
|
2917
4578
|
{
|
|
2918
4579
|
borderStyle: "round",
|
|
2919
4580
|
titles: ["Standup Notes"],
|
|
2920
4581
|
borderColor: standupResult.type === "error" ? "red" : "green",
|
|
2921
|
-
children: /* @__PURE__ */
|
|
2922
|
-
standupResult.type === "error" ? /* @__PURE__ */
|
|
2923
|
-
/* @__PURE__ */
|
|
4582
|
+
children: /* @__PURE__ */ jsxs17(Box18, { paddingX: 1, flexDirection: "column", children: [
|
|
4583
|
+
standupResult.type === "error" ? /* @__PURE__ */ jsx18(Text16, { color: "red", children: standupResult.message }) : /* @__PURE__ */ jsx18(Markdown, { children: standupResult.message }),
|
|
4584
|
+
/* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Press Esc to dismiss" })
|
|
2924
4585
|
] })
|
|
2925
4586
|
}
|
|
2926
4587
|
)
|
|
@@ -2928,10 +4589,10 @@ ${value.trim()}
|
|
|
2928
4589
|
}
|
|
2929
4590
|
|
|
2930
4591
|
// src/components/logs/LogsHistoryBox.tsx
|
|
2931
|
-
import { TitledBox as
|
|
2932
|
-
import { Box as
|
|
2933
|
-
import { ScrollView as
|
|
2934
|
-
import { jsx as
|
|
4592
|
+
import { TitledBox as TitledBox8 } from "@mishieck/ink-titled-box";
|
|
4593
|
+
import { Box as Box19, Text as Text17, useInput as useInput16 } from "ink";
|
|
4594
|
+
import { ScrollView as ScrollView10 } from "ink-scroll-view";
|
|
4595
|
+
import { jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
2935
4596
|
function LogsHistoryBox({
|
|
2936
4597
|
logFiles,
|
|
2937
4598
|
selectedDate,
|
|
@@ -2943,7 +4604,7 @@ function LogsHistoryBox({
|
|
|
2943
4604
|
const scrollRef = useScrollToIndex(highlightedIndex);
|
|
2944
4605
|
const title = "[5] Logs";
|
|
2945
4606
|
const borderColor = isActive ? "yellow" : void 0;
|
|
2946
|
-
|
|
4607
|
+
useInput16(
|
|
2947
4608
|
(input, key) => {
|
|
2948
4609
|
if (logFiles.length === 0) return;
|
|
2949
4610
|
if (key.upArrow || input === "k") {
|
|
@@ -2961,44 +4622,44 @@ function LogsHistoryBox({
|
|
|
2961
4622
|
},
|
|
2962
4623
|
{ isActive }
|
|
2963
4624
|
);
|
|
2964
|
-
return /* @__PURE__ */
|
|
2965
|
-
logFiles.length === 0 && /* @__PURE__ */
|
|
2966
|
-
logFiles.length > 0 && /* @__PURE__ */
|
|
4625
|
+
return /* @__PURE__ */ jsx19(TitledBox8, { borderStyle: "round", titles: [title], borderColor, height: 5, children: /* @__PURE__ */ jsxs18(Box19, { flexDirection: "column", paddingX: 1, flexGrow: 1, overflow: "hidden", children: [
|
|
4626
|
+
logFiles.length === 0 && /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: "No logs yet" }),
|
|
4627
|
+
logFiles.length > 0 && /* @__PURE__ */ jsx19(ScrollView10, { ref: scrollRef, children: logFiles.map((file, idx) => {
|
|
2967
4628
|
const isHighlighted = idx === highlightedIndex;
|
|
2968
4629
|
const isSelected = file.date === selectedDate;
|
|
2969
4630
|
const cursor = isHighlighted ? ">" : " ";
|
|
2970
4631
|
const indicator = isSelected ? " *" : "";
|
|
2971
|
-
return /* @__PURE__ */
|
|
2972
|
-
/* @__PURE__ */
|
|
4632
|
+
return /* @__PURE__ */ jsxs18(Box19, { children: [
|
|
4633
|
+
/* @__PURE__ */ jsxs18(Text17, { color: isHighlighted ? "yellow" : void 0, children: [
|
|
2973
4634
|
cursor,
|
|
2974
4635
|
" "
|
|
2975
4636
|
] }),
|
|
2976
|
-
/* @__PURE__ */
|
|
2977
|
-
file.isToday && /* @__PURE__ */
|
|
2978
|
-
/* @__PURE__ */
|
|
4637
|
+
/* @__PURE__ */ jsx19(Text17, { color: file.isToday ? "green" : void 0, bold: file.isToday, children: file.date }),
|
|
4638
|
+
file.isToday && /* @__PURE__ */ jsx19(Text17, { color: "green", children: " (today)" }),
|
|
4639
|
+
/* @__PURE__ */ jsx19(Text17, { dimColor: true, children: indicator })
|
|
2979
4640
|
] }, file.date);
|
|
2980
4641
|
}) })
|
|
2981
4642
|
] }) });
|
|
2982
4643
|
}
|
|
2983
4644
|
|
|
2984
4645
|
// src/components/logs/LogsView.tsx
|
|
2985
|
-
import { jsx as
|
|
4646
|
+
import { jsx as jsx20, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
2986
4647
|
function LogsView({ isActive, refreshKey, focusedBox, onFocusedBoxChange }) {
|
|
2987
4648
|
const logs = useLogs();
|
|
2988
|
-
|
|
4649
|
+
useEffect16(() => {
|
|
2989
4650
|
if (refreshKey !== void 0 && refreshKey > 0) {
|
|
2990
4651
|
logs.handleExternalLogUpdate();
|
|
2991
4652
|
}
|
|
2992
4653
|
}, [refreshKey, logs.handleExternalLogUpdate]);
|
|
2993
|
-
|
|
4654
|
+
useInput17(
|
|
2994
4655
|
(input) => {
|
|
2995
4656
|
if (input === "5") onFocusedBoxChange("history");
|
|
2996
4657
|
if (input === "6") onFocusedBoxChange("viewer");
|
|
2997
4658
|
},
|
|
2998
4659
|
{ isActive }
|
|
2999
4660
|
);
|
|
3000
|
-
return /* @__PURE__ */
|
|
3001
|
-
/* @__PURE__ */
|
|
4661
|
+
return /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", flexGrow: 1, children: [
|
|
4662
|
+
/* @__PURE__ */ jsx20(
|
|
3002
4663
|
LogsHistoryBox,
|
|
3003
4664
|
{
|
|
3004
4665
|
logFiles: logs.logFiles,
|
|
@@ -3009,7 +4670,7 @@ function LogsView({ isActive, refreshKey, focusedBox, onFocusedBoxChange }) {
|
|
|
3009
4670
|
isActive: isActive && focusedBox === "history"
|
|
3010
4671
|
}
|
|
3011
4672
|
),
|
|
3012
|
-
/* @__PURE__ */
|
|
4673
|
+
/* @__PURE__ */ jsx20(
|
|
3013
4674
|
LogViewerBox,
|
|
3014
4675
|
{
|
|
3015
4676
|
date: logs.selectedDate,
|
|
@@ -3023,10 +4684,11 @@ function LogsView({ isActive, refreshKey, focusedBox, onFocusedBoxChange }) {
|
|
|
3023
4684
|
}
|
|
3024
4685
|
|
|
3025
4686
|
// src/components/ui/KeybindingsBar.tsx
|
|
3026
|
-
import { Box as
|
|
3027
|
-
import { jsx as
|
|
4687
|
+
import { Box as Box21, Text as Text18 } from "ink";
|
|
4688
|
+
import { jsx as jsx21, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
3028
4689
|
var globalBindings = [
|
|
3029
4690
|
{ key: "1-4", label: "Focus" },
|
|
4691
|
+
{ key: "Tab", label: "Switch Tab" },
|
|
3030
4692
|
{ key: "j/k", label: "Navigate" },
|
|
3031
4693
|
{ key: "Ctrl+C", label: "Quit" }
|
|
3032
4694
|
];
|
|
@@ -3034,18 +4696,24 @@ var modalBindings = [{ key: "Esc", label: "Cancel" }];
|
|
|
3034
4696
|
var DUCK_ASCII = "<(')___";
|
|
3035
4697
|
function KeybindingsBar({ contextBindings = [], modalOpen = false, duck }) {
|
|
3036
4698
|
const allBindings = modalOpen ? [...contextBindings, ...modalBindings] : [...contextBindings, ...globalBindings];
|
|
3037
|
-
return /* @__PURE__ */
|
|
3038
|
-
allBindings.map((binding) => /* @__PURE__ */
|
|
3039
|
-
/* @__PURE__ */
|
|
3040
|
-
/* @__PURE__ */
|
|
4699
|
+
return /* @__PURE__ */ jsxs20(Box21, { flexShrink: 0, paddingX: 1, gap: 2, children: [
|
|
4700
|
+
allBindings.map((binding) => /* @__PURE__ */ jsxs20(Box21, { gap: 1, children: [
|
|
4701
|
+
/* @__PURE__ */ jsx21(Text18, { bold: true, color: binding.color ?? "yellow", children: binding.key }),
|
|
4702
|
+
/* @__PURE__ */ jsx21(Text18, { dimColor: true, children: binding.label })
|
|
3041
4703
|
] }, binding.key)),
|
|
3042
|
-
(duck == null ? void 0 : duck.visible) && /* @__PURE__ */
|
|
3043
|
-
/* @__PURE__ */
|
|
3044
|
-
/* @__PURE__ */
|
|
4704
|
+
(duck == null ? void 0 : duck.visible) && /* @__PURE__ */ jsxs20(Box21, { flexGrow: 1, justifyContent: "flex-end", gap: 1, children: [
|
|
4705
|
+
/* @__PURE__ */ jsx21(Text18, { children: DUCK_ASCII }),
|
|
4706
|
+
/* @__PURE__ */ jsx21(Text18, { dimColor: true, children: duck.message })
|
|
3045
4707
|
] })
|
|
3046
4708
|
] });
|
|
3047
4709
|
}
|
|
3048
4710
|
|
|
4711
|
+
// src/constants/tabs.ts
|
|
4712
|
+
var COLUMN2_TABS = [
|
|
4713
|
+
{ id: "logs", label: "Logs" },
|
|
4714
|
+
{ id: "jira-browser", label: "Jira" }
|
|
4715
|
+
];
|
|
4716
|
+
|
|
3049
4717
|
// src/constants/github.ts
|
|
3050
4718
|
var GITHUB_KEYBINDINGS = {
|
|
3051
4719
|
remotes: [{ key: "Space", label: "Select Remote" }],
|
|
@@ -3063,6 +4731,27 @@ var GITHUB_KEYBINDINGS = {
|
|
|
3063
4731
|
]
|
|
3064
4732
|
};
|
|
3065
4733
|
|
|
4734
|
+
// src/constants/jira-browser.ts
|
|
4735
|
+
var JIRA_BROWSER_KEYBINDINGS = {
|
|
4736
|
+
"saved-views": [
|
|
4737
|
+
{ key: "Space", label: "Select" },
|
|
4738
|
+
{ key: "a", label: "Add View", color: "green" },
|
|
4739
|
+
{ key: "e", label: "Rename" },
|
|
4740
|
+
{ key: "d", label: "Delete", color: "red" }
|
|
4741
|
+
],
|
|
4742
|
+
browser: [
|
|
4743
|
+
{ key: "Enter", label: "Details" },
|
|
4744
|
+
{ key: "/", label: "Filter" },
|
|
4745
|
+
{ key: "u", label: "Unassigned" },
|
|
4746
|
+
{ key: "m", label: "Mine" },
|
|
4747
|
+
{ key: "x", label: "Clear Filters" },
|
|
4748
|
+
{ key: "l", label: "Load More" },
|
|
4749
|
+
{ key: "o", label: "Open", color: "green" },
|
|
4750
|
+
{ key: "y", label: "Copy Link" },
|
|
4751
|
+
{ key: "r", label: "Refresh" }
|
|
4752
|
+
]
|
|
4753
|
+
};
|
|
4754
|
+
|
|
3066
4755
|
// src/constants/jira.ts
|
|
3067
4756
|
var JIRA_KEYBINDINGS = {
|
|
3068
4757
|
not_configured: [{ key: "c", label: "Configure Jira" }],
|
|
@@ -3102,34 +4791,42 @@ function computeKeybindings(focusedView, state) {
|
|
|
3102
4791
|
return JIRA_KEYBINDINGS[state.jira.jiraState];
|
|
3103
4792
|
case "logs":
|
|
3104
4793
|
return LOGS_KEYBINDINGS[state.logs.focusedBox];
|
|
4794
|
+
case "jira-browser":
|
|
4795
|
+
if (state["jira-browser"].modalOpen) return [];
|
|
4796
|
+
return JIRA_BROWSER_KEYBINDINGS[state["jira-browser"].focusedBox];
|
|
3105
4797
|
default:
|
|
3106
4798
|
return [];
|
|
3107
4799
|
}
|
|
3108
4800
|
}
|
|
3109
4801
|
|
|
3110
4802
|
// src/app.tsx
|
|
3111
|
-
import { jsx as
|
|
4803
|
+
import { jsx as jsx22, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
3112
4804
|
function App() {
|
|
3113
4805
|
const { exit } = useApp();
|
|
3114
|
-
const [focusedView, setFocusedView] =
|
|
3115
|
-
const [modalOpen, setModalOpen] =
|
|
3116
|
-
const [logRefreshKey, setLogRefreshKey] =
|
|
4806
|
+
const [focusedView, setFocusedView] = useState21("github");
|
|
4807
|
+
const [modalOpen, setModalOpen] = useState21(false);
|
|
4808
|
+
const [logRefreshKey, setLogRefreshKey] = useState21(0);
|
|
4809
|
+
const [activeTab, setActiveTab] = useState21("logs");
|
|
3117
4810
|
const duck = useRubberDuck();
|
|
3118
|
-
const [githubFocusedBox, setGithubFocusedBox] =
|
|
3119
|
-
const [jiraState, setJiraState] =
|
|
3120
|
-
const [logsFocusedBox, setLogsFocusedBox] =
|
|
3121
|
-
const
|
|
4811
|
+
const [githubFocusedBox, setGithubFocusedBox] = useState21("remotes");
|
|
4812
|
+
const [jiraState, setJiraState] = useState21("not_configured");
|
|
4813
|
+
const [logsFocusedBox, setLogsFocusedBox] = useState21("history");
|
|
4814
|
+
const [jiraBrowserFocusedBox, setJiraBrowserFocusedBox] = useState21("saved-views");
|
|
4815
|
+
const [jiraBrowserModalOpen, setJiraBrowserModalOpen] = useState21(false);
|
|
4816
|
+
const keybindings = useMemo4(
|
|
3122
4817
|
() => computeKeybindings(focusedView, {
|
|
3123
4818
|
github: { focusedBox: githubFocusedBox },
|
|
3124
4819
|
jira: { jiraState, modalOpen },
|
|
3125
|
-
logs: { focusedBox: logsFocusedBox }
|
|
4820
|
+
logs: { focusedBox: logsFocusedBox },
|
|
4821
|
+
"jira-browser": { focusedBox: jiraBrowserFocusedBox, modalOpen: jiraBrowserModalOpen }
|
|
3126
4822
|
}),
|
|
3127
|
-
[focusedView, githubFocusedBox, jiraState, modalOpen, logsFocusedBox]
|
|
4823
|
+
[focusedView, githubFocusedBox, jiraState, modalOpen, logsFocusedBox, jiraBrowserFocusedBox, jiraBrowserModalOpen]
|
|
3128
4824
|
);
|
|
3129
|
-
const handleLogUpdated =
|
|
4825
|
+
const handleLogUpdated = useCallback11(() => {
|
|
3130
4826
|
setLogRefreshKey((prev) => prev + 1);
|
|
3131
4827
|
}, []);
|
|
3132
|
-
|
|
4828
|
+
const anyModalOpen = modalOpen || jiraBrowserModalOpen;
|
|
4829
|
+
useInput18(
|
|
3133
4830
|
(input, key) => {
|
|
3134
4831
|
if (key.ctrl && input === "c") {
|
|
3135
4832
|
exit();
|
|
@@ -3141,12 +4838,23 @@ function App() {
|
|
|
3141
4838
|
setFocusedView("jira");
|
|
3142
4839
|
}
|
|
3143
4840
|
if (input === "5") {
|
|
3144
|
-
setFocusedView(
|
|
3145
|
-
setLogsFocusedBox("history");
|
|
4841
|
+
setFocusedView(activeTab);
|
|
4842
|
+
if (activeTab === "logs") setLogsFocusedBox("history");
|
|
4843
|
+
if (activeTab === "jira-browser") setJiraBrowserFocusedBox("saved-views");
|
|
3146
4844
|
}
|
|
3147
4845
|
if (input === "6") {
|
|
3148
|
-
setFocusedView(
|
|
3149
|
-
setLogsFocusedBox("viewer");
|
|
4846
|
+
setFocusedView(activeTab);
|
|
4847
|
+
if (activeTab === "logs") setLogsFocusedBox("viewer");
|
|
4848
|
+
if (activeTab === "jira-browser") setJiraBrowserFocusedBox("browser");
|
|
4849
|
+
}
|
|
4850
|
+
if (key.tab) {
|
|
4851
|
+
setActiveTab((current) => {
|
|
4852
|
+
const idx = COLUMN2_TABS.findIndex((t) => t.id === current);
|
|
4853
|
+
const next = (idx + 1) % COLUMN2_TABS.length;
|
|
4854
|
+
const nextTab = COLUMN2_TABS[next];
|
|
4855
|
+
setFocusedView(nextTab.id);
|
|
4856
|
+
return nextTab.id;
|
|
4857
|
+
});
|
|
3150
4858
|
}
|
|
3151
4859
|
if (input === "d") {
|
|
3152
4860
|
duck.toggleDuck();
|
|
@@ -3155,12 +4863,19 @@ function App() {
|
|
|
3155
4863
|
duck.quack();
|
|
3156
4864
|
}
|
|
3157
4865
|
},
|
|
3158
|
-
{ isActive: !
|
|
4866
|
+
{ isActive: !anyModalOpen }
|
|
3159
4867
|
);
|
|
3160
|
-
return /* @__PURE__ */
|
|
3161
|
-
/* @__PURE__ */
|
|
3162
|
-
/* @__PURE__ */
|
|
3163
|
-
|
|
4868
|
+
return /* @__PURE__ */ jsxs21(Box22, { flexGrow: 1, flexDirection: "column", overflow: "hidden", children: [
|
|
4869
|
+
/* @__PURE__ */ jsxs21(Box22, { height: 1, flexDirection: "row", columnGap: 1, children: [
|
|
4870
|
+
/* @__PURE__ */ jsx22(Box22, { flexGrow: 1, paddingX: 1, flexBasis: 0, children: /* @__PURE__ */ jsx22(Text19, { color: "gray", children: "Current branch" }) }),
|
|
4871
|
+
/* @__PURE__ */ jsxs21(Box22, { flexGrow: 1, gap: 1, flexBasis: 0, children: [
|
|
4872
|
+
/* @__PURE__ */ jsx22(Text19, { color: "gray", children: "Dashboards" }),
|
|
4873
|
+
COLUMN2_TABS.map((tab) => /* @__PURE__ */ jsx22(Text19, { bold: true, dimColor: activeTab !== tab.id, children: tab.label }, tab.id))
|
|
4874
|
+
] })
|
|
4875
|
+
] }),
|
|
4876
|
+
/* @__PURE__ */ jsxs21(Box22, { flexGrow: 1, flexDirection: "row", columnGap: 1, children: [
|
|
4877
|
+
/* @__PURE__ */ jsxs21(Box22, { flexDirection: "column", flexGrow: 1, flexBasis: 0, children: [
|
|
4878
|
+
/* @__PURE__ */ jsx22(
|
|
3164
4879
|
GitHubView,
|
|
3165
4880
|
{
|
|
3166
4881
|
isActive: focusedView === "github",
|
|
@@ -3168,7 +4883,7 @@ function App() {
|
|
|
3168
4883
|
onLogUpdated: handleLogUpdated
|
|
3169
4884
|
}
|
|
3170
4885
|
),
|
|
3171
|
-
/* @__PURE__ */
|
|
4886
|
+
/* @__PURE__ */ jsx22(
|
|
3172
4887
|
JiraView,
|
|
3173
4888
|
{
|
|
3174
4889
|
isActive: focusedView === "jira",
|
|
@@ -3178,21 +4893,33 @@ function App() {
|
|
|
3178
4893
|
}
|
|
3179
4894
|
)
|
|
3180
4895
|
] }),
|
|
3181
|
-
/* @__PURE__ */
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
4896
|
+
/* @__PURE__ */ jsxs21(Box22, { flexDirection: "column", flexGrow: 1, flexBasis: 0, children: [
|
|
4897
|
+
activeTab === "logs" && /* @__PURE__ */ jsx22(
|
|
4898
|
+
LogsView,
|
|
4899
|
+
{
|
|
4900
|
+
isActive: focusedView === "logs",
|
|
4901
|
+
refreshKey: logRefreshKey,
|
|
4902
|
+
focusedBox: logsFocusedBox,
|
|
4903
|
+
onFocusedBoxChange: setLogsFocusedBox
|
|
4904
|
+
}
|
|
4905
|
+
),
|
|
4906
|
+
activeTab === "jira-browser" && /* @__PURE__ */ jsx22(
|
|
4907
|
+
JiraBrowserView,
|
|
4908
|
+
{
|
|
4909
|
+
isActive: focusedView === "jira-browser",
|
|
4910
|
+
focusedBox: jiraBrowserFocusedBox,
|
|
4911
|
+
onFocusedBoxChange: setJiraBrowserFocusedBox,
|
|
4912
|
+
onModalChange: setJiraBrowserModalOpen,
|
|
4913
|
+
onLogUpdated: handleLogUpdated
|
|
4914
|
+
}
|
|
4915
|
+
)
|
|
4916
|
+
] })
|
|
3190
4917
|
] }),
|
|
3191
|
-
/* @__PURE__ */
|
|
4918
|
+
/* @__PURE__ */ jsx22(
|
|
3192
4919
|
KeybindingsBar,
|
|
3193
4920
|
{
|
|
3194
4921
|
contextBindings: keybindings,
|
|
3195
|
-
modalOpen,
|
|
4922
|
+
modalOpen: anyModalOpen,
|
|
3196
4923
|
duck: { visible: duck.visible, message: duck.message }
|
|
3197
4924
|
}
|
|
3198
4925
|
)
|
|
@@ -3203,31 +4930,31 @@ function App() {
|
|
|
3203
4930
|
import { render as inkRender } from "ink";
|
|
3204
4931
|
|
|
3205
4932
|
// src/lib/Screen.tsx
|
|
3206
|
-
import { useCallback as
|
|
3207
|
-
import { Box as
|
|
3208
|
-
import { jsx as
|
|
4933
|
+
import { useCallback as useCallback12, useEffect as useEffect17, useState as useState22 } from "react";
|
|
4934
|
+
import { Box as Box23, useStdout as useStdout2 } from "ink";
|
|
4935
|
+
import { jsx as jsx23 } from "react/jsx-runtime";
|
|
3209
4936
|
function Screen({ children }) {
|
|
3210
4937
|
const { stdout } = useStdout2();
|
|
3211
|
-
const getSize =
|
|
3212
|
-
const [size, setSize] =
|
|
3213
|
-
|
|
4938
|
+
const getSize = useCallback12(() => ({ height: stdout.rows, width: stdout.columns }), [stdout]);
|
|
4939
|
+
const [size, setSize] = useState22(getSize);
|
|
4940
|
+
useEffect17(() => {
|
|
3214
4941
|
const onResize = () => setSize(getSize());
|
|
3215
4942
|
stdout.on("resize", onResize);
|
|
3216
4943
|
return () => {
|
|
3217
4944
|
stdout.off("resize", onResize);
|
|
3218
4945
|
};
|
|
3219
4946
|
}, [stdout, getSize]);
|
|
3220
|
-
return /* @__PURE__ */
|
|
4947
|
+
return /* @__PURE__ */ jsx23(Box23, { height: size.height, width: size.width, children });
|
|
3221
4948
|
}
|
|
3222
4949
|
|
|
3223
4950
|
// src/lib/render.tsx
|
|
3224
|
-
import { jsx as
|
|
4951
|
+
import { jsx as jsx24 } from "react/jsx-runtime";
|
|
3225
4952
|
var ENTER_ALT_BUFFER = "\x1B[?1049h";
|
|
3226
4953
|
var EXIT_ALT_BUFFER = "\x1B[?1049l";
|
|
3227
4954
|
var CLEAR_SCREEN = "\x1B[2J\x1B[H";
|
|
3228
4955
|
function render(node, options) {
|
|
3229
4956
|
process.stdout.write(ENTER_ALT_BUFFER + CLEAR_SCREEN);
|
|
3230
|
-
const element = /* @__PURE__ */
|
|
4957
|
+
const element = /* @__PURE__ */ jsx24(Screen, { children: node });
|
|
3231
4958
|
const instance = inkRender(element, options);
|
|
3232
4959
|
setImmediate(() => instance.rerender(element));
|
|
3233
4960
|
const cleanup = () => process.stdout.write(EXIT_ALT_BUFFER);
|
|
@@ -3248,7 +4975,7 @@ function render(node, options) {
|
|
|
3248
4975
|
}
|
|
3249
4976
|
|
|
3250
4977
|
// src/cli.tsx
|
|
3251
|
-
import { jsx as
|
|
4978
|
+
import { jsx as jsx25 } from "react/jsx-runtime";
|
|
3252
4979
|
var cli = meow(
|
|
3253
4980
|
`
|
|
3254
4981
|
Usage
|
|
@@ -3281,4 +5008,4 @@ if (cli.flags.cwd) {
|
|
|
3281
5008
|
process.exit(1);
|
|
3282
5009
|
}
|
|
3283
5010
|
}
|
|
3284
|
-
render(/* @__PURE__ */
|
|
5011
|
+
render(/* @__PURE__ */ jsx25(App, {}));
|