clairo 1.2.2 → 2.0.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 +1629 -258
- 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";
|
|
@@ -192,7 +192,7 @@ function timeAgo(dateStr) {
|
|
|
192
192
|
}
|
|
193
193
|
function resolveCheckStatus(check) {
|
|
194
194
|
const conclusion = check.conclusion ?? check.state;
|
|
195
|
-
if (conclusion === "SUCCESS"
|
|
195
|
+
if (conclusion === "SUCCESS") return "success";
|
|
196
196
|
if (conclusion === "FAILURE" || conclusion === "ERROR") return "failure";
|
|
197
197
|
if (conclusion === "SKIPPED" || conclusion === "NEUTRAL") return "skipped";
|
|
198
198
|
if (conclusion === "PENDING" || check.status === "IN_PROGRESS" || check.status === "QUEUED" || check.status === "WAITING")
|
|
@@ -209,18 +209,19 @@ var CHECK_ICONS = { success: "\u2713", failure: "\u2717", pending: "\u25CF", ski
|
|
|
209
209
|
var CHECK_SORT_ORDER = { failure: 0, pending: 1, skipped: 2, success: 3 };
|
|
210
210
|
function resolveReviewDisplay(reviewDecision) {
|
|
211
211
|
const status = reviewDecision ?? "PENDING";
|
|
212
|
-
if (status === "APPROVED") return { text:
|
|
213
|
-
if (status === "CHANGES_REQUESTED") return { text:
|
|
214
|
-
return { text:
|
|
212
|
+
if (status === "APPROVED") return { text: "Approved", color: "green" };
|
|
213
|
+
if (status === "CHANGES_REQUESTED") return { text: "Changes Requested", color: "red" };
|
|
214
|
+
if (status === "REVIEW_REQUIRED") return { text: "Review Required", color: "yellow" };
|
|
215
|
+
return { text: "Pending", color: "yellow" };
|
|
215
216
|
}
|
|
216
217
|
function resolveMergeDisplay(pr) {
|
|
217
|
-
if (!pr) return { text: "
|
|
218
|
-
if (pr.state === "MERGED") return { text: "
|
|
219
|
-
if (pr.state === "CLOSED") return { text: "
|
|
220
|
-
if (pr.isDraft) return { text: "
|
|
221
|
-
if (pr.mergeable === "MERGEABLE") return { text: "
|
|
222
|
-
if (pr.mergeable === "CONFLICTING") return { text: "
|
|
223
|
-
return { text:
|
|
218
|
+
if (!pr) return { text: "Unknown", color: "yellow" };
|
|
219
|
+
if (pr.state === "MERGED") return { text: "Merged", color: "magenta" };
|
|
220
|
+
if (pr.state === "CLOSED") return { text: "Closed", color: "red" };
|
|
221
|
+
if (pr.isDraft) return { text: "Draft", color: "yellow" };
|
|
222
|
+
if (pr.mergeable === "MERGEABLE") return { text: "Open", color: "green" };
|
|
223
|
+
if (pr.mergeable === "CONFLICTING") return { text: "Conflicts", color: "red" };
|
|
224
|
+
return { text: "Unknown", color: "yellow" };
|
|
224
225
|
}
|
|
225
226
|
async function isGhInstalled() {
|
|
226
227
|
try {
|
|
@@ -865,7 +866,8 @@ function createAuthHeader(email, apiToken) {
|
|
|
865
866
|
return `Basic ${credentials}`;
|
|
866
867
|
}
|
|
867
868
|
async function jiraFetch(auth, endpoint, options) {
|
|
868
|
-
const
|
|
869
|
+
const prefix = (options == null ? void 0 : options.apiPrefix) ?? "/rest/api/3";
|
|
870
|
+
const url = `${auth.siteUrl}${prefix}${endpoint}`;
|
|
869
871
|
const method = (options == null ? void 0 : options.method) ?? "GET";
|
|
870
872
|
try {
|
|
871
873
|
const headers = {
|
|
@@ -910,6 +912,21 @@ async function validateCredentials(auth) {
|
|
|
910
912
|
}
|
|
911
913
|
return { success: true, data: result.data };
|
|
912
914
|
}
|
|
915
|
+
async function getCurrentUser(auth) {
|
|
916
|
+
const result = await jiraFetch(auth, "/myself");
|
|
917
|
+
if (!result.ok) {
|
|
918
|
+
if (result.status === 401 || result.status === 403) {
|
|
919
|
+
return { success: false, error: "Authentication failed", errorType: "auth_error" };
|
|
920
|
+
}
|
|
921
|
+
return {
|
|
922
|
+
success: false,
|
|
923
|
+
error: result.error ?? "Failed to fetch current user",
|
|
924
|
+
errorType: "api_error"
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
const data = result.data;
|
|
928
|
+
return { success: true, data: { accountId: data.accountId, displayName: data.displayName } };
|
|
929
|
+
}
|
|
913
930
|
async function getIssue(auth, ticketKey) {
|
|
914
931
|
const result = await jiraFetch(auth, `/issue/${ticketKey}?fields=summary,status`);
|
|
915
932
|
if (!result.ok) {
|
|
@@ -989,6 +1006,377 @@ async function applyTransition(auth, ticketKey, transitionId) {
|
|
|
989
1006
|
}
|
|
990
1007
|
return { success: true, data: null };
|
|
991
1008
|
}
|
|
1009
|
+
async function getIssueDetail(auth, issueKey) {
|
|
1010
|
+
const result = await jiraFetch(auth, `/issue/${issueKey}?fields=summary,status,description,assignee,comment`);
|
|
1011
|
+
if (!result.ok) {
|
|
1012
|
+
if (result.status === 401 || result.status === 403) {
|
|
1013
|
+
return { success: false, error: "Authentication failed", errorType: "auth_error" };
|
|
1014
|
+
}
|
|
1015
|
+
if (result.status === 404) {
|
|
1016
|
+
return { success: false, error: `Issue ${issueKey} not found`, errorType: "invalid_ticket" };
|
|
1017
|
+
}
|
|
1018
|
+
return {
|
|
1019
|
+
success: false,
|
|
1020
|
+
error: result.error ?? "Failed to fetch issue details",
|
|
1021
|
+
errorType: "api_error"
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
return { success: true, data: result.data };
|
|
1025
|
+
}
|
|
1026
|
+
async function assignIssue(auth, issueKey, accountId) {
|
|
1027
|
+
const result = await jiraFetch(auth, `/issue/${issueKey}/assignee`, {
|
|
1028
|
+
method: "PUT",
|
|
1029
|
+
body: { accountId }
|
|
1030
|
+
});
|
|
1031
|
+
if (!result.ok) {
|
|
1032
|
+
if (result.status === 401 || result.status === 403) {
|
|
1033
|
+
return { success: false, error: "Authentication failed", errorType: "auth_error" };
|
|
1034
|
+
}
|
|
1035
|
+
if (result.status === 404) {
|
|
1036
|
+
return { success: false, error: `Issue ${issueKey} not found`, errorType: "invalid_ticket" };
|
|
1037
|
+
}
|
|
1038
|
+
return {
|
|
1039
|
+
success: false,
|
|
1040
|
+
error: result.error ?? "Failed to assign issue",
|
|
1041
|
+
errorType: "api_error"
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
return { success: true, data: null };
|
|
1045
|
+
}
|
|
1046
|
+
async function unassignIssue(auth, issueKey) {
|
|
1047
|
+
const result = await jiraFetch(auth, `/issue/${issueKey}/assignee`, {
|
|
1048
|
+
method: "PUT",
|
|
1049
|
+
body: { accountId: null }
|
|
1050
|
+
});
|
|
1051
|
+
if (!result.ok) {
|
|
1052
|
+
if (result.status === 401 || result.status === 403) {
|
|
1053
|
+
return { success: false, error: "Authentication failed", errorType: "auth_error" };
|
|
1054
|
+
}
|
|
1055
|
+
if (result.status === 404) {
|
|
1056
|
+
return { success: false, error: `Issue ${issueKey} not found`, errorType: "invalid_ticket" };
|
|
1057
|
+
}
|
|
1058
|
+
return {
|
|
1059
|
+
success: false,
|
|
1060
|
+
error: result.error ?? "Failed to unassign issue",
|
|
1061
|
+
errorType: "api_error"
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
return { success: true, data: null };
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
// src/lib/jira/search.ts
|
|
1068
|
+
async function searchIssues(auth, jql, opts) {
|
|
1069
|
+
const params = new URLSearchParams({
|
|
1070
|
+
jql,
|
|
1071
|
+
fields: "summary,status,assignee,priority,issuetype,sprint,closedSprints",
|
|
1072
|
+
startAt: String((opts == null ? void 0 : opts.startAt) ?? 0),
|
|
1073
|
+
maxResults: String((opts == null ? void 0 : opts.maxResults) ?? 50)
|
|
1074
|
+
});
|
|
1075
|
+
const result = await jiraFetch(auth, `/search?${params.toString()}`);
|
|
1076
|
+
if (!result.ok) {
|
|
1077
|
+
if (result.status === 401 || result.status === 403) {
|
|
1078
|
+
return { success: false, error: "Authentication failed", errorType: "auth_error" };
|
|
1079
|
+
}
|
|
1080
|
+
return {
|
|
1081
|
+
success: false,
|
|
1082
|
+
error: result.error ?? "Failed to search issues",
|
|
1083
|
+
errorType: "api_error"
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
return { success: true, data: result.data };
|
|
1087
|
+
}
|
|
1088
|
+
async function getFilterJql(auth, filterId) {
|
|
1089
|
+
const result = await jiraFetch(auth, `/filter/${filterId}`);
|
|
1090
|
+
if (!result.ok) {
|
|
1091
|
+
if (result.status === 401 || result.status === 403) {
|
|
1092
|
+
return { success: false, error: "Authentication failed", errorType: "auth_error" };
|
|
1093
|
+
}
|
|
1094
|
+
if (result.status === 404) {
|
|
1095
|
+
return { success: false, error: `Filter ${filterId} not found`, errorType: "api_error" };
|
|
1096
|
+
}
|
|
1097
|
+
return {
|
|
1098
|
+
success: false,
|
|
1099
|
+
error: result.error ?? "Failed to fetch filter",
|
|
1100
|
+
errorType: "api_error"
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
const data = result.data;
|
|
1104
|
+
return { success: true, data: data.jql };
|
|
1105
|
+
}
|
|
1106
|
+
async function getBoardIssues(auth, boardId, opts) {
|
|
1107
|
+
const params = new URLSearchParams({
|
|
1108
|
+
fields: "summary,status,assignee,priority,issuetype,sprint,closedSprints",
|
|
1109
|
+
startAt: String((opts == null ? void 0 : opts.startAt) ?? 0),
|
|
1110
|
+
maxResults: String((opts == null ? void 0 : opts.maxResults) ?? 50)
|
|
1111
|
+
});
|
|
1112
|
+
if (opts == null ? void 0 : opts.jql) {
|
|
1113
|
+
params.set("jql", opts.jql);
|
|
1114
|
+
}
|
|
1115
|
+
const result = await jiraFetch(auth, `/board/${boardId}/issue?${params.toString()}`, {
|
|
1116
|
+
apiPrefix: "/rest/agile/1.0"
|
|
1117
|
+
});
|
|
1118
|
+
if (!result.ok) {
|
|
1119
|
+
if (result.status === 401 || result.status === 403) {
|
|
1120
|
+
return { success: false, error: "Authentication failed", errorType: "auth_error" };
|
|
1121
|
+
}
|
|
1122
|
+
if (result.status === 404) {
|
|
1123
|
+
return { success: false, error: `Board ${boardId} not found`, errorType: "api_error" };
|
|
1124
|
+
}
|
|
1125
|
+
return {
|
|
1126
|
+
success: false,
|
|
1127
|
+
error: result.error ?? "Failed to fetch board issues",
|
|
1128
|
+
errorType: "api_error"
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
return { success: true, data: result.data };
|
|
1132
|
+
}
|
|
1133
|
+
function escapeJql(text) {
|
|
1134
|
+
return text.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
1135
|
+
}
|
|
1136
|
+
function appendTextSearch(jql, searchText) {
|
|
1137
|
+
const escaped = escapeJql(searchText);
|
|
1138
|
+
return `(${jql}) AND text ~ "${escaped}"`;
|
|
1139
|
+
}
|
|
1140
|
+
async function fetchViewIssues(auth, view, opts) {
|
|
1141
|
+
const { searchText, ...pageOpts } = opts ?? {};
|
|
1142
|
+
switch (view.source.type) {
|
|
1143
|
+
case "jql": {
|
|
1144
|
+
const jql = searchText ? appendTextSearch(view.source.jql, searchText) : view.source.jql;
|
|
1145
|
+
return searchIssues(auth, jql, pageOpts);
|
|
1146
|
+
}
|
|
1147
|
+
case "filter": {
|
|
1148
|
+
const filterResult = await getFilterJql(auth, view.source.filterId);
|
|
1149
|
+
if (!filterResult.success) return filterResult;
|
|
1150
|
+
const jql = searchText ? appendTextSearch(filterResult.data, searchText) : filterResult.data;
|
|
1151
|
+
return searchIssues(auth, jql, pageOpts);
|
|
1152
|
+
}
|
|
1153
|
+
case "board": {
|
|
1154
|
+
const jql = searchText ? `text ~ "${escapeJql(searchText)}"` : void 0;
|
|
1155
|
+
return getBoardIssues(auth, view.source.boardId, { ...pageOpts, jql });
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
// src/lib/jira/views.ts
|
|
1161
|
+
function getSavedViews(repoPath) {
|
|
1162
|
+
const config = getRepoConfig(repoPath);
|
|
1163
|
+
return config.savedJiraViews ?? [];
|
|
1164
|
+
}
|
|
1165
|
+
function addSavedView(repoPath, name, url, source) {
|
|
1166
|
+
const views = getSavedViews(repoPath);
|
|
1167
|
+
const view = {
|
|
1168
|
+
id: Date.now().toString(36),
|
|
1169
|
+
name,
|
|
1170
|
+
url,
|
|
1171
|
+
source,
|
|
1172
|
+
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1173
|
+
};
|
|
1174
|
+
updateRepoConfig(repoPath, { savedJiraViews: [...views, view] });
|
|
1175
|
+
return view;
|
|
1176
|
+
}
|
|
1177
|
+
function renameSavedView(repoPath, viewId, newName) {
|
|
1178
|
+
const views = getSavedViews(repoPath);
|
|
1179
|
+
updateRepoConfig(repoPath, {
|
|
1180
|
+
savedJiraViews: views.map((v) => v.id === viewId ? { ...v, name: newName } : v)
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
function removeSavedView(repoPath, viewId) {
|
|
1184
|
+
const views = getSavedViews(repoPath);
|
|
1185
|
+
updateRepoConfig(repoPath, {
|
|
1186
|
+
savedJiraViews: views.filter((v) => v.id !== viewId)
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// src/lib/jira/url-parser.ts
|
|
1191
|
+
function parseJiraUrl(input) {
|
|
1192
|
+
const trimmed = input.trim();
|
|
1193
|
+
let url;
|
|
1194
|
+
try {
|
|
1195
|
+
url = new URL(trimmed);
|
|
1196
|
+
} catch {
|
|
1197
|
+
return null;
|
|
1198
|
+
}
|
|
1199
|
+
const filterId = url.searchParams.get("filter");
|
|
1200
|
+
if (filterId && /^\d+$/.test(filterId)) {
|
|
1201
|
+
return { type: "filter", filterId };
|
|
1202
|
+
}
|
|
1203
|
+
const jql = url.searchParams.get("jql");
|
|
1204
|
+
if (jql) {
|
|
1205
|
+
return { type: "jql", jql };
|
|
1206
|
+
}
|
|
1207
|
+
const boardMatch = url.pathname.match(/\/boards\/(\d+)/);
|
|
1208
|
+
if (boardMatch) {
|
|
1209
|
+
return { type: "board", boardId: boardMatch[1] };
|
|
1210
|
+
}
|
|
1211
|
+
return null;
|
|
1212
|
+
}
|
|
1213
|
+
function generateViewName(input) {
|
|
1214
|
+
const source = parseJiraUrl(input);
|
|
1215
|
+
if (!source) return "Jira View";
|
|
1216
|
+
switch (source.type) {
|
|
1217
|
+
case "filter":
|
|
1218
|
+
return `Filter #${source.filterId}`;
|
|
1219
|
+
case "jql":
|
|
1220
|
+
return truncate(source.jql, 40);
|
|
1221
|
+
case "board":
|
|
1222
|
+
return `Board #${source.boardId}`;
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
function truncate(str, maxLen) {
|
|
1226
|
+
if (str.length <= maxLen) return str;
|
|
1227
|
+
return str.slice(0, maxLen - 1) + "\u2026";
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
// src/lib/jira/adf-to-markdown.ts
|
|
1231
|
+
function adfToMarkdown(node) {
|
|
1232
|
+
if (!node || typeof node !== "object") return "";
|
|
1233
|
+
return processNode(node).trimEnd();
|
|
1234
|
+
}
|
|
1235
|
+
function processNode(node, indent = "") {
|
|
1236
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
1237
|
+
switch (node.type) {
|
|
1238
|
+
case "doc":
|
|
1239
|
+
return processChildren(node, indent);
|
|
1240
|
+
case "paragraph":
|
|
1241
|
+
return processChildren(node, indent) + "\n\n";
|
|
1242
|
+
case "heading": {
|
|
1243
|
+
const level = ((_a = node.attrs) == null ? void 0 : _a.level) ?? 1;
|
|
1244
|
+
const prefix = "#".repeat(level);
|
|
1245
|
+
return `${prefix} ${processChildren(node, indent)}
|
|
1246
|
+
|
|
1247
|
+
`;
|
|
1248
|
+
}
|
|
1249
|
+
case "text":
|
|
1250
|
+
return applyMarks(node.text ?? "", node.marks);
|
|
1251
|
+
case "hardBreak":
|
|
1252
|
+
return "\n";
|
|
1253
|
+
case "bulletList":
|
|
1254
|
+
return processList(node, indent, "bullet") + "\n";
|
|
1255
|
+
case "orderedList":
|
|
1256
|
+
return processList(node, indent, "ordered") + "\n";
|
|
1257
|
+
case "listItem":
|
|
1258
|
+
return processChildren(node, indent);
|
|
1259
|
+
case "codeBlock": {
|
|
1260
|
+
const lang = ((_b = node.attrs) == null ? void 0 : _b.language) ?? "";
|
|
1261
|
+
const code = processChildren(node, indent);
|
|
1262
|
+
return `\`\`\`${lang}
|
|
1263
|
+
${code}
|
|
1264
|
+
\`\`\`
|
|
1265
|
+
|
|
1266
|
+
`;
|
|
1267
|
+
}
|
|
1268
|
+
case "blockquote": {
|
|
1269
|
+
const content = processChildren(node, indent);
|
|
1270
|
+
return content.split("\n").map((line) => `> ${line}`).join("\n") + "\n\n";
|
|
1271
|
+
}
|
|
1272
|
+
case "rule":
|
|
1273
|
+
return "---\n\n";
|
|
1274
|
+
case "mention":
|
|
1275
|
+
return `@${((_c = node.attrs) == null ? void 0 : _c.text) ?? "unknown"}`;
|
|
1276
|
+
case "emoji":
|
|
1277
|
+
return ((_d = node.attrs) == null ? void 0 : _d.shortName) ?? ((_e = node.attrs) == null ? void 0 : _e.text) ?? "";
|
|
1278
|
+
case "inlineCard": {
|
|
1279
|
+
const url = (_f = node.attrs) == null ? void 0 : _f.url;
|
|
1280
|
+
return url ? `[${url}](${url})` : "";
|
|
1281
|
+
}
|
|
1282
|
+
case "mediaSingle":
|
|
1283
|
+
case "mediaGroup":
|
|
1284
|
+
return processChildren(node, indent);
|
|
1285
|
+
case "media":
|
|
1286
|
+
return "[Attachment]\n\n";
|
|
1287
|
+
case "table":
|
|
1288
|
+
return processTable(node) + "\n";
|
|
1289
|
+
case "panel": {
|
|
1290
|
+
const panelType = ((_g = node.attrs) == null ? void 0 : _g.panelType) ?? "info";
|
|
1291
|
+
const label = panelType.charAt(0).toUpperCase() + panelType.slice(1);
|
|
1292
|
+
return `[${label}] ${processChildren(node, indent)}
|
|
1293
|
+
`;
|
|
1294
|
+
}
|
|
1295
|
+
default:
|
|
1296
|
+
if (node.content) {
|
|
1297
|
+
return processChildren(node, indent);
|
|
1298
|
+
}
|
|
1299
|
+
return node.text ?? "";
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
function processChildren(node, indent = "") {
|
|
1303
|
+
if (!node.content) return "";
|
|
1304
|
+
return node.content.map((child) => processNode(child, indent)).join("");
|
|
1305
|
+
}
|
|
1306
|
+
function processList(node, indent, style) {
|
|
1307
|
+
if (!node.content) return "";
|
|
1308
|
+
return node.content.map((item, idx) => {
|
|
1309
|
+
const prefix = style === "bullet" ? "- " : `${idx + 1}. `;
|
|
1310
|
+
const childContent = (item.content ?? []).map((child) => {
|
|
1311
|
+
if (child.type === "bulletList" || child.type === "orderedList") {
|
|
1312
|
+
return processList(child, indent + " ", child.type === "bulletList" ? "bullet" : "ordered");
|
|
1313
|
+
}
|
|
1314
|
+
if (child.type === "paragraph") {
|
|
1315
|
+
return processChildren(child, indent);
|
|
1316
|
+
}
|
|
1317
|
+
return processNode(child, indent + " ");
|
|
1318
|
+
}).join("");
|
|
1319
|
+
return `${indent}${prefix}${childContent}`;
|
|
1320
|
+
}).join("\n");
|
|
1321
|
+
}
|
|
1322
|
+
function processTable(node) {
|
|
1323
|
+
if (!node.content) return "";
|
|
1324
|
+
const rows = [];
|
|
1325
|
+
let hasHeader = false;
|
|
1326
|
+
for (const row of node.content) {
|
|
1327
|
+
if (row.type !== "tableRow") continue;
|
|
1328
|
+
const cells = [];
|
|
1329
|
+
for (const cell of row.content ?? []) {
|
|
1330
|
+
if (cell.type === "tableHeader") hasHeader = true;
|
|
1331
|
+
const text = processChildren(cell).replace(/\n/g, " ").trim();
|
|
1332
|
+
cells.push(text);
|
|
1333
|
+
}
|
|
1334
|
+
rows.push(cells);
|
|
1335
|
+
}
|
|
1336
|
+
if (rows.length === 0) return "";
|
|
1337
|
+
const colCount = Math.max(...rows.map((r) => r.length));
|
|
1338
|
+
const lines = [];
|
|
1339
|
+
for (let i = 0; i < rows.length; i++) {
|
|
1340
|
+
const row = rows[i];
|
|
1341
|
+
while (row.length < colCount) row.push("");
|
|
1342
|
+
lines.push(`| ${row.join(" | ")} |`);
|
|
1343
|
+
if (i === 0 && hasHeader) {
|
|
1344
|
+
lines.push(`| ${Array(colCount).fill("---").join(" | ")} |`);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
return lines.join("\n") + "\n";
|
|
1348
|
+
}
|
|
1349
|
+
function applyMarks(text, marks) {
|
|
1350
|
+
var _a;
|
|
1351
|
+
if (!marks || marks.length === 0) return text;
|
|
1352
|
+
let result = text;
|
|
1353
|
+
for (const mark of marks) {
|
|
1354
|
+
switch (mark.type) {
|
|
1355
|
+
case "strong":
|
|
1356
|
+
result = `**${result}**`;
|
|
1357
|
+
break;
|
|
1358
|
+
case "em":
|
|
1359
|
+
result = `*${result}*`;
|
|
1360
|
+
break;
|
|
1361
|
+
case "code":
|
|
1362
|
+
result = `\`${result}\``;
|
|
1363
|
+
break;
|
|
1364
|
+
case "strike":
|
|
1365
|
+
result = `~~${result}~~`;
|
|
1366
|
+
break;
|
|
1367
|
+
case "link": {
|
|
1368
|
+
const href = (_a = mark.attrs) == null ? void 0 : _a.href;
|
|
1369
|
+
if (href) {
|
|
1370
|
+
result = `[${result}](${href})`;
|
|
1371
|
+
}
|
|
1372
|
+
break;
|
|
1373
|
+
}
|
|
1374
|
+
case "underline":
|
|
1375
|
+
break;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
return result;
|
|
1379
|
+
}
|
|
992
1380
|
|
|
993
1381
|
// src/lib/logs/index.ts
|
|
994
1382
|
import { spawnSync } from "child_process";
|
|
@@ -1115,6 +1503,18 @@ function logJiraStatusChanged(ticketKey, ticketName, oldStatus, newStatus) {
|
|
|
1115
1503
|
${ticketKey}: ${ticketName}
|
|
1116
1504
|
${oldStatus} \u2192 ${newStatus}
|
|
1117
1505
|
|
|
1506
|
+
`;
|
|
1507
|
+
appendToLog(today, entry);
|
|
1508
|
+
}
|
|
1509
|
+
function logJiraAssigneeChanged(ticketKey, ticketName, action, displayName) {
|
|
1510
|
+
const timestamp = formatTimestamp();
|
|
1511
|
+
const today = getTodayDate();
|
|
1512
|
+
const detail = action === "assigned" && displayName ? `Assigned to ${displayName}` : "Unassigned";
|
|
1513
|
+
const entry = `## ${timestamp} - Updated Jira ticket
|
|
1514
|
+
|
|
1515
|
+
${ticketKey}: ${ticketName}
|
|
1516
|
+
${detail}
|
|
1517
|
+
|
|
1118
1518
|
`;
|
|
1119
1519
|
appendToLog(today, entry);
|
|
1120
1520
|
}
|
|
@@ -1338,7 +1738,11 @@ function PRDetailsBox({ pr, loading, error, isActive }) {
|
|
|
1338
1738
|
pr.deletions
|
|
1339
1739
|
] })
|
|
1340
1740
|
] }),
|
|
1341
|
-
(((_c = pr.labels) == null ? void 0 : _c.length) ?? 0) > 0 && /* @__PURE__ */ jsx3(
|
|
1741
|
+
(((_c = pr.labels) == null ? void 0 : _c.length) ?? 0) > 0 && /* @__PURE__ */ jsx3(Box3, { gap: 1, children: pr.labels.map((l) => /* @__PURE__ */ jsxs2(Box3, { children: [
|
|
1742
|
+
/* @__PURE__ */ jsx3(Text2, { color: "gray", children: "\uE0B6" }),
|
|
1743
|
+
/* @__PURE__ */ jsx3(Text2, { color: "black", backgroundColor: "gray", children: l.name }),
|
|
1744
|
+
/* @__PURE__ */ jsx3(Text2, { color: "gray", children: "\uE0B4" })
|
|
1745
|
+
] }, l.name)) }),
|
|
1342
1746
|
/* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(Divider, {}) }),
|
|
1343
1747
|
(((_d = pr.assignees) == null ? void 0 : _d.length) ?? 0) > 0 && /* @__PURE__ */ jsxs2(Box3, { marginTop: 1, children: [
|
|
1344
1748
|
/* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "Assignees: " }),
|
|
@@ -1654,13 +2058,13 @@ function useLogs() {
|
|
|
1654
2058
|
import { useCallback as useCallback6, useState as useState7 } from "react";
|
|
1655
2059
|
function useModal() {
|
|
1656
2060
|
const [modalType, setModalType] = useState7("none");
|
|
1657
|
-
const
|
|
2061
|
+
const open6 = useCallback6((type) => setModalType(type), []);
|
|
1658
2062
|
const close = useCallback6(() => setModalType("none"), []);
|
|
1659
2063
|
const isOpen = modalType !== "none";
|
|
1660
2064
|
return {
|
|
1661
2065
|
type: modalType,
|
|
1662
2066
|
isOpen,
|
|
1663
|
-
open:
|
|
2067
|
+
open: open6,
|
|
1664
2068
|
close
|
|
1665
2069
|
};
|
|
1666
2070
|
}
|
|
@@ -1766,7 +2170,9 @@ var REACTION_MESSAGES = {
|
|
|
1766
2170
|
error: ["Uh oh...", "There there...", "*concerned quacking*", "Quack... not good."],
|
|
1767
2171
|
"jira:transition": ["Ticket moving!", "Progress!", "Workflow in motion!"],
|
|
1768
2172
|
"jira:linked": ["Ticket linked!", "Jira connection made!", "Tracking enabled!"],
|
|
1769
|
-
"jira:configured": ["Jira ready!", "Integration complete!", "Connected to Jira!"]
|
|
2173
|
+
"jira:configured": ["Jira ready!", "Integration complete!", "Connected to Jira!"],
|
|
2174
|
+
"jira:assigned": ["Assigned!", "On it!", "Claimed!"],
|
|
2175
|
+
"jira:unassigned": ["Unassigned!", "Free agent!", "Released!"]
|
|
1770
2176
|
};
|
|
1771
2177
|
function useRubberDuck() {
|
|
1772
2178
|
const [state, setState] = useState9({
|
|
@@ -2239,11 +2645,11 @@ ${body}`;
|
|
|
2239
2645
|
] });
|
|
2240
2646
|
}
|
|
2241
2647
|
|
|
2242
|
-
// src/components/jira/
|
|
2243
|
-
import
|
|
2244
|
-
import {
|
|
2648
|
+
// src/components/jira-browser/JiraBrowserView.tsx
|
|
2649
|
+
import { useCallback as useCallback10, useEffect as useEffect12, useMemo as useMemo3, useRef as useRef7, useState as useState16 } from "react";
|
|
2650
|
+
import { Box as Box12, useInput as useInput10 } from "ink";
|
|
2245
2651
|
|
|
2246
|
-
// src/components/jira/
|
|
2652
|
+
// src/components/jira-browser/AddViewModal.tsx
|
|
2247
2653
|
import { useState as useState12 } from "react";
|
|
2248
2654
|
import { Box as Box8, Text as Text7, useInput as useInput6 } from "ink";
|
|
2249
2655
|
|
|
@@ -2262,8 +2668,11 @@ function TextInput({ value, onChange, placeholder, isActive, mask }) {
|
|
|
2262
2668
|
if (key.return || key.escape || key.upArrow || key.downArrow || key.tab) {
|
|
2263
2669
|
return;
|
|
2264
2670
|
}
|
|
2265
|
-
if (input && input.length
|
|
2266
|
-
|
|
2671
|
+
if (input && input.length > 0) {
|
|
2672
|
+
const printable = input.replace(/[^\x20-\x7E\u00A0-\uFFFF]/g, "");
|
|
2673
|
+
if (printable.length > 0) {
|
|
2674
|
+
onChange(value + printable);
|
|
2675
|
+
}
|
|
2267
2676
|
}
|
|
2268
2677
|
},
|
|
2269
2678
|
{ isActive }
|
|
@@ -2276,11 +2685,13 @@ function TextInput({ value, onChange, placeholder, isActive, mask }) {
|
|
|
2276
2685
|
] }) });
|
|
2277
2686
|
}
|
|
2278
2687
|
|
|
2279
|
-
// src/components/jira/
|
|
2688
|
+
// src/components/jira-browser/AddViewModal.tsx
|
|
2280
2689
|
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2281
|
-
function
|
|
2282
|
-
const [
|
|
2283
|
-
const
|
|
2690
|
+
function AddViewModal({ onSubmit, onCancel, loading, error }) {
|
|
2691
|
+
const [url, setUrl] = useState12("");
|
|
2692
|
+
const [name, setName] = useState12("");
|
|
2693
|
+
const [activeField, setActiveField] = useState12("url");
|
|
2694
|
+
const canSubmit = url.trim().length > 0;
|
|
2284
2695
|
useInput6(
|
|
2285
2696
|
(_input, key) => {
|
|
2286
2697
|
if (loading) return;
|
|
@@ -2288,120 +2699,1014 @@ function LinkTicketModal({ onSubmit, onCancel, loading, error }) {
|
|
|
2288
2699
|
onCancel();
|
|
2289
2700
|
return;
|
|
2290
2701
|
}
|
|
2702
|
+
if (key.tab) {
|
|
2703
|
+
setActiveField((f) => f === "url" ? "name" : "url");
|
|
2704
|
+
return;
|
|
2705
|
+
}
|
|
2291
2706
|
if (key.return && canSubmit) {
|
|
2292
|
-
|
|
2707
|
+
const viewName = name.trim() || generateViewName(url.trim());
|
|
2708
|
+
onSubmit(url.trim(), viewName);
|
|
2293
2709
|
}
|
|
2294
2710
|
},
|
|
2295
2711
|
{ isActive: !loading }
|
|
2296
2712
|
);
|
|
2297
2713
|
return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, paddingY: 1, children: [
|
|
2298
|
-
/* @__PURE__ */ jsx8(Text7, { bold: true, color: "yellow", children: "
|
|
2299
|
-
/* @__PURE__ */ jsx8(Text7, { dimColor: true, children: "
|
|
2714
|
+
/* @__PURE__ */ jsx8(Text7, { bold: true, color: "yellow", children: "Add Jira View" }),
|
|
2715
|
+
/* @__PURE__ */ jsx8(Text7, { dimColor: true, children: "Tab to switch fields, Enter to save, Esc to cancel" }),
|
|
2300
2716
|
/* @__PURE__ */ jsx8(Box8, { marginTop: 1 }),
|
|
2301
2717
|
error && /* @__PURE__ */ jsx8(Box8, { marginBottom: 1, children: /* @__PURE__ */ jsx8(Text7, { color: "red", children: error }) }),
|
|
2302
2718
|
/* @__PURE__ */ jsxs7(Box8, { children: [
|
|
2303
|
-
/* @__PURE__ */ jsx8(Text7, { color: "blue", children: "
|
|
2304
|
-
/* @__PURE__ */ jsx8(
|
|
2719
|
+
/* @__PURE__ */ jsx8(Text7, { color: "blue", children: "URL: " }),
|
|
2720
|
+
/* @__PURE__ */ jsx8(
|
|
2721
|
+
TextInput,
|
|
2722
|
+
{
|
|
2723
|
+
value: url,
|
|
2724
|
+
onChange: setUrl,
|
|
2725
|
+
placeholder: "https://company.atlassian.net/issues/?filter=12345",
|
|
2726
|
+
isActive: !loading && activeField === "url"
|
|
2727
|
+
}
|
|
2728
|
+
)
|
|
2729
|
+
] }),
|
|
2730
|
+
/* @__PURE__ */ jsxs7(Box8, { marginTop: 1, children: [
|
|
2731
|
+
/* @__PURE__ */ jsx8(Text7, { color: "blue", children: "Name: " }),
|
|
2732
|
+
/* @__PURE__ */ jsx8(
|
|
2733
|
+
TextInput,
|
|
2734
|
+
{
|
|
2735
|
+
value: name,
|
|
2736
|
+
onChange: setName,
|
|
2737
|
+
placeholder: "(auto-generated from URL)",
|
|
2738
|
+
isActive: !loading && activeField === "name"
|
|
2739
|
+
}
|
|
2740
|
+
)
|
|
2305
2741
|
] }),
|
|
2306
|
-
loading && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text7, { color: "yellow", children: "
|
|
2307
|
-
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text7, { dimColor: true, children: "
|
|
2742
|
+
loading && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text7, { color: "yellow", children: "Validating view..." }) }),
|
|
2743
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text7, { dimColor: true, children: "Supports: filter URLs, JQL URLs, board URLs" }) })
|
|
2308
2744
|
] });
|
|
2309
2745
|
}
|
|
2310
2746
|
|
|
2311
|
-
// src/components/jira/
|
|
2747
|
+
// src/components/jira-browser/JiraSavedViewBrowserBox.tsx
|
|
2748
|
+
import open4 from "open";
|
|
2749
|
+
import { useCallback as useCallback9, useEffect as useEffect10, useMemo as useMemo2, useState as useState14 } from "react";
|
|
2312
2750
|
import { TitledBox as TitledBox4 } from "@mishieck/ink-titled-box";
|
|
2313
|
-
import { Box as
|
|
2751
|
+
import { Box as Box10, Text as Text9, useInput as useInput8 } from "ink";
|
|
2752
|
+
import { ScrollView as ScrollView6 } from "ink-scroll-view";
|
|
2753
|
+
import Spinner3 from "ink-spinner";
|
|
2314
2754
|
|
|
2315
|
-
// src/components/jira/
|
|
2316
|
-
import
|
|
2755
|
+
// src/components/jira-browser/JiraIssueDetailView.tsx
|
|
2756
|
+
import open3 from "open";
|
|
2757
|
+
import { useEffect as useEffect9, useRef as useRef6, useState as useState13 } from "react";
|
|
2317
2758
|
import { Box as Box9, Text as Text8, useInput as useInput7 } from "ink";
|
|
2759
|
+
import { ScrollView as ScrollView5 } from "ink-scroll-view";
|
|
2318
2760
|
import SelectInput from "ink-select-input";
|
|
2761
|
+
import Spinner2 from "ink-spinner";
|
|
2319
2762
|
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2320
|
-
function
|
|
2321
|
-
|
|
2763
|
+
function JiraIssueDetailView({
|
|
2764
|
+
issueKey,
|
|
2765
|
+
issueSummary,
|
|
2766
|
+
auth,
|
|
2767
|
+
myAccountId,
|
|
2768
|
+
myDisplayName,
|
|
2769
|
+
isActive,
|
|
2770
|
+
onClose,
|
|
2771
|
+
onIssueUpdated,
|
|
2772
|
+
onLogUpdated
|
|
2773
|
+
}) {
|
|
2774
|
+
var _a;
|
|
2775
|
+
const scrollRef = useRef6(null);
|
|
2776
|
+
const [detail, setDetail] = useState13(null);
|
|
2322
2777
|
const [loading, setLoading] = useState13(true);
|
|
2323
|
-
const [applying, setApplying] = useState13(false);
|
|
2324
2778
|
const [error, setError] = useState13(null);
|
|
2779
|
+
const [mode, setMode] = useState13("normal");
|
|
2780
|
+
const [transitions, setTransitions] = useState13([]);
|
|
2781
|
+
const [transitionsLoading, setTransitionsLoading] = useState13(false);
|
|
2782
|
+
const [actionLoading, setActionLoading] = useState13(null);
|
|
2783
|
+
const [actionError, setActionError] = useState13(null);
|
|
2325
2784
|
useEffect9(() => {
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
if (!siteUrl || !creds.email || !creds.apiToken) {
|
|
2330
|
-
setError("Jira not configured");
|
|
2331
|
-
duckEvents.emit("error");
|
|
2332
|
-
setLoading(false);
|
|
2333
|
-
return;
|
|
2334
|
-
}
|
|
2335
|
-
const auth = { siteUrl, email: creds.email, apiToken: creds.apiToken };
|
|
2336
|
-
const result = await getTransitions(auth, ticketKey);
|
|
2785
|
+
setLoading(true);
|
|
2786
|
+
setError(null);
|
|
2787
|
+
getIssueDetail(auth, issueKey).then((result) => {
|
|
2337
2788
|
if (result.success) {
|
|
2338
|
-
|
|
2789
|
+
setDetail(result.data);
|
|
2339
2790
|
} else {
|
|
2340
2791
|
setError(result.error);
|
|
2341
|
-
duckEvents.emit("error");
|
|
2342
2792
|
}
|
|
2343
2793
|
setLoading(false);
|
|
2344
|
-
};
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
const
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
const
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
return;
|
|
2794
|
+
});
|
|
2795
|
+
}, [issueKey, auth.siteUrl]);
|
|
2796
|
+
const getIssueUrl = () => `${auth.siteUrl}/browse/${issueKey}`;
|
|
2797
|
+
const openTransitionPicker = async () => {
|
|
2798
|
+
setTransitionsLoading(true);
|
|
2799
|
+
setActionError(null);
|
|
2800
|
+
const result = await getTransitions(auth, issueKey);
|
|
2801
|
+
if (result.success) {
|
|
2802
|
+
setTransitions(result.data);
|
|
2803
|
+
setMode("transitions");
|
|
2804
|
+
} else {
|
|
2805
|
+
setActionError(result.error);
|
|
2357
2806
|
}
|
|
2358
|
-
|
|
2359
|
-
|
|
2807
|
+
setTransitionsLoading(false);
|
|
2808
|
+
};
|
|
2809
|
+
const handleTransitionSelect = async (item) => {
|
|
2810
|
+
setMode("normal");
|
|
2811
|
+
setActionLoading("Updating status...");
|
|
2812
|
+
setActionError(null);
|
|
2813
|
+
const result = await applyTransition(auth, issueKey, item.value);
|
|
2360
2814
|
if (result.success) {
|
|
2361
2815
|
const transition = transitions.find((t) => t.id === item.value);
|
|
2362
2816
|
const newStatus = (transition == null ? void 0 : transition.to.name) ?? item.label;
|
|
2817
|
+
const oldStatus = (detail == null ? void 0 : detail.fields.status.name) ?? "Unknown";
|
|
2818
|
+
setDetail((prev) => prev ? { ...prev, fields: { ...prev.fields, status: { name: newStatus } } } : prev);
|
|
2819
|
+
onIssueUpdated(issueKey, { status: newStatus });
|
|
2363
2820
|
duckEvents.emit("jira:transition");
|
|
2364
|
-
|
|
2821
|
+
logJiraStatusChanged(issueKey, issueSummary, oldStatus, newStatus);
|
|
2822
|
+
onLogUpdated == null ? void 0 : onLogUpdated();
|
|
2365
2823
|
} else {
|
|
2366
|
-
|
|
2824
|
+
setActionError(result.error);
|
|
2825
|
+
duckEvents.emit("error");
|
|
2826
|
+
}
|
|
2827
|
+
setActionLoading(null);
|
|
2828
|
+
};
|
|
2829
|
+
const handleAssignToMe = async () => {
|
|
2830
|
+
if (!myAccountId || !myDisplayName) return;
|
|
2831
|
+
setActionLoading("Assigning...");
|
|
2832
|
+
setActionError(null);
|
|
2833
|
+
const result = await assignIssue(auth, issueKey, myAccountId);
|
|
2834
|
+
if (result.success) {
|
|
2835
|
+
const assignee = { accountId: myAccountId, displayName: myDisplayName };
|
|
2836
|
+
setDetail((prev) => prev ? { ...prev, fields: { ...prev.fields, assignee } } : prev);
|
|
2837
|
+
onIssueUpdated(issueKey, { assignee });
|
|
2838
|
+
duckEvents.emit("jira:assigned");
|
|
2839
|
+
logJiraAssigneeChanged(issueKey, issueSummary, "assigned", myDisplayName);
|
|
2840
|
+
onLogUpdated == null ? void 0 : onLogUpdated();
|
|
2841
|
+
} else {
|
|
2842
|
+
setActionError(result.error);
|
|
2367
2843
|
duckEvents.emit("error");
|
|
2368
|
-
setApplying(false);
|
|
2369
2844
|
}
|
|
2845
|
+
setActionLoading(null);
|
|
2846
|
+
};
|
|
2847
|
+
const handleUnassign = async () => {
|
|
2848
|
+
setActionLoading("Unassigning...");
|
|
2849
|
+
setActionError(null);
|
|
2850
|
+
const result = await unassignIssue(auth, issueKey);
|
|
2851
|
+
if (result.success) {
|
|
2852
|
+
setDetail((prev) => prev ? { ...prev, fields: { ...prev.fields, assignee: null } } : prev);
|
|
2853
|
+
onIssueUpdated(issueKey, { assignee: null });
|
|
2854
|
+
duckEvents.emit("jira:unassigned");
|
|
2855
|
+
logJiraAssigneeChanged(issueKey, issueSummary, "unassigned");
|
|
2856
|
+
onLogUpdated == null ? void 0 : onLogUpdated();
|
|
2857
|
+
} else {
|
|
2858
|
+
setActionError(result.error);
|
|
2859
|
+
duckEvents.emit("error");
|
|
2860
|
+
}
|
|
2861
|
+
setActionLoading(null);
|
|
2370
2862
|
};
|
|
2371
2863
|
useInput7(
|
|
2372
|
-
(
|
|
2373
|
-
|
|
2374
|
-
|
|
2864
|
+
(input, key) => {
|
|
2865
|
+
var _a2, _b;
|
|
2866
|
+
if (mode === "transitions") {
|
|
2867
|
+
if (key.escape) {
|
|
2868
|
+
setMode("normal");
|
|
2869
|
+
}
|
|
2870
|
+
return;
|
|
2375
2871
|
}
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2872
|
+
if (key.escape && !actionLoading) {
|
|
2873
|
+
onClose();
|
|
2874
|
+
return;
|
|
2875
|
+
}
|
|
2876
|
+
if (key.upArrow || input === "k") {
|
|
2877
|
+
(_a2 = scrollRef.current) == null ? void 0 : _a2.scrollBy(-1);
|
|
2878
|
+
}
|
|
2879
|
+
if (key.downArrow || input === "j") {
|
|
2880
|
+
(_b = scrollRef.current) == null ? void 0 : _b.scrollBy(1);
|
|
2881
|
+
}
|
|
2882
|
+
if (input === "o") {
|
|
2883
|
+
open3(getIssueUrl()).catch(() => {
|
|
2884
|
+
});
|
|
2885
|
+
}
|
|
2886
|
+
if (input === "y") {
|
|
2887
|
+
copyToClipboard(getIssueUrl());
|
|
2888
|
+
}
|
|
2889
|
+
if (input === "s" && !actionLoading) {
|
|
2890
|
+
openTransitionPicker();
|
|
2891
|
+
}
|
|
2892
|
+
if (input === "a" && !actionLoading && myAccountId) {
|
|
2893
|
+
handleAssignToMe();
|
|
2894
|
+
}
|
|
2895
|
+
if (input === "A" && !actionLoading) {
|
|
2896
|
+
handleUnassign();
|
|
2897
|
+
}
|
|
2898
|
+
},
|
|
2899
|
+
{ isActive }
|
|
2900
|
+
);
|
|
2901
|
+
const statusColor = getStatusColor((detail == null ? void 0 : detail.fields.status.name) ?? "");
|
|
2902
|
+
const descriptionMd = (detail == null ? void 0 : detail.fields.description) ? adfToMarkdown(detail.fields.description) : null;
|
|
2903
|
+
const comments = (detail == null ? void 0 : detail.fields.comment.comments) ?? [];
|
|
2904
|
+
const totalComments = (detail == null ? void 0 : detail.fields.comment.total) ?? 0;
|
|
2905
|
+
return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", flexGrow: 1, children: [
|
|
2906
|
+
/* @__PURE__ */ jsxs8(Box9, { flexGrow: 1, flexBasis: 0, overflow: "hidden", children: [
|
|
2907
|
+
loading && /* @__PURE__ */ jsx9(Box9, { paddingX: 1, children: /* @__PURE__ */ jsxs8(Text8, { color: "yellow", children: [
|
|
2908
|
+
/* @__PURE__ */ jsx9(Spinner2, { type: "dots" }),
|
|
2909
|
+
" Loading issue details..."
|
|
2910
|
+
] }) }),
|
|
2911
|
+
error && /* @__PURE__ */ jsx9(Box9, { paddingX: 1, children: /* @__PURE__ */ jsx9(Text8, { color: "red", children: error }) }),
|
|
2912
|
+
!loading && !error && detail && /* @__PURE__ */ jsx9(ScrollView5, { ref: scrollRef, children: /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", paddingX: 1, children: [
|
|
2913
|
+
/* @__PURE__ */ jsxs8(Box9, { children: [
|
|
2914
|
+
/* @__PURE__ */ jsx9(Text8, { bold: true, color: "blue", children: detail.key }),
|
|
2915
|
+
/* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
|
|
2916
|
+
" ",
|
|
2917
|
+
detail.fields.summary
|
|
2918
|
+
] })
|
|
2919
|
+
] }),
|
|
2920
|
+
/* @__PURE__ */ jsxs8(Box9, { gap: 1, children: [
|
|
2921
|
+
/* @__PURE__ */ jsx9(Text8, { dimColor: true, children: "Status:" }),
|
|
2922
|
+
/* @__PURE__ */ jsx9(Text8, { color: statusColor, children: detail.fields.status.name }),
|
|
2923
|
+
/* @__PURE__ */ jsx9(Text8, { dimColor: true, children: "Assignee:" }),
|
|
2924
|
+
/* @__PURE__ */ jsx9(Text8, { children: ((_a = detail.fields.assignee) == null ? void 0 : _a.displayName) ?? "Unassigned" })
|
|
2925
|
+
] }),
|
|
2926
|
+
/* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Divider, {}) }),
|
|
2927
|
+
/* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "column", children: [
|
|
2928
|
+
/* @__PURE__ */ jsx9(Text8, { dimColor: true, children: "Description:" }),
|
|
2929
|
+
descriptionMd ? /* @__PURE__ */ jsx9(Markdown, { children: descriptionMd }) : /* @__PURE__ */ jsx9(Text8, { dimColor: true, italic: true, children: "No description" })
|
|
2930
|
+
] }),
|
|
2931
|
+
/* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Divider, {}) }),
|
|
2932
|
+
/* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "column", children: [
|
|
2933
|
+
/* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
2934
|
+
"Comments (",
|
|
2935
|
+
totalComments,
|
|
2936
|
+
"):"
|
|
2937
|
+
] }),
|
|
2938
|
+
comments.length === 0 && /* @__PURE__ */ jsx9(Text8, { dimColor: true, italic: true, children: "No comments" }),
|
|
2939
|
+
comments.map((comment) => /* @__PURE__ */ jsx9(CommentBlock, { comment }, comment.id)),
|
|
2940
|
+
comments.length < totalComments && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
2941
|
+
"Showing ",
|
|
2942
|
+
comments.length,
|
|
2943
|
+
" of ",
|
|
2944
|
+
totalComments,
|
|
2945
|
+
" comments. Open in browser to see all."
|
|
2946
|
+
] }) })
|
|
2947
|
+
] })
|
|
2948
|
+
] }) })
|
|
2949
|
+
] }),
|
|
2950
|
+
mode === "transitions" && /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
2951
|
+
/* @__PURE__ */ jsx9(Text8, { bold: true, color: "yellow", children: "Change Status" }),
|
|
2952
|
+
/* @__PURE__ */ jsx9(
|
|
2953
|
+
SelectInput,
|
|
2954
|
+
{
|
|
2955
|
+
items: transitions.map((t) => ({ label: t.name, value: t.id })),
|
|
2956
|
+
onSelect: handleTransitionSelect
|
|
2957
|
+
}
|
|
2958
|
+
),
|
|
2959
|
+
/* @__PURE__ */ jsx9(Text8, { dimColor: true, children: "Esc to cancel" })
|
|
2960
|
+
] }),
|
|
2961
|
+
/* @__PURE__ */ jsxs8(Box9, { paddingX: 1, flexDirection: "column", children: [
|
|
2962
|
+
actionLoading && /* @__PURE__ */ jsxs8(Text8, { color: "yellow", children: [
|
|
2963
|
+
/* @__PURE__ */ jsx9(Spinner2, { type: "dots" }),
|
|
2964
|
+
" ",
|
|
2965
|
+
actionLoading
|
|
2966
|
+
] }),
|
|
2967
|
+
actionError && /* @__PURE__ */ jsx9(Text8, { color: "red", children: actionError }),
|
|
2968
|
+
transitionsLoading && /* @__PURE__ */ jsxs8(Text8, { color: "yellow", children: [
|
|
2969
|
+
/* @__PURE__ */ jsx9(Spinner2, { type: "dots" }),
|
|
2970
|
+
" Loading transitions..."
|
|
2971
|
+
] }),
|
|
2972
|
+
!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" })
|
|
2973
|
+
] })
|
|
2974
|
+
] });
|
|
2975
|
+
}
|
|
2976
|
+
function CommentBlock({ comment }) {
|
|
2977
|
+
const bodyMd = adfToMarkdown(comment.body);
|
|
2978
|
+
return /* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "column", children: [
|
|
2979
|
+
/* @__PURE__ */ jsxs8(Box9, { gap: 1, children: [
|
|
2980
|
+
/* @__PURE__ */ jsx9(Text8, { bold: true, children: comment.author.displayName }),
|
|
2981
|
+
/* @__PURE__ */ jsx9(Text8, { dimColor: true, children: timeAgo(comment.created) })
|
|
2982
|
+
] }),
|
|
2983
|
+
bodyMd ? /* @__PURE__ */ jsx9(Markdown, { children: bodyMd }) : /* @__PURE__ */ jsx9(Text8, { dimColor: true, italic: true, children: "Empty comment" })
|
|
2984
|
+
] });
|
|
2985
|
+
}
|
|
2986
|
+
function getStatusColor(status) {
|
|
2987
|
+
const lower = status.toLowerCase();
|
|
2988
|
+
if (lower === "done" || lower === "closed" || lower === "resolved") return "green";
|
|
2989
|
+
if (lower === "in progress" || lower === "in review") return "yellow";
|
|
2990
|
+
return "gray";
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2993
|
+
// src/components/jira-browser/JiraSavedViewBrowserBox.tsx
|
|
2994
|
+
import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2995
|
+
function groupBySprint(issues) {
|
|
2996
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2997
|
+
for (const issue of issues) {
|
|
2998
|
+
const sprint = issue.fields.sprint ?? null;
|
|
2999
|
+
const key = sprint ? String(sprint.id) : "__backlog__";
|
|
3000
|
+
if (!groups.has(key)) {
|
|
3001
|
+
groups.set(key, { sprint, issues: [] });
|
|
3002
|
+
}
|
|
3003
|
+
groups.get(key).issues.push(issue);
|
|
3004
|
+
}
|
|
3005
|
+
const stateOrder = { active: 0, future: 1, closed: 2 };
|
|
3006
|
+
const entries = [...groups.values()];
|
|
3007
|
+
entries.sort((a, b) => {
|
|
3008
|
+
if (!a.sprint) return 1;
|
|
3009
|
+
if (!b.sprint) return -1;
|
|
3010
|
+
const aOrder = stateOrder[a.sprint.state] ?? 3;
|
|
3011
|
+
const bOrder = stateOrder[b.sprint.state] ?? 3;
|
|
3012
|
+
return aOrder - bOrder;
|
|
3013
|
+
});
|
|
3014
|
+
return entries;
|
|
3015
|
+
}
|
|
3016
|
+
function buildRows(groups) {
|
|
3017
|
+
var _a;
|
|
3018
|
+
const hasSprints = groups.some((g) => g.sprint !== null);
|
|
3019
|
+
if (!hasSprints) {
|
|
3020
|
+
return groups.flatMap((g) => g.issues.map((issue) => ({ type: "issue", issue })));
|
|
3021
|
+
}
|
|
3022
|
+
const rows = [];
|
|
3023
|
+
for (const group of groups) {
|
|
3024
|
+
const label = group.sprint ? group.sprint.name : "Backlog";
|
|
3025
|
+
const state = ((_a = group.sprint) == null ? void 0 : _a.state) ?? null;
|
|
3026
|
+
rows.push({ type: "header", label, state });
|
|
3027
|
+
for (const issue of group.issues) {
|
|
3028
|
+
rows.push({ type: "issue", issue });
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
3031
|
+
return rows;
|
|
3032
|
+
}
|
|
3033
|
+
function JiraSavedViewBrowserBox({
|
|
3034
|
+
view,
|
|
3035
|
+
auth,
|
|
3036
|
+
myAccountId,
|
|
3037
|
+
myDisplayName,
|
|
3038
|
+
isActive,
|
|
3039
|
+
onInputModeChange,
|
|
3040
|
+
onLogUpdated
|
|
3041
|
+
}) {
|
|
3042
|
+
const [issues, setIssues] = useState14([]);
|
|
3043
|
+
const [loading, setLoading] = useState14(false);
|
|
3044
|
+
const [error, setError] = useState14(null);
|
|
3045
|
+
const [total, setTotal] = useState14(0);
|
|
3046
|
+
const [highlightedIndex, setHighlightedIndex] = useState14(0);
|
|
3047
|
+
const [inputText, setInputText] = useState14("");
|
|
3048
|
+
const [searchText, setSearchText] = useState14("");
|
|
3049
|
+
const [isFiltering, setIsFiltering] = useState14(false);
|
|
3050
|
+
const [assigneeFilter, setAssigneeFilter] = useState14("all");
|
|
3051
|
+
const [detailIssue, setDetailIssue] = useState14(null);
|
|
3052
|
+
useEffect10(() => {
|
|
3053
|
+
onInputModeChange == null ? void 0 : onInputModeChange(isFiltering || detailIssue !== null);
|
|
3054
|
+
}, [isFiltering, detailIssue, onInputModeChange]);
|
|
3055
|
+
const title = "[6] Issues";
|
|
3056
|
+
const borderColor = isActive ? "yellow" : void 0;
|
|
3057
|
+
const displayTitle = view ? `${title} - ${view.name}` : title;
|
|
3058
|
+
const filteredIssues = useMemo2(() => {
|
|
3059
|
+
if (assigneeFilter === "unassigned") {
|
|
3060
|
+
return issues.filter((issue) => !issue.fields.assignee);
|
|
3061
|
+
}
|
|
3062
|
+
if (assigneeFilter === "me" && myAccountId) {
|
|
3063
|
+
return issues.filter((issue) => {
|
|
3064
|
+
var _a;
|
|
3065
|
+
return ((_a = issue.fields.assignee) == null ? void 0 : _a.accountId) === myAccountId;
|
|
3066
|
+
});
|
|
3067
|
+
}
|
|
3068
|
+
return issues;
|
|
3069
|
+
}, [issues, assigneeFilter, myAccountId]);
|
|
3070
|
+
const rows = useMemo2(() => {
|
|
3071
|
+
const groups = groupBySprint(filteredIssues);
|
|
3072
|
+
return buildRows(groups);
|
|
3073
|
+
}, [filteredIssues]);
|
|
3074
|
+
const navigableIndices = useMemo2(
|
|
3075
|
+
() => rows.map((r, i) => r.type === "issue" ? i : -1).filter((i) => i >= 0),
|
|
3076
|
+
[rows]
|
|
3077
|
+
);
|
|
3078
|
+
const scrollRef = useScrollToIndex(navigableIndices.length > 0 ? navigableIndices[highlightedIndex] ?? 0 : 0);
|
|
3079
|
+
const currentIssue = useMemo2(() => {
|
|
3080
|
+
if (navigableIndices.length === 0) return null;
|
|
3081
|
+
const rowIdx = navigableIndices[highlightedIndex];
|
|
3082
|
+
if (rowIdx === void 0) return null;
|
|
3083
|
+
const row = rows[rowIdx];
|
|
3084
|
+
return (row == null ? void 0 : row.type) === "issue" ? row.issue : null;
|
|
3085
|
+
}, [rows, navigableIndices, highlightedIndex]);
|
|
3086
|
+
const hasMore = issues.length < total;
|
|
3087
|
+
const doFetch = useCallback9(
|
|
3088
|
+
async (search, startAt = 0, append = false) => {
|
|
3089
|
+
if (!view || !auth) return;
|
|
3090
|
+
setLoading(true);
|
|
3091
|
+
setError(null);
|
|
3092
|
+
const result = await fetchViewIssues(auth, view, {
|
|
3093
|
+
startAt,
|
|
3094
|
+
maxResults: 50,
|
|
3095
|
+
searchText: search || void 0
|
|
3096
|
+
});
|
|
3097
|
+
if (result.success) {
|
|
3098
|
+
setIssues((prev) => append ? [...prev, ...result.data.issues] : result.data.issues);
|
|
3099
|
+
setTotal(result.data.total);
|
|
3100
|
+
if (!append) setHighlightedIndex(0);
|
|
3101
|
+
} else {
|
|
3102
|
+
setError(result.error);
|
|
3103
|
+
if (!append) {
|
|
3104
|
+
setIssues([]);
|
|
3105
|
+
setTotal(0);
|
|
3106
|
+
}
|
|
3107
|
+
}
|
|
3108
|
+
setLoading(false);
|
|
3109
|
+
},
|
|
3110
|
+
[view, auth]
|
|
3111
|
+
);
|
|
3112
|
+
useEffect10(() => {
|
|
3113
|
+
if (view && auth) {
|
|
3114
|
+
setSearchText("");
|
|
3115
|
+
setInputText("");
|
|
3116
|
+
doFetch("");
|
|
3117
|
+
} else {
|
|
3118
|
+
setIssues([]);
|
|
3119
|
+
setTotal(0);
|
|
3120
|
+
setError(null);
|
|
3121
|
+
}
|
|
3122
|
+
}, [view == null ? void 0 : view.id, auth == null ? void 0 : auth.siteUrl]);
|
|
3123
|
+
const getIssueUrl = (issue) => {
|
|
3124
|
+
if (!auth) return null;
|
|
3125
|
+
return `${auth.siteUrl}/browse/${issue.key}`;
|
|
3126
|
+
};
|
|
3127
|
+
const handleIssueUpdated = useCallback9(
|
|
3128
|
+
(key, updates) => {
|
|
3129
|
+
setIssues(
|
|
3130
|
+
(prev) => prev.map((issue) => {
|
|
3131
|
+
if (issue.key !== key) return issue;
|
|
3132
|
+
const updated = { ...issue, fields: { ...issue.fields } };
|
|
3133
|
+
if (updates.status !== void 0) {
|
|
3134
|
+
updated.fields.status = { name: updates.status };
|
|
3135
|
+
}
|
|
3136
|
+
if ("assignee" in updates) {
|
|
3137
|
+
updated.fields.assignee = updates.assignee;
|
|
3138
|
+
}
|
|
3139
|
+
return updated;
|
|
3140
|
+
})
|
|
3141
|
+
);
|
|
3142
|
+
},
|
|
3143
|
+
[]
|
|
3144
|
+
);
|
|
3145
|
+
const hasActiveFilters = searchText.length > 0 || assigneeFilter !== "all";
|
|
3146
|
+
useInput8(
|
|
3147
|
+
(input, key) => {
|
|
3148
|
+
if (detailIssue) return;
|
|
3149
|
+
if (isFiltering) {
|
|
3150
|
+
if (key.escape) {
|
|
3151
|
+
setIsFiltering(false);
|
|
3152
|
+
setInputText(searchText);
|
|
3153
|
+
return;
|
|
3154
|
+
}
|
|
3155
|
+
if (key.return) {
|
|
3156
|
+
setIsFiltering(false);
|
|
3157
|
+
const newSearch = inputText.trim();
|
|
3158
|
+
if (newSearch !== searchText) {
|
|
3159
|
+
setSearchText(newSearch);
|
|
3160
|
+
doFetch(newSearch);
|
|
3161
|
+
}
|
|
3162
|
+
return;
|
|
3163
|
+
}
|
|
3164
|
+
if (key.backspace || key.delete) {
|
|
3165
|
+
setInputText((prev) => prev.slice(0, -1));
|
|
3166
|
+
return;
|
|
3167
|
+
}
|
|
3168
|
+
if (input && input.length > 0) {
|
|
3169
|
+
const printable = input.replace(/[^\x20-\x7E\u00A0-\uFFFF]/g, "");
|
|
3170
|
+
if (printable.length > 0) {
|
|
3171
|
+
setInputText((prev) => prev + printable);
|
|
3172
|
+
}
|
|
3173
|
+
return;
|
|
3174
|
+
}
|
|
3175
|
+
return;
|
|
3176
|
+
}
|
|
3177
|
+
if (navigableIndices.length > 0) {
|
|
3178
|
+
if (key.upArrow || input === "k") {
|
|
3179
|
+
setHighlightedIndex((i) => Math.max(0, i - 1));
|
|
3180
|
+
}
|
|
3181
|
+
if (key.downArrow || input === "j") {
|
|
3182
|
+
setHighlightedIndex((i) => Math.min(navigableIndices.length - 1, i + 1));
|
|
3183
|
+
}
|
|
3184
|
+
if (input === "o" && currentIssue) {
|
|
3185
|
+
const url = getIssueUrl(currentIssue);
|
|
3186
|
+
if (url) open4(url).catch(() => {
|
|
3187
|
+
});
|
|
3188
|
+
}
|
|
3189
|
+
if (input === "y" && currentIssue) {
|
|
3190
|
+
const url = getIssueUrl(currentIssue);
|
|
3191
|
+
if (url) copyToClipboard(url);
|
|
3192
|
+
}
|
|
3193
|
+
if (key.return && currentIssue && auth) {
|
|
3194
|
+
setDetailIssue({ key: currentIssue.key, summary: currentIssue.fields.summary });
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
if (input === "/") {
|
|
3198
|
+
setIsFiltering(true);
|
|
3199
|
+
setInputText(searchText);
|
|
3200
|
+
return;
|
|
3201
|
+
}
|
|
3202
|
+
if (input === "u") {
|
|
3203
|
+
setAssigneeFilter((f) => f === "unassigned" ? "all" : "unassigned");
|
|
3204
|
+
setHighlightedIndex(0);
|
|
3205
|
+
return;
|
|
3206
|
+
}
|
|
3207
|
+
if (input === "m") {
|
|
3208
|
+
setAssigneeFilter((f) => f === "me" ? "all" : "me");
|
|
3209
|
+
setHighlightedIndex(0);
|
|
3210
|
+
return;
|
|
3211
|
+
}
|
|
3212
|
+
if (input === "x") {
|
|
3213
|
+
setAssigneeFilter("all");
|
|
3214
|
+
if (searchText) {
|
|
3215
|
+
setSearchText("");
|
|
3216
|
+
setInputText("");
|
|
3217
|
+
doFetch("");
|
|
3218
|
+
}
|
|
3219
|
+
setHighlightedIndex(0);
|
|
3220
|
+
return;
|
|
3221
|
+
}
|
|
3222
|
+
if (input === "l" && hasMore) {
|
|
3223
|
+
doFetch(searchText, issues.length, true);
|
|
3224
|
+
return;
|
|
3225
|
+
}
|
|
3226
|
+
if (input === "r") {
|
|
3227
|
+
doFetch(searchText);
|
|
3228
|
+
}
|
|
3229
|
+
},
|
|
3230
|
+
{ isActive }
|
|
3231
|
+
);
|
|
3232
|
+
const filterParts = [];
|
|
3233
|
+
if (assigneeFilter === "unassigned") filterParts.push("unassigned");
|
|
3234
|
+
if (assigneeFilter === "me") filterParts.push("mine");
|
|
3235
|
+
if (searchText) filterParts.push(`"${searchText}"`);
|
|
3236
|
+
return /* @__PURE__ */ jsx10(TitledBox4, { borderStyle: "round", titles: [displayTitle], borderColor, flexGrow: 1, children: /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", flexGrow: 1, children: detailIssue && auth ? /* @__PURE__ */ jsx10(
|
|
3237
|
+
JiraIssueDetailView,
|
|
3238
|
+
{
|
|
3239
|
+
issueKey: detailIssue.key,
|
|
3240
|
+
issueSummary: detailIssue.summary,
|
|
3241
|
+
auth,
|
|
3242
|
+
myAccountId,
|
|
3243
|
+
myDisplayName,
|
|
3244
|
+
isActive,
|
|
3245
|
+
onClose: () => setDetailIssue(null),
|
|
3246
|
+
onIssueUpdated: handleIssueUpdated,
|
|
3247
|
+
onLogUpdated
|
|
3248
|
+
}
|
|
3249
|
+
) : /* @__PURE__ */ jsxs9(Fragment2, { children: [
|
|
3250
|
+
(isFiltering || hasActiveFilters) && /* @__PURE__ */ jsxs9(Box10, { paddingX: 1, children: [
|
|
3251
|
+
/* @__PURE__ */ jsx10(Text9, { color: "blue", children: "Search: " }),
|
|
3252
|
+
isFiltering ? /* @__PURE__ */ jsxs9(Fragment2, { children: [
|
|
3253
|
+
/* @__PURE__ */ jsx10(Text9, { children: inputText }),
|
|
3254
|
+
/* @__PURE__ */ jsx10(Text9, { backgroundColor: "yellow", children: " " })
|
|
3255
|
+
] }) : /* @__PURE__ */ jsxs9(Fragment2, { children: [
|
|
3256
|
+
/* @__PURE__ */ jsx10(Text9, { children: filterParts.join(" + ") }),
|
|
3257
|
+
/* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
|
|
3258
|
+
" ",
|
|
3259
|
+
"(",
|
|
3260
|
+
filteredIssues.length,
|
|
3261
|
+
"/",
|
|
3262
|
+
total,
|
|
3263
|
+
")"
|
|
3264
|
+
] })
|
|
3265
|
+
] })
|
|
3266
|
+
] }),
|
|
3267
|
+
/* @__PURE__ */ jsxs9(Box10, { flexGrow: 1, flexBasis: 0, overflow: "hidden", children: [
|
|
3268
|
+
!view && /* @__PURE__ */ jsx10(Box10, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: "Select a view to browse issues" }) }),
|
|
3269
|
+
view && loading && issues.length === 0 && /* @__PURE__ */ jsx10(Box10, { paddingX: 1, children: /* @__PURE__ */ jsxs9(Text9, { color: "yellow", children: [
|
|
3270
|
+
/* @__PURE__ */ jsx10(Spinner3, { type: "dots" }),
|
|
3271
|
+
" Loading issues..."
|
|
3272
|
+
] }) }),
|
|
3273
|
+
view && error && /* @__PURE__ */ jsx10(Box10, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { color: "red", children: error }) }),
|
|
3274
|
+
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" }) }),
|
|
3275
|
+
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" }) }),
|
|
3276
|
+
rows.length > 0 && /* @__PURE__ */ jsx10(ScrollView6, { ref: scrollRef, children: rows.map((row, rowIdx) => {
|
|
3277
|
+
if (row.type === "header") {
|
|
3278
|
+
const stateLabel = row.state === "active" ? " (active)" : "";
|
|
3279
|
+
return /* @__PURE__ */ jsx10(Box10, { paddingX: 1, marginTop: rowIdx > 0 ? 1 : 0, children: /* @__PURE__ */ jsxs9(Text9, { bold: true, color: "magenta", children: [
|
|
3280
|
+
row.label,
|
|
3281
|
+
stateLabel && /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: stateLabel })
|
|
3282
|
+
] }) }, `header-${row.label}`);
|
|
3283
|
+
}
|
|
3284
|
+
const navIdx = navigableIndices.indexOf(rowIdx);
|
|
3285
|
+
const isHighlighted = navIdx === highlightedIndex;
|
|
3286
|
+
const cursor = isHighlighted ? ">" : " ";
|
|
3287
|
+
const statusColor = getStatusColor2(row.issue.fields.status.name);
|
|
3288
|
+
return /* @__PURE__ */ jsxs9(Box10, { paddingX: 1, children: [
|
|
3289
|
+
/* @__PURE__ */ jsxs9(Text9, { color: isHighlighted ? "yellow" : void 0, children: [
|
|
3290
|
+
cursor,
|
|
3291
|
+
" "
|
|
3292
|
+
] }),
|
|
3293
|
+
/* @__PURE__ */ jsx10(Text9, { bold: true, color: "blue", children: row.issue.key }),
|
|
3294
|
+
/* @__PURE__ */ jsxs9(Text9, { children: [
|
|
3295
|
+
" ",
|
|
3296
|
+
row.issue.fields.summary
|
|
3297
|
+
] }),
|
|
3298
|
+
/* @__PURE__ */ jsxs9(Text9, { color: statusColor, children: [
|
|
3299
|
+
" [",
|
|
3300
|
+
row.issue.fields.status.name,
|
|
3301
|
+
"]"
|
|
3302
|
+
] })
|
|
3303
|
+
] }, row.issue.key);
|
|
3304
|
+
}) })
|
|
3305
|
+
] }),
|
|
3306
|
+
view && !loading && issues.length > 0 && /* @__PURE__ */ jsx10(Box10, { paddingX: 1, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
|
|
3307
|
+
issues.length,
|
|
3308
|
+
" of ",
|
|
3309
|
+
total,
|
|
3310
|
+
" loaded",
|
|
3311
|
+
hasMore && " \xB7 l to load more"
|
|
3312
|
+
] }) }),
|
|
3313
|
+
view && loading && issues.length > 0 && /* @__PURE__ */ jsx10(Box10, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: "Loading more..." }) })
|
|
3314
|
+
] }) }) });
|
|
3315
|
+
}
|
|
3316
|
+
function getStatusColor2(status) {
|
|
3317
|
+
const lower = status.toLowerCase();
|
|
3318
|
+
if (lower === "done" || lower === "closed" || lower === "resolved") return "green";
|
|
3319
|
+
if (lower === "in progress" || lower === "in review") return "yellow";
|
|
3320
|
+
return "gray";
|
|
3321
|
+
}
|
|
3322
|
+
|
|
3323
|
+
// src/components/jira-browser/JiraSavedViewsBox.tsx
|
|
3324
|
+
import { useEffect as useEffect11, useState as useState15 } from "react";
|
|
3325
|
+
import { TitledBox as TitledBox5 } from "@mishieck/ink-titled-box";
|
|
3326
|
+
import { Box as Box11, Text as Text10, useInput as useInput9 } from "ink";
|
|
3327
|
+
import { ScrollView as ScrollView7 } from "ink-scroll-view";
|
|
3328
|
+
import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
3329
|
+
function JiraSavedViewsBox({
|
|
3330
|
+
views,
|
|
3331
|
+
selectedViewId,
|
|
3332
|
+
highlightedIndex,
|
|
3333
|
+
onHighlight,
|
|
3334
|
+
onSelect,
|
|
3335
|
+
onAdd,
|
|
3336
|
+
onDelete,
|
|
3337
|
+
onRename,
|
|
3338
|
+
isActive,
|
|
3339
|
+
onInputModeChange
|
|
3340
|
+
}) {
|
|
3341
|
+
const scrollRef = useScrollToIndex(highlightedIndex);
|
|
3342
|
+
const [renaming, setRenaming] = useState15(null);
|
|
3343
|
+
const [renameValue, setRenameValue] = useState15("");
|
|
3344
|
+
useEffect11(() => {
|
|
3345
|
+
onInputModeChange == null ? void 0 : onInputModeChange(renaming !== null);
|
|
3346
|
+
}, [renaming, onInputModeChange]);
|
|
3347
|
+
const title = "[5] Views";
|
|
3348
|
+
const borderColor = isActive ? "yellow" : void 0;
|
|
3349
|
+
useInput9(
|
|
3350
|
+
(input, key) => {
|
|
3351
|
+
if (renaming) {
|
|
3352
|
+
if (key.escape) {
|
|
3353
|
+
setRenaming(null);
|
|
3354
|
+
setRenameValue("");
|
|
3355
|
+
return;
|
|
3356
|
+
}
|
|
3357
|
+
if (key.return) {
|
|
3358
|
+
const trimmed = renameValue.trim();
|
|
3359
|
+
if (trimmed.length > 0) {
|
|
3360
|
+
onRename(renaming, trimmed);
|
|
3361
|
+
}
|
|
3362
|
+
setRenaming(null);
|
|
3363
|
+
setRenameValue("");
|
|
3364
|
+
return;
|
|
3365
|
+
}
|
|
3366
|
+
return;
|
|
3367
|
+
}
|
|
3368
|
+
if (views.length === 0) {
|
|
3369
|
+
if (input === "a") onAdd();
|
|
3370
|
+
return;
|
|
3371
|
+
}
|
|
3372
|
+
if (key.upArrow || input === "k") {
|
|
3373
|
+
onHighlight(Math.max(0, highlightedIndex - 1));
|
|
3374
|
+
}
|
|
3375
|
+
if (key.downArrow || input === "j") {
|
|
3376
|
+
onHighlight(Math.min(views.length - 1, highlightedIndex + 1));
|
|
3377
|
+
}
|
|
3378
|
+
if (input === " ") {
|
|
3379
|
+
const view = views[highlightedIndex];
|
|
3380
|
+
if (view) onSelect(view.id);
|
|
3381
|
+
}
|
|
3382
|
+
if (input === "a") onAdd();
|
|
3383
|
+
if (input === "e") {
|
|
3384
|
+
const view = views[highlightedIndex];
|
|
3385
|
+
if (view) {
|
|
3386
|
+
setRenaming(view.id);
|
|
3387
|
+
setRenameValue(view.name);
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3390
|
+
if (input === "d") {
|
|
3391
|
+
const view = views[highlightedIndex];
|
|
3392
|
+
if (view) onDelete(view.id);
|
|
3393
|
+
}
|
|
3394
|
+
},
|
|
3395
|
+
{ isActive }
|
|
3396
|
+
);
|
|
3397
|
+
return /* @__PURE__ */ jsx11(TitledBox5, { borderStyle: "round", titles: [title], borderColor, height: 5, children: /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", paddingX: 1, flexGrow: 1, overflow: "hidden", children: [
|
|
3398
|
+
views.length === 0 && /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: "No saved views" }),
|
|
3399
|
+
views.length > 0 && /* @__PURE__ */ jsx11(ScrollView7, { ref: scrollRef, children: views.map((view, idx) => {
|
|
3400
|
+
const isHighlighted = isActive && idx === highlightedIndex;
|
|
3401
|
+
const isSelected = view.id === selectedViewId;
|
|
3402
|
+
const isRenaming = view.id === renaming;
|
|
3403
|
+
const cursor = isHighlighted ? ">" : " ";
|
|
3404
|
+
const nameColor = isSelected ? "green" : void 0;
|
|
3405
|
+
const indicator = isSelected ? " *" : "";
|
|
3406
|
+
return /* @__PURE__ */ jsxs10(Box11, { children: [
|
|
3407
|
+
/* @__PURE__ */ jsxs10(Text10, { color: isHighlighted ? "yellow" : void 0, children: [
|
|
3408
|
+
cursor,
|
|
3409
|
+
" "
|
|
3410
|
+
] }),
|
|
3411
|
+
isRenaming ? /* @__PURE__ */ jsx11(TextInput, { value: renameValue, onChange: setRenameValue, isActive: true }) : /* @__PURE__ */ jsxs10(Fragment3, { children: [
|
|
3412
|
+
/* @__PURE__ */ jsx11(Text10, { color: nameColor, children: view.name }),
|
|
3413
|
+
/* @__PURE__ */ jsx11(Text10, { dimColor: true, children: indicator })
|
|
3414
|
+
] })
|
|
3415
|
+
] }, view.id);
|
|
3416
|
+
}) })
|
|
3417
|
+
] }) });
|
|
3418
|
+
}
|
|
3419
|
+
|
|
3420
|
+
// src/components/jira-browser/JiraBrowserView.tsx
|
|
3421
|
+
import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
3422
|
+
function JiraBrowserView({
|
|
3423
|
+
isActive,
|
|
3424
|
+
focusedBox,
|
|
3425
|
+
onFocusedBoxChange,
|
|
3426
|
+
onModalChange,
|
|
3427
|
+
onLogUpdated
|
|
3428
|
+
}) {
|
|
3429
|
+
const repo = useGitRepo();
|
|
3430
|
+
const modal = useModal();
|
|
3431
|
+
const [views, setViews] = useState16([]);
|
|
3432
|
+
const [selectedViewId, setSelectedViewId] = useState16(null);
|
|
3433
|
+
const [highlightedIndex, setHighlightedIndex] = useState16(0);
|
|
3434
|
+
const [addError, setAddError] = useState16(void 0);
|
|
3435
|
+
const [addLoading, setAddLoading] = useState16(false);
|
|
3436
|
+
const [myAccountId, setMyAccountId] = useState16(null);
|
|
3437
|
+
const [myDisplayName, setMyDisplayName] = useState16(null);
|
|
3438
|
+
const [inputModeActive, setInputModeActive] = useState16(false);
|
|
3439
|
+
const lastRepoRef = useRef7(null);
|
|
3440
|
+
const auth = useMemo3(() => {
|
|
3441
|
+
if (!repo.repoPath || !isJiraConfigured(repo.repoPath)) return null;
|
|
3442
|
+
const siteUrl = getJiraSiteUrl(repo.repoPath);
|
|
3443
|
+
const creds = getJiraCredentials(repo.repoPath);
|
|
3444
|
+
if (!siteUrl || !creds.email || !creds.apiToken) return null;
|
|
3445
|
+
return { siteUrl, email: creds.email, apiToken: creds.apiToken };
|
|
3446
|
+
}, [repo.repoPath]);
|
|
3447
|
+
const selectedView = useMemo3(() => views.find((v) => v.id === selectedViewId) ?? null, [views, selectedViewId]);
|
|
3448
|
+
useEffect12(() => {
|
|
3449
|
+
if (!repo.repoPath || repo.repoPath === lastRepoRef.current) return;
|
|
3450
|
+
lastRepoRef.current = repo.repoPath;
|
|
3451
|
+
const loaded = getSavedViews(repo.repoPath);
|
|
3452
|
+
setViews(loaded);
|
|
3453
|
+
if (loaded.length > 0 && !selectedViewId) {
|
|
3454
|
+
setSelectedViewId(loaded[0].id);
|
|
3455
|
+
}
|
|
3456
|
+
}, [repo.repoPath]);
|
|
3457
|
+
useEffect12(() => {
|
|
3458
|
+
if (!auth) {
|
|
3459
|
+
setMyAccountId(null);
|
|
3460
|
+
return;
|
|
3461
|
+
}
|
|
3462
|
+
getCurrentUser(auth).then((result) => {
|
|
3463
|
+
if (result.success) {
|
|
3464
|
+
setMyAccountId(result.data.accountId);
|
|
3465
|
+
setMyDisplayName(result.data.displayName);
|
|
3466
|
+
}
|
|
3467
|
+
});
|
|
3468
|
+
}, [auth == null ? void 0 : auth.siteUrl, auth == null ? void 0 : auth.email]);
|
|
3469
|
+
useEffect12(() => {
|
|
3470
|
+
onModalChange == null ? void 0 : onModalChange(modal.isOpen || inputModeActive);
|
|
3471
|
+
}, [modal.isOpen, inputModeActive, onModalChange]);
|
|
3472
|
+
useEffect12(() => {
|
|
3473
|
+
if (!isActive) modal.close();
|
|
3474
|
+
}, [isActive, modal.close]);
|
|
3475
|
+
const refreshViews = useCallback10(() => {
|
|
3476
|
+
if (!repo.repoPath) return;
|
|
3477
|
+
setViews(getSavedViews(repo.repoPath));
|
|
3478
|
+
}, [repo.repoPath]);
|
|
3479
|
+
const handleSelectView = useCallback10((viewId) => {
|
|
3480
|
+
setSelectedViewId(viewId);
|
|
3481
|
+
}, []);
|
|
3482
|
+
const handleAddView = useCallback10(
|
|
3483
|
+
async (url, name) => {
|
|
3484
|
+
if (!repo.repoPath) return;
|
|
3485
|
+
const source = parseJiraUrl(url);
|
|
3486
|
+
if (!source) {
|
|
3487
|
+
setAddError("Unrecognized Jira URL format");
|
|
3488
|
+
return;
|
|
3489
|
+
}
|
|
3490
|
+
setAddLoading(true);
|
|
3491
|
+
setAddError(void 0);
|
|
3492
|
+
try {
|
|
3493
|
+
const view = addSavedView(repo.repoPath, name, url, source);
|
|
3494
|
+
refreshViews();
|
|
3495
|
+
setSelectedViewId(view.id);
|
|
3496
|
+
modal.close();
|
|
3497
|
+
} catch {
|
|
3498
|
+
setAddError("Failed to save view");
|
|
3499
|
+
} finally {
|
|
3500
|
+
setAddLoading(false);
|
|
3501
|
+
}
|
|
3502
|
+
},
|
|
3503
|
+
[repo.repoPath, refreshViews, modal.close]
|
|
3504
|
+
);
|
|
3505
|
+
const handleRenameView = useCallback10(
|
|
3506
|
+
(viewId, newName) => {
|
|
3507
|
+
if (!repo.repoPath) return;
|
|
3508
|
+
renameSavedView(repo.repoPath, viewId, newName);
|
|
3509
|
+
refreshViews();
|
|
3510
|
+
},
|
|
3511
|
+
[repo.repoPath, refreshViews]
|
|
3512
|
+
);
|
|
3513
|
+
const handleDeleteView = useCallback10(
|
|
3514
|
+
(viewId) => {
|
|
3515
|
+
if (!repo.repoPath) return;
|
|
3516
|
+
removeSavedView(repo.repoPath, viewId);
|
|
3517
|
+
refreshViews();
|
|
3518
|
+
if (selectedViewId === viewId) {
|
|
3519
|
+
const remaining = getSavedViews(repo.repoPath);
|
|
3520
|
+
setSelectedViewId(remaining.length > 0 ? remaining[0].id : null);
|
|
3521
|
+
}
|
|
3522
|
+
setHighlightedIndex((i) => Math.max(0, i - 1));
|
|
3523
|
+
},
|
|
3524
|
+
[repo.repoPath, selectedViewId, refreshViews]
|
|
3525
|
+
);
|
|
3526
|
+
useInput10(
|
|
3527
|
+
(input) => {
|
|
3528
|
+
if (input === "5") onFocusedBoxChange("saved-views");
|
|
3529
|
+
if (input === "6") onFocusedBoxChange("browser");
|
|
3530
|
+
},
|
|
3531
|
+
{ isActive: isActive && !modal.isOpen }
|
|
3532
|
+
);
|
|
3533
|
+
if (modal.type === "add") {
|
|
3534
|
+
return /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx12(
|
|
3535
|
+
AddViewModal,
|
|
3536
|
+
{
|
|
3537
|
+
onSubmit: handleAddView,
|
|
3538
|
+
onCancel: () => {
|
|
3539
|
+
modal.close();
|
|
3540
|
+
setAddError(void 0);
|
|
3541
|
+
},
|
|
3542
|
+
loading: addLoading,
|
|
3543
|
+
error: addError
|
|
3544
|
+
}
|
|
3545
|
+
) });
|
|
3546
|
+
}
|
|
3547
|
+
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", flexGrow: 1, children: [
|
|
3548
|
+
/* @__PURE__ */ jsx12(
|
|
3549
|
+
JiraSavedViewsBox,
|
|
3550
|
+
{
|
|
3551
|
+
views,
|
|
3552
|
+
selectedViewId,
|
|
3553
|
+
highlightedIndex,
|
|
3554
|
+
onHighlight: setHighlightedIndex,
|
|
3555
|
+
onSelect: handleSelectView,
|
|
3556
|
+
onAdd: () => modal.open("add"),
|
|
3557
|
+
onDelete: handleDeleteView,
|
|
3558
|
+
onRename: handleRenameView,
|
|
3559
|
+
isActive: isActive && focusedBox === "saved-views",
|
|
3560
|
+
onInputModeChange: setInputModeActive
|
|
3561
|
+
}
|
|
3562
|
+
),
|
|
3563
|
+
/* @__PURE__ */ jsx12(
|
|
3564
|
+
JiraSavedViewBrowserBox,
|
|
3565
|
+
{
|
|
3566
|
+
view: selectedView,
|
|
3567
|
+
auth,
|
|
3568
|
+
myAccountId,
|
|
3569
|
+
myDisplayName,
|
|
3570
|
+
isActive: isActive && focusedBox === "browser",
|
|
3571
|
+
onInputModeChange: setInputModeActive,
|
|
3572
|
+
onLogUpdated
|
|
3573
|
+
}
|
|
3574
|
+
)
|
|
3575
|
+
] });
|
|
3576
|
+
}
|
|
3577
|
+
|
|
3578
|
+
// src/components/jira/JiraView.tsx
|
|
3579
|
+
import open5 from "open";
|
|
3580
|
+
import { useEffect as useEffect14, useRef as useRef8 } from "react";
|
|
3581
|
+
|
|
3582
|
+
// src/components/jira/LinkTicketModal.tsx
|
|
3583
|
+
import { useState as useState17 } from "react";
|
|
3584
|
+
import { Box as Box13, Text as Text11, useInput as useInput11 } from "ink";
|
|
3585
|
+
import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3586
|
+
function LinkTicketModal({ onSubmit, onCancel, loading, error }) {
|
|
3587
|
+
const [ticketInput, setTicketInput] = useState17("");
|
|
3588
|
+
const canSubmit = ticketInput.trim().length > 0;
|
|
3589
|
+
useInput11(
|
|
3590
|
+
(_input, key) => {
|
|
3591
|
+
if (loading) return;
|
|
3592
|
+
if (key.escape) {
|
|
3593
|
+
onCancel();
|
|
3594
|
+
return;
|
|
3595
|
+
}
|
|
3596
|
+
if (key.return && canSubmit) {
|
|
3597
|
+
onSubmit(ticketInput.trim());
|
|
3598
|
+
}
|
|
3599
|
+
},
|
|
3600
|
+
{ isActive: !loading }
|
|
3601
|
+
);
|
|
3602
|
+
return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, paddingY: 1, children: [
|
|
3603
|
+
/* @__PURE__ */ jsx13(Text11, { bold: true, color: "yellow", children: "Link Jira Ticket" }),
|
|
3604
|
+
/* @__PURE__ */ jsx13(Text11, { dimColor: true, children: "Type ticket ID, Enter to submit, Esc to cancel" }),
|
|
3605
|
+
/* @__PURE__ */ jsx13(Box13, { marginTop: 1 }),
|
|
3606
|
+
error && /* @__PURE__ */ jsx13(Box13, { marginBottom: 1, children: /* @__PURE__ */ jsx13(Text11, { color: "red", children: error }) }),
|
|
3607
|
+
/* @__PURE__ */ jsxs12(Box13, { children: [
|
|
3608
|
+
/* @__PURE__ */ jsx13(Text11, { color: "blue", children: "Ticket: " }),
|
|
3609
|
+
/* @__PURE__ */ jsx13(TextInput, { value: ticketInput, onChange: setTicketInput, placeholder: "PROJ-123", isActive: !loading })
|
|
3610
|
+
] }),
|
|
3611
|
+
loading && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx13(Text11, { color: "yellow", children: "Fetching ticket..." }) }),
|
|
3612
|
+
/* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: "Examples: PROJ-123 or https://company.atlassian.net/browse/PROJ-123" }) })
|
|
3613
|
+
] });
|
|
3614
|
+
}
|
|
3615
|
+
|
|
3616
|
+
// src/components/jira/JiraView.tsx
|
|
3617
|
+
import { TitledBox as TitledBox6 } from "@mishieck/ink-titled-box";
|
|
3618
|
+
import { Box as Box17, Text as Text15, useInput as useInput14 } from "ink";
|
|
3619
|
+
|
|
3620
|
+
// src/components/jira/ChangeStatusModal.tsx
|
|
3621
|
+
import { useEffect as useEffect13, useState as useState18 } from "react";
|
|
3622
|
+
import { Box as Box14, Text as Text12, useInput as useInput12 } from "ink";
|
|
3623
|
+
import SelectInput2 from "ink-select-input";
|
|
3624
|
+
import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
3625
|
+
function ChangeStatusModal({ repoPath, ticketKey, currentStatus, onComplete, onCancel }) {
|
|
3626
|
+
const [transitions, setTransitions] = useState18([]);
|
|
3627
|
+
const [loading, setLoading] = useState18(true);
|
|
3628
|
+
const [applying, setApplying] = useState18(false);
|
|
3629
|
+
const [error, setError] = useState18(null);
|
|
3630
|
+
useEffect13(() => {
|
|
3631
|
+
const fetchTransitions = async () => {
|
|
3632
|
+
const siteUrl = getJiraSiteUrl(repoPath);
|
|
3633
|
+
const creds = getJiraCredentials(repoPath);
|
|
3634
|
+
if (!siteUrl || !creds.email || !creds.apiToken) {
|
|
3635
|
+
setError("Jira not configured");
|
|
3636
|
+
duckEvents.emit("error");
|
|
3637
|
+
setLoading(false);
|
|
3638
|
+
return;
|
|
3639
|
+
}
|
|
3640
|
+
const auth = { siteUrl, email: creds.email, apiToken: creds.apiToken };
|
|
3641
|
+
const result = await getTransitions(auth, ticketKey);
|
|
3642
|
+
if (result.success) {
|
|
3643
|
+
setTransitions(result.data);
|
|
3644
|
+
} else {
|
|
3645
|
+
setError(result.error);
|
|
3646
|
+
duckEvents.emit("error");
|
|
3647
|
+
}
|
|
3648
|
+
setLoading(false);
|
|
3649
|
+
};
|
|
3650
|
+
fetchTransitions();
|
|
3651
|
+
}, [repoPath, ticketKey]);
|
|
3652
|
+
const handleSelect = async (item) => {
|
|
3653
|
+
setApplying(true);
|
|
3654
|
+
setError(null);
|
|
3655
|
+
const siteUrl = getJiraSiteUrl(repoPath);
|
|
3656
|
+
const creds = getJiraCredentials(repoPath);
|
|
3657
|
+
if (!siteUrl || !creds.email || !creds.apiToken) {
|
|
3658
|
+
setError("Jira not configured");
|
|
3659
|
+
duckEvents.emit("error");
|
|
3660
|
+
setApplying(false);
|
|
3661
|
+
return;
|
|
3662
|
+
}
|
|
3663
|
+
const auth = { siteUrl, email: creds.email, apiToken: creds.apiToken };
|
|
3664
|
+
const result = await applyTransition(auth, ticketKey, item.value);
|
|
3665
|
+
if (result.success) {
|
|
3666
|
+
const transition = transitions.find((t) => t.id === item.value);
|
|
3667
|
+
const newStatus = (transition == null ? void 0 : transition.to.name) ?? item.label;
|
|
3668
|
+
duckEvents.emit("jira:transition");
|
|
3669
|
+
onComplete(newStatus);
|
|
3670
|
+
} else {
|
|
3671
|
+
setError(result.error);
|
|
3672
|
+
duckEvents.emit("error");
|
|
3673
|
+
setApplying(false);
|
|
3674
|
+
}
|
|
3675
|
+
};
|
|
3676
|
+
useInput12(
|
|
3677
|
+
(_input, key) => {
|
|
3678
|
+
if (key.escape && !applying) {
|
|
3679
|
+
onCancel();
|
|
3680
|
+
}
|
|
3681
|
+
},
|
|
3682
|
+
{ isActive: !applying }
|
|
3683
|
+
);
|
|
3684
|
+
const items = transitions.map((t) => ({
|
|
3685
|
+
label: t.name,
|
|
3686
|
+
value: t.id
|
|
3687
|
+
}));
|
|
3688
|
+
const initialIndex = Math.max(
|
|
3689
|
+
0,
|
|
3690
|
+
transitions.findIndex((t) => t.to.name === currentStatus)
|
|
3691
|
+
);
|
|
3692
|
+
return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, paddingY: 1, children: [
|
|
3693
|
+
/* @__PURE__ */ jsxs13(Text12, { bold: true, color: "yellow", children: [
|
|
3694
|
+
"Change Status: ",
|
|
2390
3695
|
ticketKey
|
|
2391
3696
|
] }),
|
|
2392
|
-
loading && /* @__PURE__ */
|
|
2393
|
-
error && /* @__PURE__ */
|
|
2394
|
-
!loading && !error && transitions.length === 0 && /* @__PURE__ */
|
|
2395
|
-
!loading && !error && transitions.length > 0 && !applying && /* @__PURE__ */
|
|
2396
|
-
applying && /* @__PURE__ */
|
|
2397
|
-
/* @__PURE__ */
|
|
3697
|
+
loading && /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Loading transitions..." }),
|
|
3698
|
+
error && /* @__PURE__ */ jsx14(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { color: "red", children: error }) }),
|
|
3699
|
+
!loading && !error && transitions.length === 0 && /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "No available transitions" }),
|
|
3700
|
+
!loading && !error && transitions.length > 0 && !applying && /* @__PURE__ */ jsx14(Box14, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx14(SelectInput2, { items, initialIndex, onSelect: handleSelect }) }),
|
|
3701
|
+
applying && /* @__PURE__ */ jsx14(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { color: "yellow", children: "Updating status..." }) }),
|
|
3702
|
+
/* @__PURE__ */ jsx14(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Esc to cancel" }) })
|
|
2398
3703
|
] });
|
|
2399
3704
|
}
|
|
2400
3705
|
|
|
2401
3706
|
// src/components/jira/ConfigureJiraSiteModal.tsx
|
|
2402
|
-
import { useState as
|
|
2403
|
-
import { Box as
|
|
2404
|
-
import { ScrollView as
|
|
3707
|
+
import { useState as useState19 } from "react";
|
|
3708
|
+
import { Box as Box15, Text as Text13, useInput as useInput13 } from "ink";
|
|
3709
|
+
import { ScrollView as ScrollView8 } from "ink-scroll-view";
|
|
2405
3710
|
|
|
2406
3711
|
// src/lib/editor.ts
|
|
2407
3712
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
@@ -2432,7 +3737,7 @@ function openInEditor(content, filename) {
|
|
|
2432
3737
|
}
|
|
2433
3738
|
|
|
2434
3739
|
// src/components/jira/ConfigureJiraSiteModal.tsx
|
|
2435
|
-
import { jsx as
|
|
3740
|
+
import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
2436
3741
|
var MAX_VISIBLE_ITEMS = 4;
|
|
2437
3742
|
function ConfigureJiraSiteModal({
|
|
2438
3743
|
initialSiteUrl,
|
|
@@ -2444,17 +3749,17 @@ function ConfigureJiraSiteModal({
|
|
|
2444
3749
|
error
|
|
2445
3750
|
}) {
|
|
2446
3751
|
const hasExisting = existingConfigs.length > 0;
|
|
2447
|
-
const [mode, setMode] =
|
|
2448
|
-
const [selectedExisting, setSelectedExisting] =
|
|
3752
|
+
const [mode, setMode] = useState19(hasExisting ? "choose" : "manual");
|
|
3753
|
+
const [selectedExisting, setSelectedExisting] = useState19(0);
|
|
2449
3754
|
const scrollRef = useScrollToIndex(selectedExisting);
|
|
2450
|
-
const [siteUrl, setSiteUrl] =
|
|
2451
|
-
const [email, setEmail] =
|
|
2452
|
-
const [apiToken, setApiToken] =
|
|
2453
|
-
const [selectedItem, setSelectedItem] =
|
|
3755
|
+
const [siteUrl, setSiteUrl] = useState19(initialSiteUrl ?? "");
|
|
3756
|
+
const [email, setEmail] = useState19(initialEmail ?? "");
|
|
3757
|
+
const [apiToken, setApiToken] = useState19("");
|
|
3758
|
+
const [selectedItem, setSelectedItem] = useState19("siteUrl");
|
|
2454
3759
|
const items = ["siteUrl", "email", "apiToken", "submit"];
|
|
2455
3760
|
const canSubmit = siteUrl.trim() && email.trim() && apiToken.trim();
|
|
2456
3761
|
const chooseItems = existingConfigs.length + 1;
|
|
2457
|
-
|
|
3762
|
+
useInput13(
|
|
2458
3763
|
(input, key) => {
|
|
2459
3764
|
if (loading) return;
|
|
2460
3765
|
if (key.escape) {
|
|
@@ -2526,38 +3831,38 @@ function ConfigureJiraSiteModal({
|
|
|
2526
3831
|
const prefix = isSelected ? "> " : " ";
|
|
2527
3832
|
const color = isSelected ? "yellow" : void 0;
|
|
2528
3833
|
const displayValue = isSensitive && value ? "*".repeat(Math.min(value.length, 20)) : value;
|
|
2529
|
-
return /* @__PURE__ */
|
|
2530
|
-
/* @__PURE__ */
|
|
3834
|
+
return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
|
|
3835
|
+
/* @__PURE__ */ jsxs14(Text13, { color, bold: isSelected, children: [
|
|
2531
3836
|
prefix,
|
|
2532
3837
|
label
|
|
2533
3838
|
] }),
|
|
2534
|
-
value !== void 0 && /* @__PURE__ */
|
|
3839
|
+
value !== void 0 && /* @__PURE__ */ jsx15(Box15, { marginLeft: 4, children: /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: displayValue || "(empty - press Enter to edit)" }) })
|
|
2535
3840
|
] });
|
|
2536
3841
|
};
|
|
2537
3842
|
if (mode === "choose") {
|
|
2538
3843
|
const totalItems = existingConfigs.length + 1;
|
|
2539
3844
|
const listHeight = Math.min(totalItems * 2, MAX_VISIBLE_ITEMS * 2);
|
|
2540
|
-
return /* @__PURE__ */
|
|
2541
|
-
/* @__PURE__ */
|
|
2542
|
-
/* @__PURE__ */
|
|
2543
|
-
/* @__PURE__ */
|
|
2544
|
-
error && /* @__PURE__ */
|
|
2545
|
-
/* @__PURE__ */
|
|
3845
|
+
return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, paddingY: 1, children: [
|
|
3846
|
+
/* @__PURE__ */ jsx15(Text13, { bold: true, color: "cyan", children: "Configure Jira Site" }),
|
|
3847
|
+
/* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "Select an existing configuration or enter new credentials" }),
|
|
3848
|
+
/* @__PURE__ */ jsx15(Box15, { marginTop: 1 }),
|
|
3849
|
+
error && /* @__PURE__ */ jsx15(Box15, { marginBottom: 1, children: /* @__PURE__ */ jsx15(Text13, { color: "red", children: error }) }),
|
|
3850
|
+
/* @__PURE__ */ jsx15(Box15, { height: listHeight, overflow: "hidden", children: /* @__PURE__ */ jsxs14(ScrollView8, { ref: scrollRef, children: [
|
|
2546
3851
|
existingConfigs.map((config, idx) => {
|
|
2547
3852
|
const isSelected = selectedExisting === idx;
|
|
2548
|
-
return /* @__PURE__ */
|
|
2549
|
-
/* @__PURE__ */
|
|
3853
|
+
return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
|
|
3854
|
+
/* @__PURE__ */ jsxs14(Text13, { color: isSelected ? "yellow" : void 0, bold: isSelected, children: [
|
|
2550
3855
|
isSelected ? "> " : " ",
|
|
2551
3856
|
config.siteUrl
|
|
2552
3857
|
] }),
|
|
2553
|
-
/* @__PURE__ */
|
|
3858
|
+
/* @__PURE__ */ jsxs14(Text13, { dimColor: true, children: [
|
|
2554
3859
|
" ",
|
|
2555
3860
|
config.email
|
|
2556
3861
|
] })
|
|
2557
3862
|
] }, config.siteUrl + config.email);
|
|
2558
3863
|
}),
|
|
2559
|
-
/* @__PURE__ */
|
|
2560
|
-
|
|
3864
|
+
/* @__PURE__ */ jsx15(Box15, { children: /* @__PURE__ */ jsxs14(
|
|
3865
|
+
Text13,
|
|
2561
3866
|
{
|
|
2562
3867
|
color: selectedExisting === existingConfigs.length ? "yellow" : void 0,
|
|
2563
3868
|
bold: selectedExisting === existingConfigs.length,
|
|
@@ -2568,44 +3873,44 @@ function ConfigureJiraSiteModal({
|
|
|
2568
3873
|
}
|
|
2569
3874
|
) })
|
|
2570
3875
|
] }) }),
|
|
2571
|
-
loading && /* @__PURE__ */
|
|
3876
|
+
loading && /* @__PURE__ */ jsx15(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text13, { color: "yellow", children: "Validating credentials..." }) })
|
|
2572
3877
|
] });
|
|
2573
3878
|
}
|
|
2574
|
-
return /* @__PURE__ */
|
|
2575
|
-
/* @__PURE__ */
|
|
2576
|
-
/* @__PURE__ */
|
|
3879
|
+
return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, paddingY: 1, children: [
|
|
3880
|
+
/* @__PURE__ */ jsx15(Text13, { bold: true, color: "cyan", children: "Configure Jira Site" }),
|
|
3881
|
+
/* @__PURE__ */ jsxs14(Text13, { dimColor: true, children: [
|
|
2577
3882
|
"Up/Down to select, Enter to edit, Esc to ",
|
|
2578
3883
|
hasExisting ? "go back" : "cancel"
|
|
2579
3884
|
] }),
|
|
2580
|
-
/* @__PURE__ */
|
|
2581
|
-
error && /* @__PURE__ */
|
|
3885
|
+
/* @__PURE__ */ jsx15(Box15, { marginTop: 1 }),
|
|
3886
|
+
error && /* @__PURE__ */ jsx15(Box15, { marginBottom: 1, children: /* @__PURE__ */ jsx15(Text13, { color: "red", children: error }) }),
|
|
2582
3887
|
renderItem("siteUrl", "Site URL (e.g., https://company.atlassian.net)", siteUrl),
|
|
2583
|
-
/* @__PURE__ */
|
|
3888
|
+
/* @__PURE__ */ jsx15(Box15, { marginTop: 1 }),
|
|
2584
3889
|
renderItem("email", "Email", email),
|
|
2585
|
-
/* @__PURE__ */
|
|
3890
|
+
/* @__PURE__ */ jsx15(Box15, { marginTop: 1 }),
|
|
2586
3891
|
renderItem("apiToken", "API Token", apiToken, true),
|
|
2587
|
-
/* @__PURE__ */
|
|
2588
|
-
/* @__PURE__ */
|
|
3892
|
+
/* @__PURE__ */ jsx15(Box15, { marginTop: 1 }),
|
|
3893
|
+
/* @__PURE__ */ jsx15(Box15, { children: /* @__PURE__ */ jsxs14(Text13, { color: selectedItem === "submit" ? "green" : void 0, bold: selectedItem === "submit", children: [
|
|
2589
3894
|
selectedItem === "submit" ? "> " : " ",
|
|
2590
3895
|
canSubmit ? "[Save Configuration]" : "[Fill all fields first]"
|
|
2591
3896
|
] }) }),
|
|
2592
|
-
loading && /* @__PURE__ */
|
|
2593
|
-
/* @__PURE__ */
|
|
3897
|
+
loading && /* @__PURE__ */ jsx15(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text13, { color: "yellow", children: "Validating credentials..." }) }),
|
|
3898
|
+
/* @__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" }) })
|
|
2594
3899
|
] });
|
|
2595
3900
|
}
|
|
2596
3901
|
|
|
2597
3902
|
// src/components/jira/TicketItem.tsx
|
|
2598
|
-
import { Box as
|
|
2599
|
-
import { jsx as
|
|
3903
|
+
import { Box as Box16, Text as Text14 } from "ink";
|
|
3904
|
+
import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
2600
3905
|
function TicketItem({ ticketKey, summary, status, isHighlighted, isSelected }) {
|
|
2601
3906
|
const prefix = isHighlighted ? "> " : isSelected ? "\u25CF " : " ";
|
|
2602
3907
|
const textColor = isSelected ? "green" : void 0;
|
|
2603
|
-
return /* @__PURE__ */
|
|
3908
|
+
return /* @__PURE__ */ jsx16(Box16, { children: /* @__PURE__ */ jsxs15(Text14, { color: textColor, children: [
|
|
2604
3909
|
prefix,
|
|
2605
|
-
/* @__PURE__ */
|
|
3910
|
+
/* @__PURE__ */ jsx16(Text14, { bold: true, color: "blue", children: ticketKey }),
|
|
2606
3911
|
" ",
|
|
2607
3912
|
summary,
|
|
2608
|
-
status && /* @__PURE__ */
|
|
3913
|
+
status && /* @__PURE__ */ jsxs15(Text14, { dimColor: true, children: [
|
|
2609
3914
|
" [",
|
|
2610
3915
|
status,
|
|
2611
3916
|
"]"
|
|
@@ -2614,15 +3919,15 @@ function TicketItem({ ticketKey, summary, status, isHighlighted, isSelected }) {
|
|
|
2614
3919
|
}
|
|
2615
3920
|
|
|
2616
3921
|
// src/components/jira/JiraView.tsx
|
|
2617
|
-
import { jsx as
|
|
3922
|
+
import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
2618
3923
|
function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated }) {
|
|
2619
3924
|
const repo = useGitRepo();
|
|
2620
3925
|
const jira = useJiraTickets();
|
|
2621
3926
|
const modal = useModal();
|
|
2622
3927
|
const nav = useListNavigation({ items: jira.tickets });
|
|
2623
3928
|
const currentTicket = jira.tickets[nav.index] ?? null;
|
|
2624
|
-
const lastInitRef =
|
|
2625
|
-
|
|
3929
|
+
const lastInitRef = useRef8(null);
|
|
3930
|
+
useEffect14(() => {
|
|
2626
3931
|
if (repo.loading || !repo.repoPath || !repo.currentBranch) return;
|
|
2627
3932
|
const current = { branch: repo.currentBranch };
|
|
2628
3933
|
const last = lastInitRef.current;
|
|
@@ -2630,17 +3935,17 @@ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated })
|
|
|
2630
3935
|
lastInitRef.current = current;
|
|
2631
3936
|
jira.initializeJiraState(repo.repoPath, repo.currentBranch, repo.currentRepoSlug);
|
|
2632
3937
|
}, [repo.loading, repo.repoPath, repo.currentBranch, repo.currentRepoSlug, jira.initializeJiraState]);
|
|
2633
|
-
|
|
3938
|
+
useEffect14(() => {
|
|
2634
3939
|
if (isActive) {
|
|
2635
3940
|
repo.refreshBranch();
|
|
2636
3941
|
} else {
|
|
2637
3942
|
modal.close();
|
|
2638
3943
|
}
|
|
2639
3944
|
}, [isActive, repo.refreshBranch, modal.close]);
|
|
2640
|
-
|
|
3945
|
+
useEffect14(() => {
|
|
2641
3946
|
onModalChange == null ? void 0 : onModalChange(modal.isOpen);
|
|
2642
3947
|
}, [modal.isOpen, onModalChange]);
|
|
2643
|
-
|
|
3948
|
+
useEffect14(() => {
|
|
2644
3949
|
onJiraStateChange == null ? void 0 : onJiraStateChange(jira.jiraState);
|
|
2645
3950
|
}, [jira.jiraState, onJiraStateChange]);
|
|
2646
3951
|
const handleConfigureSubmit = async (siteUrl, email, apiToken) => {
|
|
@@ -2666,7 +3971,7 @@ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated })
|
|
|
2666
3971
|
};
|
|
2667
3972
|
const handleOpenInBrowser = () => {
|
|
2668
3973
|
const url = getTicketUrl();
|
|
2669
|
-
if (url)
|
|
3974
|
+
if (url) open5(url).catch(() => {
|
|
2670
3975
|
});
|
|
2671
3976
|
};
|
|
2672
3977
|
const handleCopyLink = () => {
|
|
@@ -2686,7 +3991,7 @@ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated })
|
|
|
2686
3991
|
clearJiraConfig(repo.repoPath);
|
|
2687
3992
|
jira.initializeJiraState(repo.repoPath, repo.currentBranch, repo.currentRepoSlug);
|
|
2688
3993
|
};
|
|
2689
|
-
|
|
3994
|
+
useInput14(
|
|
2690
3995
|
(input, key) => {
|
|
2691
3996
|
if (input === "c" && jira.jiraState === "not_configured") {
|
|
2692
3997
|
modal.open("configure");
|
|
@@ -2712,13 +4017,13 @@ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated })
|
|
|
2712
4017
|
{ isActive: isActive && !modal.isOpen }
|
|
2713
4018
|
);
|
|
2714
4019
|
if (repo.isRepo === false) {
|
|
2715
|
-
return /* @__PURE__ */
|
|
4020
|
+
return /* @__PURE__ */ jsx17(TitledBox6, { borderStyle: "round", titles: ["Jira"], flexShrink: 0, children: /* @__PURE__ */ jsx17(Text15, { color: "red", children: "Not a git repository" }) });
|
|
2716
4021
|
}
|
|
2717
4022
|
if (modal.type === "configure") {
|
|
2718
4023
|
const siteUrl = repo.repoPath ? getJiraSiteUrl(repo.repoPath) : void 0;
|
|
2719
4024
|
const creds = repo.repoPath ? getJiraCredentials(repo.repoPath) : { email: null, apiToken: null };
|
|
2720
4025
|
const existingConfigs = getExistingJiraConfigs(repo.repoPath ?? void 0);
|
|
2721
|
-
return /* @__PURE__ */
|
|
4026
|
+
return /* @__PURE__ */ jsx17(Box17, { flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsx17(
|
|
2722
4027
|
ConfigureJiraSiteModal,
|
|
2723
4028
|
{
|
|
2724
4029
|
initialSiteUrl: siteUrl ?? void 0,
|
|
@@ -2735,7 +4040,7 @@ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated })
|
|
|
2735
4040
|
) });
|
|
2736
4041
|
}
|
|
2737
4042
|
if (modal.type === "link") {
|
|
2738
|
-
return /* @__PURE__ */
|
|
4043
|
+
return /* @__PURE__ */ jsx17(Box17, { flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsx17(
|
|
2739
4044
|
LinkTicketModal,
|
|
2740
4045
|
{
|
|
2741
4046
|
onSubmit: handleLinkSubmit,
|
|
@@ -2749,7 +4054,7 @@ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated })
|
|
|
2749
4054
|
) });
|
|
2750
4055
|
}
|
|
2751
4056
|
if (modal.type === "status" && repo.repoPath && repo.currentBranch && currentTicket) {
|
|
2752
|
-
return /* @__PURE__ */
|
|
4057
|
+
return /* @__PURE__ */ jsx17(Box17, { flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsx17(
|
|
2753
4058
|
ChangeStatusModal,
|
|
2754
4059
|
{
|
|
2755
4060
|
repoPath: repo.repoPath,
|
|
@@ -2762,10 +4067,10 @@ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated })
|
|
|
2762
4067
|
}
|
|
2763
4068
|
const title = "[4] Jira";
|
|
2764
4069
|
const borderColor = isActive ? "yellow" : void 0;
|
|
2765
|
-
return /* @__PURE__ */
|
|
2766
|
-
jira.jiraState === "not_configured" && /* @__PURE__ */
|
|
2767
|
-
jira.jiraState === "no_tickets" && /* @__PURE__ */
|
|
2768
|
-
jira.jiraState === "has_tickets" && jira.tickets.map((ticket, idx) => /* @__PURE__ */
|
|
4070
|
+
return /* @__PURE__ */ jsx17(TitledBox6, { borderStyle: "round", titles: [title], borderColor, flexShrink: 0, children: /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", paddingX: 1, children: [
|
|
4071
|
+
jira.jiraState === "not_configured" && /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "No Jira site configured" }),
|
|
4072
|
+
jira.jiraState === "no_tickets" && /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "No tickets linked to this branch" }),
|
|
4073
|
+
jira.jiraState === "has_tickets" && jira.tickets.map((ticket, idx) => /* @__PURE__ */ jsx17(
|
|
2769
4074
|
TicketItem,
|
|
2770
4075
|
{
|
|
2771
4076
|
ticketKey: ticket.key,
|
|
@@ -2779,28 +4084,28 @@ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated })
|
|
|
2779
4084
|
}
|
|
2780
4085
|
|
|
2781
4086
|
// src/components/logs/LogsView.tsx
|
|
2782
|
-
import { useEffect as
|
|
2783
|
-
import { Box as
|
|
4087
|
+
import { useEffect as useEffect16 } from "react";
|
|
4088
|
+
import { Box as Box20, useInput as useInput17 } from "ink";
|
|
2784
4089
|
|
|
2785
4090
|
// src/components/logs/LogViewerBox.tsx
|
|
2786
|
-
import { useEffect as
|
|
2787
|
-
import { TitledBox as
|
|
2788
|
-
import { Box as
|
|
2789
|
-
import { ScrollView as
|
|
2790
|
-
import
|
|
4091
|
+
import { useEffect as useEffect15, useRef as useRef9, useState as useState20 } from "react";
|
|
4092
|
+
import { TitledBox as TitledBox7 } from "@mishieck/ink-titled-box";
|
|
4093
|
+
import { Box as Box18, Text as Text16, useInput as useInput15 } from "ink";
|
|
4094
|
+
import { ScrollView as ScrollView9 } from "ink-scroll-view";
|
|
4095
|
+
import Spinner4 from "ink-spinner";
|
|
2791
4096
|
import TextInput2 from "ink-text-input";
|
|
2792
|
-
import { jsx as
|
|
4097
|
+
import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
2793
4098
|
function LogViewerBox({ date, content, isActive, onRefresh, onLogCreated }) {
|
|
2794
|
-
const scrollRef =
|
|
2795
|
-
const [isInputMode, setIsInputMode] =
|
|
2796
|
-
const [inputValue, setInputValue] =
|
|
2797
|
-
const [isGeneratingStandup, setIsGeneratingStandup] =
|
|
2798
|
-
const [standupResult, setStandupResult] =
|
|
2799
|
-
const claudeProcessRef =
|
|
4099
|
+
const scrollRef = useRef9(null);
|
|
4100
|
+
const [isInputMode, setIsInputMode] = useState20(false);
|
|
4101
|
+
const [inputValue, setInputValue] = useState20("");
|
|
4102
|
+
const [isGeneratingStandup, setIsGeneratingStandup] = useState20(false);
|
|
4103
|
+
const [standupResult, setStandupResult] = useState20(null);
|
|
4104
|
+
const claudeProcessRef = useRef9(null);
|
|
2800
4105
|
const title = "[6] Log Content";
|
|
2801
4106
|
const borderColor = isActive ? "yellow" : void 0;
|
|
2802
4107
|
const displayTitle = date ? `${title} - ${date}.md` : title;
|
|
2803
|
-
|
|
4108
|
+
useInput15(
|
|
2804
4109
|
(input, key) => {
|
|
2805
4110
|
var _a, _b, _c;
|
|
2806
4111
|
if (key.escape && isInputMode) {
|
|
@@ -2862,7 +4167,7 @@ function LogViewerBox({ date, content, isActive, onRefresh, onLogCreated }) {
|
|
|
2862
4167
|
},
|
|
2863
4168
|
{ isActive }
|
|
2864
4169
|
);
|
|
2865
|
-
|
|
4170
|
+
useEffect15(() => {
|
|
2866
4171
|
return () => {
|
|
2867
4172
|
var _a;
|
|
2868
4173
|
(_a = claudeProcessRef.current) == null ? void 0 : _a.cancel();
|
|
@@ -2885,14 +4190,14 @@ ${value.trim()}
|
|
|
2885
4190
|
setIsInputMode(false);
|
|
2886
4191
|
onRefresh();
|
|
2887
4192
|
};
|
|
2888
|
-
return /* @__PURE__ */
|
|
2889
|
-
/* @__PURE__ */
|
|
2890
|
-
!date && /* @__PURE__ */
|
|
2891
|
-
date && content === null && /* @__PURE__ */
|
|
2892
|
-
date && content !== null && content.trim() === "" && /* @__PURE__ */
|
|
2893
|
-
date && content && content.trim() !== "" && /* @__PURE__ */
|
|
4193
|
+
return /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", flexGrow: 1, children: [
|
|
4194
|
+
/* @__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: [
|
|
4195
|
+
!date && /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Select a log file to view" }),
|
|
4196
|
+
date && content === null && /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Log file not found" }),
|
|
4197
|
+
date && content !== null && content.trim() === "" && /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Empty log file" }),
|
|
4198
|
+
date && content && content.trim() !== "" && /* @__PURE__ */ jsx18(Markdown, { children: content })
|
|
2894
4199
|
] }) }) }) }),
|
|
2895
|
-
isInputMode && /* @__PURE__ */
|
|
4200
|
+
isInputMode && /* @__PURE__ */ jsx18(TitledBox7, { borderStyle: "round", titles: ["Add Entry"], borderColor: "yellow", children: /* @__PURE__ */ jsx18(Box18, { paddingX: 1, children: /* @__PURE__ */ jsx18(
|
|
2896
4201
|
TextInput2,
|
|
2897
4202
|
{
|
|
2898
4203
|
value: inputValue,
|
|
@@ -2900,22 +4205,22 @@ ${value.trim()}
|
|
|
2900
4205
|
onSubmit: handleInputSubmit
|
|
2901
4206
|
}
|
|
2902
4207
|
) }) }),
|
|
2903
|
-
isGeneratingStandup && /* @__PURE__ */
|
|
2904
|
-
/* @__PURE__ */
|
|
2905
|
-
/* @__PURE__ */
|
|
4208
|
+
isGeneratingStandup && /* @__PURE__ */ jsx18(TitledBox7, { borderStyle: "round", titles: ["Standup Notes"], borderColor: "yellow", children: /* @__PURE__ */ jsxs17(Box18, { paddingX: 1, flexDirection: "column", children: [
|
|
4209
|
+
/* @__PURE__ */ jsxs17(Text16, { color: "yellow", children: [
|
|
4210
|
+
/* @__PURE__ */ jsx18(Spinner4, { type: "dots" }),
|
|
2906
4211
|
" Generating standup notes..."
|
|
2907
4212
|
] }),
|
|
2908
|
-
/* @__PURE__ */
|
|
4213
|
+
/* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Press Esc to cancel" })
|
|
2909
4214
|
] }) }),
|
|
2910
|
-
standupResult && /* @__PURE__ */
|
|
2911
|
-
|
|
4215
|
+
standupResult && /* @__PURE__ */ jsx18(
|
|
4216
|
+
TitledBox7,
|
|
2912
4217
|
{
|
|
2913
4218
|
borderStyle: "round",
|
|
2914
4219
|
titles: ["Standup Notes"],
|
|
2915
4220
|
borderColor: standupResult.type === "error" ? "red" : "green",
|
|
2916
|
-
children: /* @__PURE__ */
|
|
2917
|
-
standupResult.type === "error" ? /* @__PURE__ */
|
|
2918
|
-
/* @__PURE__ */
|
|
4221
|
+
children: /* @__PURE__ */ jsxs17(Box18, { paddingX: 1, flexDirection: "column", children: [
|
|
4222
|
+
standupResult.type === "error" ? /* @__PURE__ */ jsx18(Text16, { color: "red", children: standupResult.message }) : /* @__PURE__ */ jsx18(Markdown, { children: standupResult.message }),
|
|
4223
|
+
/* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Press Esc to dismiss" })
|
|
2919
4224
|
] })
|
|
2920
4225
|
}
|
|
2921
4226
|
)
|
|
@@ -2923,10 +4228,10 @@ ${value.trim()}
|
|
|
2923
4228
|
}
|
|
2924
4229
|
|
|
2925
4230
|
// src/components/logs/LogsHistoryBox.tsx
|
|
2926
|
-
import { TitledBox as
|
|
2927
|
-
import { Box as
|
|
2928
|
-
import { ScrollView as
|
|
2929
|
-
import { jsx as
|
|
4231
|
+
import { TitledBox as TitledBox8 } from "@mishieck/ink-titled-box";
|
|
4232
|
+
import { Box as Box19, Text as Text17, useInput as useInput16 } from "ink";
|
|
4233
|
+
import { ScrollView as ScrollView10 } from "ink-scroll-view";
|
|
4234
|
+
import { jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
2930
4235
|
function LogsHistoryBox({
|
|
2931
4236
|
logFiles,
|
|
2932
4237
|
selectedDate,
|
|
@@ -2938,7 +4243,7 @@ function LogsHistoryBox({
|
|
|
2938
4243
|
const scrollRef = useScrollToIndex(highlightedIndex);
|
|
2939
4244
|
const title = "[5] Logs";
|
|
2940
4245
|
const borderColor = isActive ? "yellow" : void 0;
|
|
2941
|
-
|
|
4246
|
+
useInput16(
|
|
2942
4247
|
(input, key) => {
|
|
2943
4248
|
if (logFiles.length === 0) return;
|
|
2944
4249
|
if (key.upArrow || input === "k") {
|
|
@@ -2956,44 +4261,44 @@ function LogsHistoryBox({
|
|
|
2956
4261
|
},
|
|
2957
4262
|
{ isActive }
|
|
2958
4263
|
);
|
|
2959
|
-
return /* @__PURE__ */
|
|
2960
|
-
logFiles.length === 0 && /* @__PURE__ */
|
|
2961
|
-
logFiles.length > 0 && /* @__PURE__ */
|
|
4264
|
+
return /* @__PURE__ */ jsx19(TitledBox8, { borderStyle: "round", titles: [title], borderColor, height: 5, children: /* @__PURE__ */ jsxs18(Box19, { flexDirection: "column", paddingX: 1, flexGrow: 1, overflow: "hidden", children: [
|
|
4265
|
+
logFiles.length === 0 && /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: "No logs yet" }),
|
|
4266
|
+
logFiles.length > 0 && /* @__PURE__ */ jsx19(ScrollView10, { ref: scrollRef, children: logFiles.map((file, idx) => {
|
|
2962
4267
|
const isHighlighted = idx === highlightedIndex;
|
|
2963
4268
|
const isSelected = file.date === selectedDate;
|
|
2964
4269
|
const cursor = isHighlighted ? ">" : " ";
|
|
2965
4270
|
const indicator = isSelected ? " *" : "";
|
|
2966
|
-
return /* @__PURE__ */
|
|
2967
|
-
/* @__PURE__ */
|
|
4271
|
+
return /* @__PURE__ */ jsxs18(Box19, { children: [
|
|
4272
|
+
/* @__PURE__ */ jsxs18(Text17, { color: isHighlighted ? "yellow" : void 0, children: [
|
|
2968
4273
|
cursor,
|
|
2969
4274
|
" "
|
|
2970
4275
|
] }),
|
|
2971
|
-
/* @__PURE__ */
|
|
2972
|
-
file.isToday && /* @__PURE__ */
|
|
2973
|
-
/* @__PURE__ */
|
|
4276
|
+
/* @__PURE__ */ jsx19(Text17, { color: file.isToday ? "green" : void 0, bold: file.isToday, children: file.date }),
|
|
4277
|
+
file.isToday && /* @__PURE__ */ jsx19(Text17, { color: "green", children: " (today)" }),
|
|
4278
|
+
/* @__PURE__ */ jsx19(Text17, { dimColor: true, children: indicator })
|
|
2974
4279
|
] }, file.date);
|
|
2975
4280
|
}) })
|
|
2976
4281
|
] }) });
|
|
2977
4282
|
}
|
|
2978
4283
|
|
|
2979
4284
|
// src/components/logs/LogsView.tsx
|
|
2980
|
-
import { jsx as
|
|
4285
|
+
import { jsx as jsx20, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
2981
4286
|
function LogsView({ isActive, refreshKey, focusedBox, onFocusedBoxChange }) {
|
|
2982
4287
|
const logs = useLogs();
|
|
2983
|
-
|
|
4288
|
+
useEffect16(() => {
|
|
2984
4289
|
if (refreshKey !== void 0 && refreshKey > 0) {
|
|
2985
4290
|
logs.handleExternalLogUpdate();
|
|
2986
4291
|
}
|
|
2987
4292
|
}, [refreshKey, logs.handleExternalLogUpdate]);
|
|
2988
|
-
|
|
4293
|
+
useInput17(
|
|
2989
4294
|
(input) => {
|
|
2990
4295
|
if (input === "5") onFocusedBoxChange("history");
|
|
2991
4296
|
if (input === "6") onFocusedBoxChange("viewer");
|
|
2992
4297
|
},
|
|
2993
4298
|
{ isActive }
|
|
2994
4299
|
);
|
|
2995
|
-
return /* @__PURE__ */
|
|
2996
|
-
/* @__PURE__ */
|
|
4300
|
+
return /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", flexGrow: 1, children: [
|
|
4301
|
+
/* @__PURE__ */ jsx20(
|
|
2997
4302
|
LogsHistoryBox,
|
|
2998
4303
|
{
|
|
2999
4304
|
logFiles: logs.logFiles,
|
|
@@ -3004,7 +4309,7 @@ function LogsView({ isActive, refreshKey, focusedBox, onFocusedBoxChange }) {
|
|
|
3004
4309
|
isActive: isActive && focusedBox === "history"
|
|
3005
4310
|
}
|
|
3006
4311
|
),
|
|
3007
|
-
/* @__PURE__ */
|
|
4312
|
+
/* @__PURE__ */ jsx20(
|
|
3008
4313
|
LogViewerBox,
|
|
3009
4314
|
{
|
|
3010
4315
|
date: logs.selectedDate,
|
|
@@ -3018,10 +4323,11 @@ function LogsView({ isActive, refreshKey, focusedBox, onFocusedBoxChange }) {
|
|
|
3018
4323
|
}
|
|
3019
4324
|
|
|
3020
4325
|
// src/components/ui/KeybindingsBar.tsx
|
|
3021
|
-
import { Box as
|
|
3022
|
-
import { jsx as
|
|
4326
|
+
import { Box as Box21, Text as Text18 } from "ink";
|
|
4327
|
+
import { jsx as jsx21, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
3023
4328
|
var globalBindings = [
|
|
3024
4329
|
{ key: "1-4", label: "Focus" },
|
|
4330
|
+
{ key: "Tab", label: "Switch Tab" },
|
|
3025
4331
|
{ key: "j/k", label: "Navigate" },
|
|
3026
4332
|
{ key: "Ctrl+C", label: "Quit" }
|
|
3027
4333
|
];
|
|
@@ -3029,18 +4335,24 @@ var modalBindings = [{ key: "Esc", label: "Cancel" }];
|
|
|
3029
4335
|
var DUCK_ASCII = "<(')___";
|
|
3030
4336
|
function KeybindingsBar({ contextBindings = [], modalOpen = false, duck }) {
|
|
3031
4337
|
const allBindings = modalOpen ? [...contextBindings, ...modalBindings] : [...contextBindings, ...globalBindings];
|
|
3032
|
-
return /* @__PURE__ */
|
|
3033
|
-
allBindings.map((binding) => /* @__PURE__ */
|
|
3034
|
-
/* @__PURE__ */
|
|
3035
|
-
/* @__PURE__ */
|
|
4338
|
+
return /* @__PURE__ */ jsxs20(Box21, { flexShrink: 0, paddingX: 1, gap: 2, children: [
|
|
4339
|
+
allBindings.map((binding) => /* @__PURE__ */ jsxs20(Box21, { gap: 1, children: [
|
|
4340
|
+
/* @__PURE__ */ jsx21(Text18, { bold: true, color: binding.color ?? "yellow", children: binding.key }),
|
|
4341
|
+
/* @__PURE__ */ jsx21(Text18, { dimColor: true, children: binding.label })
|
|
3036
4342
|
] }, binding.key)),
|
|
3037
|
-
(duck == null ? void 0 : duck.visible) && /* @__PURE__ */
|
|
3038
|
-
/* @__PURE__ */
|
|
3039
|
-
/* @__PURE__ */
|
|
4343
|
+
(duck == null ? void 0 : duck.visible) && /* @__PURE__ */ jsxs20(Box21, { flexGrow: 1, justifyContent: "flex-end", gap: 1, children: [
|
|
4344
|
+
/* @__PURE__ */ jsx21(Text18, { children: DUCK_ASCII }),
|
|
4345
|
+
/* @__PURE__ */ jsx21(Text18, { dimColor: true, children: duck.message })
|
|
3040
4346
|
] })
|
|
3041
4347
|
] });
|
|
3042
4348
|
}
|
|
3043
4349
|
|
|
4350
|
+
// src/constants/tabs.ts
|
|
4351
|
+
var COLUMN2_TABS = [
|
|
4352
|
+
{ id: "logs", label: "Logs" },
|
|
4353
|
+
{ id: "jira-browser", label: "Jira" }
|
|
4354
|
+
];
|
|
4355
|
+
|
|
3044
4356
|
// src/constants/github.ts
|
|
3045
4357
|
var GITHUB_KEYBINDINGS = {
|
|
3046
4358
|
remotes: [{ key: "Space", label: "Select Remote" }],
|
|
@@ -3058,6 +4370,27 @@ var GITHUB_KEYBINDINGS = {
|
|
|
3058
4370
|
]
|
|
3059
4371
|
};
|
|
3060
4372
|
|
|
4373
|
+
// src/constants/jira-browser.ts
|
|
4374
|
+
var JIRA_BROWSER_KEYBINDINGS = {
|
|
4375
|
+
"saved-views": [
|
|
4376
|
+
{ key: "Space", label: "Select" },
|
|
4377
|
+
{ key: "a", label: "Add View", color: "green" },
|
|
4378
|
+
{ key: "e", label: "Rename" },
|
|
4379
|
+
{ key: "d", label: "Delete", color: "red" }
|
|
4380
|
+
],
|
|
4381
|
+
browser: [
|
|
4382
|
+
{ key: "Enter", label: "Details" },
|
|
4383
|
+
{ key: "/", label: "Filter" },
|
|
4384
|
+
{ key: "u", label: "Unassigned" },
|
|
4385
|
+
{ key: "m", label: "Mine" },
|
|
4386
|
+
{ key: "x", label: "Clear Filters" },
|
|
4387
|
+
{ key: "l", label: "Load More" },
|
|
4388
|
+
{ key: "o", label: "Open", color: "green" },
|
|
4389
|
+
{ key: "y", label: "Copy Link" },
|
|
4390
|
+
{ key: "r", label: "Refresh" }
|
|
4391
|
+
]
|
|
4392
|
+
};
|
|
4393
|
+
|
|
3061
4394
|
// src/constants/jira.ts
|
|
3062
4395
|
var JIRA_KEYBINDINGS = {
|
|
3063
4396
|
not_configured: [{ key: "c", label: "Configure Jira" }],
|
|
@@ -3097,34 +4430,42 @@ function computeKeybindings(focusedView, state) {
|
|
|
3097
4430
|
return JIRA_KEYBINDINGS[state.jira.jiraState];
|
|
3098
4431
|
case "logs":
|
|
3099
4432
|
return LOGS_KEYBINDINGS[state.logs.focusedBox];
|
|
4433
|
+
case "jira-browser":
|
|
4434
|
+
if (state["jira-browser"].modalOpen) return [];
|
|
4435
|
+
return JIRA_BROWSER_KEYBINDINGS[state["jira-browser"].focusedBox];
|
|
3100
4436
|
default:
|
|
3101
4437
|
return [];
|
|
3102
4438
|
}
|
|
3103
4439
|
}
|
|
3104
4440
|
|
|
3105
4441
|
// src/app.tsx
|
|
3106
|
-
import { jsx as
|
|
4442
|
+
import { jsx as jsx22, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
3107
4443
|
function App() {
|
|
3108
4444
|
const { exit } = useApp();
|
|
3109
|
-
const [focusedView, setFocusedView] =
|
|
3110
|
-
const [modalOpen, setModalOpen] =
|
|
3111
|
-
const [logRefreshKey, setLogRefreshKey] =
|
|
4445
|
+
const [focusedView, setFocusedView] = useState21("github");
|
|
4446
|
+
const [modalOpen, setModalOpen] = useState21(false);
|
|
4447
|
+
const [logRefreshKey, setLogRefreshKey] = useState21(0);
|
|
4448
|
+
const [activeTab, setActiveTab] = useState21("logs");
|
|
3112
4449
|
const duck = useRubberDuck();
|
|
3113
|
-
const [githubFocusedBox, setGithubFocusedBox] =
|
|
3114
|
-
const [jiraState, setJiraState] =
|
|
3115
|
-
const [logsFocusedBox, setLogsFocusedBox] =
|
|
3116
|
-
const
|
|
4450
|
+
const [githubFocusedBox, setGithubFocusedBox] = useState21("remotes");
|
|
4451
|
+
const [jiraState, setJiraState] = useState21("not_configured");
|
|
4452
|
+
const [logsFocusedBox, setLogsFocusedBox] = useState21("history");
|
|
4453
|
+
const [jiraBrowserFocusedBox, setJiraBrowserFocusedBox] = useState21("saved-views");
|
|
4454
|
+
const [jiraBrowserModalOpen, setJiraBrowserModalOpen] = useState21(false);
|
|
4455
|
+
const keybindings = useMemo4(
|
|
3117
4456
|
() => computeKeybindings(focusedView, {
|
|
3118
4457
|
github: { focusedBox: githubFocusedBox },
|
|
3119
4458
|
jira: { jiraState, modalOpen },
|
|
3120
|
-
logs: { focusedBox: logsFocusedBox }
|
|
4459
|
+
logs: { focusedBox: logsFocusedBox },
|
|
4460
|
+
"jira-browser": { focusedBox: jiraBrowserFocusedBox, modalOpen: jiraBrowserModalOpen }
|
|
3121
4461
|
}),
|
|
3122
|
-
[focusedView, githubFocusedBox, jiraState, modalOpen, logsFocusedBox]
|
|
4462
|
+
[focusedView, githubFocusedBox, jiraState, modalOpen, logsFocusedBox, jiraBrowserFocusedBox, jiraBrowserModalOpen]
|
|
3123
4463
|
);
|
|
3124
|
-
const handleLogUpdated =
|
|
4464
|
+
const handleLogUpdated = useCallback11(() => {
|
|
3125
4465
|
setLogRefreshKey((prev) => prev + 1);
|
|
3126
4466
|
}, []);
|
|
3127
|
-
|
|
4467
|
+
const anyModalOpen = modalOpen || jiraBrowserModalOpen;
|
|
4468
|
+
useInput18(
|
|
3128
4469
|
(input, key) => {
|
|
3129
4470
|
if (key.ctrl && input === "c") {
|
|
3130
4471
|
exit();
|
|
@@ -3136,12 +4477,23 @@ function App() {
|
|
|
3136
4477
|
setFocusedView("jira");
|
|
3137
4478
|
}
|
|
3138
4479
|
if (input === "5") {
|
|
3139
|
-
setFocusedView(
|
|
3140
|
-
setLogsFocusedBox("history");
|
|
4480
|
+
setFocusedView(activeTab);
|
|
4481
|
+
if (activeTab === "logs") setLogsFocusedBox("history");
|
|
4482
|
+
if (activeTab === "jira-browser") setJiraBrowserFocusedBox("saved-views");
|
|
3141
4483
|
}
|
|
3142
4484
|
if (input === "6") {
|
|
3143
|
-
setFocusedView(
|
|
3144
|
-
setLogsFocusedBox("viewer");
|
|
4485
|
+
setFocusedView(activeTab);
|
|
4486
|
+
if (activeTab === "logs") setLogsFocusedBox("viewer");
|
|
4487
|
+
if (activeTab === "jira-browser") setJiraBrowserFocusedBox("browser");
|
|
4488
|
+
}
|
|
4489
|
+
if (key.tab) {
|
|
4490
|
+
setActiveTab((current) => {
|
|
4491
|
+
const idx = COLUMN2_TABS.findIndex((t) => t.id === current);
|
|
4492
|
+
const next = (idx + 1) % COLUMN2_TABS.length;
|
|
4493
|
+
const nextTab = COLUMN2_TABS[next];
|
|
4494
|
+
setFocusedView(nextTab.id);
|
|
4495
|
+
return nextTab.id;
|
|
4496
|
+
});
|
|
3145
4497
|
}
|
|
3146
4498
|
if (input === "d") {
|
|
3147
4499
|
duck.toggleDuck();
|
|
@@ -3150,12 +4502,19 @@ function App() {
|
|
|
3150
4502
|
duck.quack();
|
|
3151
4503
|
}
|
|
3152
4504
|
},
|
|
3153
|
-
{ isActive: !
|
|
4505
|
+
{ isActive: !anyModalOpen }
|
|
3154
4506
|
);
|
|
3155
|
-
return /* @__PURE__ */
|
|
3156
|
-
/* @__PURE__ */
|
|
3157
|
-
/* @__PURE__ */
|
|
3158
|
-
|
|
4507
|
+
return /* @__PURE__ */ jsxs21(Box22, { flexGrow: 1, flexDirection: "column", overflow: "hidden", children: [
|
|
4508
|
+
/* @__PURE__ */ jsxs21(Box22, { height: 1, flexDirection: "row", columnGap: 1, children: [
|
|
4509
|
+
/* @__PURE__ */ jsx22(Box22, { flexGrow: 1, paddingX: 1, flexBasis: 0, children: /* @__PURE__ */ jsx22(Text19, { color: "gray", children: "Current branch" }) }),
|
|
4510
|
+
/* @__PURE__ */ jsxs21(Box22, { flexGrow: 1, gap: 1, flexBasis: 0, children: [
|
|
4511
|
+
/* @__PURE__ */ jsx22(Text19, { color: "gray", children: "Dashboards" }),
|
|
4512
|
+
COLUMN2_TABS.map((tab) => /* @__PURE__ */ jsx22(Text19, { bold: true, dimColor: activeTab !== tab.id, children: tab.label }, tab.id))
|
|
4513
|
+
] })
|
|
4514
|
+
] }),
|
|
4515
|
+
/* @__PURE__ */ jsxs21(Box22, { flexGrow: 1, flexDirection: "row", columnGap: 1, children: [
|
|
4516
|
+
/* @__PURE__ */ jsxs21(Box22, { flexDirection: "column", flexGrow: 1, flexBasis: 0, children: [
|
|
4517
|
+
/* @__PURE__ */ jsx22(
|
|
3159
4518
|
GitHubView,
|
|
3160
4519
|
{
|
|
3161
4520
|
isActive: focusedView === "github",
|
|
@@ -3163,7 +4522,7 @@ function App() {
|
|
|
3163
4522
|
onLogUpdated: handleLogUpdated
|
|
3164
4523
|
}
|
|
3165
4524
|
),
|
|
3166
|
-
/* @__PURE__ */
|
|
4525
|
+
/* @__PURE__ */ jsx22(
|
|
3167
4526
|
JiraView,
|
|
3168
4527
|
{
|
|
3169
4528
|
isActive: focusedView === "jira",
|
|
@@ -3173,21 +4532,33 @@ function App() {
|
|
|
3173
4532
|
}
|
|
3174
4533
|
)
|
|
3175
4534
|
] }),
|
|
3176
|
-
/* @__PURE__ */
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
4535
|
+
/* @__PURE__ */ jsxs21(Box22, { flexDirection: "column", flexGrow: 1, flexBasis: 0, children: [
|
|
4536
|
+
activeTab === "logs" && /* @__PURE__ */ jsx22(
|
|
4537
|
+
LogsView,
|
|
4538
|
+
{
|
|
4539
|
+
isActive: focusedView === "logs",
|
|
4540
|
+
refreshKey: logRefreshKey,
|
|
4541
|
+
focusedBox: logsFocusedBox,
|
|
4542
|
+
onFocusedBoxChange: setLogsFocusedBox
|
|
4543
|
+
}
|
|
4544
|
+
),
|
|
4545
|
+
activeTab === "jira-browser" && /* @__PURE__ */ jsx22(
|
|
4546
|
+
JiraBrowserView,
|
|
4547
|
+
{
|
|
4548
|
+
isActive: focusedView === "jira-browser",
|
|
4549
|
+
focusedBox: jiraBrowserFocusedBox,
|
|
4550
|
+
onFocusedBoxChange: setJiraBrowserFocusedBox,
|
|
4551
|
+
onModalChange: setJiraBrowserModalOpen,
|
|
4552
|
+
onLogUpdated: handleLogUpdated
|
|
4553
|
+
}
|
|
4554
|
+
)
|
|
4555
|
+
] })
|
|
3185
4556
|
] }),
|
|
3186
|
-
/* @__PURE__ */
|
|
4557
|
+
/* @__PURE__ */ jsx22(
|
|
3187
4558
|
KeybindingsBar,
|
|
3188
4559
|
{
|
|
3189
4560
|
contextBindings: keybindings,
|
|
3190
|
-
modalOpen,
|
|
4561
|
+
modalOpen: anyModalOpen,
|
|
3191
4562
|
duck: { visible: duck.visible, message: duck.message }
|
|
3192
4563
|
}
|
|
3193
4564
|
)
|
|
@@ -3198,31 +4569,31 @@ function App() {
|
|
|
3198
4569
|
import { render as inkRender } from "ink";
|
|
3199
4570
|
|
|
3200
4571
|
// src/lib/Screen.tsx
|
|
3201
|
-
import { useCallback as
|
|
3202
|
-
import { Box as
|
|
3203
|
-
import { jsx as
|
|
4572
|
+
import { useCallback as useCallback12, useEffect as useEffect17, useState as useState22 } from "react";
|
|
4573
|
+
import { Box as Box23, useStdout as useStdout2 } from "ink";
|
|
4574
|
+
import { jsx as jsx23 } from "react/jsx-runtime";
|
|
3204
4575
|
function Screen({ children }) {
|
|
3205
4576
|
const { stdout } = useStdout2();
|
|
3206
|
-
const getSize =
|
|
3207
|
-
const [size, setSize] =
|
|
3208
|
-
|
|
4577
|
+
const getSize = useCallback12(() => ({ height: stdout.rows, width: stdout.columns }), [stdout]);
|
|
4578
|
+
const [size, setSize] = useState22(getSize);
|
|
4579
|
+
useEffect17(() => {
|
|
3209
4580
|
const onResize = () => setSize(getSize());
|
|
3210
4581
|
stdout.on("resize", onResize);
|
|
3211
4582
|
return () => {
|
|
3212
4583
|
stdout.off("resize", onResize);
|
|
3213
4584
|
};
|
|
3214
4585
|
}, [stdout, getSize]);
|
|
3215
|
-
return /* @__PURE__ */
|
|
4586
|
+
return /* @__PURE__ */ jsx23(Box23, { height: size.height, width: size.width, children });
|
|
3216
4587
|
}
|
|
3217
4588
|
|
|
3218
4589
|
// src/lib/render.tsx
|
|
3219
|
-
import { jsx as
|
|
4590
|
+
import { jsx as jsx24 } from "react/jsx-runtime";
|
|
3220
4591
|
var ENTER_ALT_BUFFER = "\x1B[?1049h";
|
|
3221
4592
|
var EXIT_ALT_BUFFER = "\x1B[?1049l";
|
|
3222
4593
|
var CLEAR_SCREEN = "\x1B[2J\x1B[H";
|
|
3223
4594
|
function render(node, options) {
|
|
3224
4595
|
process.stdout.write(ENTER_ALT_BUFFER + CLEAR_SCREEN);
|
|
3225
|
-
const element = /* @__PURE__ */
|
|
4596
|
+
const element = /* @__PURE__ */ jsx24(Screen, { children: node });
|
|
3226
4597
|
const instance = inkRender(element, options);
|
|
3227
4598
|
setImmediate(() => instance.rerender(element));
|
|
3228
4599
|
const cleanup = () => process.stdout.write(EXIT_ALT_BUFFER);
|
|
@@ -3243,7 +4614,7 @@ function render(node, options) {
|
|
|
3243
4614
|
}
|
|
3244
4615
|
|
|
3245
4616
|
// src/cli.tsx
|
|
3246
|
-
import { jsx as
|
|
4617
|
+
import { jsx as jsx25 } from "react/jsx-runtime";
|
|
3247
4618
|
var cli = meow(
|
|
3248
4619
|
`
|
|
3249
4620
|
Usage
|
|
@@ -3276,4 +4647,4 @@ if (cli.flags.cwd) {
|
|
|
3276
4647
|
process.exit(1);
|
|
3277
4648
|
}
|
|
3278
4649
|
}
|
|
3279
|
-
render(/* @__PURE__ */
|
|
4650
|
+
render(/* @__PURE__ */ jsx25(App, {}));
|