@kud/jira-ink 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +30 -13
- package/dist/index.js +249 -200
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ type IssueDetail = {
|
|
|
19
19
|
assignee: string;
|
|
20
20
|
reporter: string;
|
|
21
21
|
labels: string[];
|
|
22
|
+
/** Jira's priority name, verbatim; absent when the field is off. */
|
|
23
|
+
priority?: string;
|
|
22
24
|
parent?: {
|
|
23
25
|
key: string;
|
|
24
26
|
summary: string;
|
|
@@ -79,6 +81,12 @@ type IssueBodyProps = {
|
|
|
79
81
|
/** Instance URL, as `loadConfig` resolves it; only used to build the browser link. */
|
|
80
82
|
baseUrl: string;
|
|
81
83
|
issueKey: string;
|
|
84
|
+
/**
|
|
85
|
+
* The summary as the host already knows it — from the list row that was
|
|
86
|
+
* opened — so the frame carries the title from the first frame rather than
|
|
87
|
+
* only once the fetch answers. The fetched summary wins once it is here.
|
|
88
|
+
*/
|
|
89
|
+
summary?: string;
|
|
82
90
|
onExit: () => void;
|
|
83
91
|
/** Override the terminal-derived size when the host draws inside a frame. */
|
|
84
92
|
width?: number;
|
|
@@ -97,7 +105,7 @@ type IssueBodyProps = {
|
|
|
97
105
|
* `IssueDetailView` for jira-cli's own TUI, which owns the prompts they need;
|
|
98
106
|
* here they are simply not wired, so the hint row does not offer them.
|
|
99
107
|
*/
|
|
100
|
-
declare const IssueBody: ({ client, baseUrl, issueKey, onExit, width, height, frame, theme, }: IssueBodyProps) => string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | react.ReactPortal | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | react.JSX.Element | null | undefined;
|
|
108
|
+
declare const IssueBody: ({ client, baseUrl, issueKey, summary, onExit, width, height, frame, theme, }: IssueBodyProps) => string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | react.ReactPortal | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | react.JSX.Element | null | undefined;
|
|
101
109
|
|
|
102
110
|
/** Jira's own three, plus `unknown` for a status that arrives without one. */
|
|
103
111
|
type StatusCategory = "new" | "indeterminate" | "done" | "unknown";
|
|
@@ -183,13 +191,25 @@ type Block = {
|
|
|
183
191
|
declare const blocksFor: (rows: BoardRow[]) => Block[];
|
|
184
192
|
/** Index into `blocks` of the n-th issue, so a cursor over issues maps to a line. */
|
|
185
193
|
declare const blockIndexOfIssue: (blocks: Block[], issueIndex: number) => number;
|
|
194
|
+
type PriorityGlyph = "⇈" | "↑" | "=" | "↓" | "⇊" | " ";
|
|
186
195
|
/**
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
196
|
+
* Rank against the scheme's default rung, in Jira's own arrow grammar: two
|
|
197
|
+
* rungs above draws `⇈`, one `↑`, the default `=`, one below `↓`, two `⇊`.
|
|
198
|
+
*
|
|
199
|
+
* Five glyphs, not three, and the normal rung gets one. The column used to
|
|
200
|
+
* mark the two ends only (`▲` / `▼`, nothing for normal), which on a four-rung
|
|
201
|
+
* scheme — P1 to P4, the common shape — put a mark on half the rows, drew P2
|
|
202
|
+
* as if it were normal, and made P3 and P4 identical. And a blank beside a
|
|
203
|
+
* marked row read as MISSING rather than as normal: the absent-vs-zero
|
|
204
|
+
* distinction, one glyph at a time. So `=` says "we looked, it is normal",
|
|
205
|
+
* and the blank finally means only "no priority, or one we cannot place".
|
|
206
|
+
*
|
|
207
|
+
* Names are per-instance, so this matches the default scheme, the common
|
|
208
|
+
* renames and the P0–P5 ladder rather than an exact list. Deriving rank from
|
|
209
|
+
* the instance's ordered priority list, with its default flag, is the honest
|
|
210
|
+
* version of this and where it goes next; the names are the fallback.
|
|
191
211
|
*/
|
|
192
|
-
declare const priorityGlyph: (name: string | null) =>
|
|
212
|
+
declare const priorityGlyph: (name: string | null) => PriorityGlyph;
|
|
193
213
|
declare const relativeAge: (iso: string, now?: number) => string;
|
|
194
214
|
declare const pillVariantFor: (row: Pick<BoardRow, "type" | "container">) => "group" | "info" | "error" | "muted";
|
|
195
215
|
type BoardModel = {
|
|
@@ -281,18 +301,15 @@ declare const IssueBoard: ({ model, viewer, loadedAt, scope, showingAll, searchE
|
|
|
281
301
|
|
|
282
302
|
type PriorityMarker = {
|
|
283
303
|
/** Always one column wide, so a list filling every row keeps its grid. */
|
|
284
|
-
marker:
|
|
304
|
+
marker: PriorityGlyph;
|
|
285
305
|
/** Absent for the blank: nothing to paint. */
|
|
286
306
|
color?: string;
|
|
287
307
|
};
|
|
288
308
|
/**
|
|
289
309
|
* The priority cell as the board draws it — glyph and colour together, so a
|
|
290
310
|
* host filling gh-ink's `marker` / `markerColor` pair and the board's own row
|
|
291
|
-
* cannot disagree on a hue.
|
|
292
|
-
*
|
|
293
|
-
* do not merge at row-scan speed, and a warm one on the high end so it clears
|
|
294
|
-
* the row text. The hex-vs-token question was settled with the pill map: one
|
|
295
|
-
* site, here, never a host-side table that drifts.
|
|
311
|
+
* cannot disagree on a hue. One site, never a host-side table that drifts;
|
|
312
|
+
* see `priorityGlyph` for what the glyphs say.
|
|
296
313
|
*/
|
|
297
314
|
declare const priorityMarker: (name: string | null) => PriorityMarker;
|
|
298
315
|
|
|
@@ -305,4 +322,4 @@ declare const priorityMarker: (name: string | null) => PriorityMarker;
|
|
|
305
322
|
declare const MOCK_BOARD_ROWS: BoardRow[];
|
|
306
323
|
declare const mockBoard: () => BoardModel;
|
|
307
324
|
|
|
308
|
-
export { type Block, type BoardModel, type BoardOptions, type BoardRow, type BoardScope, type BoardTab, type BoardTabs, IssueBoard, type IssueBoardProps, IssueBody, type IssueBodyProps, type IssueDetail, IssueDetailView, type IssueDetailViewProps, MOCK_BOARD_ROWS, OFF_BOARD, type PriorityMarker, type StatusCategory, type TabSource, type Transition, blockIndexOfIssue, blocksFor, boardOf, countsFor, issueDetailOf, mockBoard, pillVariantFor, priorityGlyph, priorityMarker, relativeAge, tabOf, tabsFromBoard, tabsFromCategories, tabsFromConfig, toBoardRow, transitionsOf, visibleTabs };
|
|
325
|
+
export { type Block, type BoardModel, type BoardOptions, type BoardRow, type BoardScope, type BoardTab, type BoardTabs, IssueBoard, type IssueBoardProps, IssueBody, type IssueBodyProps, type IssueDetail, IssueDetailView, type IssueDetailViewProps, MOCK_BOARD_ROWS, OFF_BOARD, type PriorityGlyph, type PriorityMarker, type StatusCategory, type TabSource, type Transition, blockIndexOfIssue, blocksFor, boardOf, countsFor, issueDetailOf, mockBoard, pillVariantFor, priorityGlyph, priorityMarker, relativeAge, tabOf, tabsFromBoard, tabsFromCategories, tabsFromConfig, toBoardRow, transitionsOf, visibleTabs };
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,191 @@
|
|
|
1
1
|
import { MarkdownViewport } from '@kud/ink-markdown';
|
|
2
|
-
import {
|
|
2
|
+
import { colors, KeyValue, Badge, FooterHints, StatusMessage, useTabs, useListCursor, pillWidth, TextInput, Tabs, SelectableRow, Pill, LoadingScreen } from '@kud/ink-ui';
|
|
3
3
|
import { useInput, Box, Text, useStdout } from 'ink';
|
|
4
4
|
import { useState, useEffect, useMemo } from 'react';
|
|
5
|
+
import { isContainerType, locateAttachments, adfToMarkdown, looksLikeJql } from '@kud/jira';
|
|
5
6
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
6
7
|
import { exec } from 'child_process';
|
|
7
|
-
import { locateAttachments, adfToMarkdown, isContainerType, looksLikeJql } from '@kud/jira';
|
|
8
8
|
|
|
9
9
|
// src/components/issue-detail-view.tsx
|
|
10
|
+
var CATEGORIES = ["new", "indeterminate", "done"];
|
|
11
|
+
var categoryOf = (key) => CATEGORIES.find((c) => c === key) ?? "unknown";
|
|
12
|
+
var toBoardRow = (issue) => ({
|
|
13
|
+
key: issue.key,
|
|
14
|
+
summary: issue.fields.summary ?? "",
|
|
15
|
+
status: issue.fields.status?.name ?? "\u2014",
|
|
16
|
+
statusId: issue.fields.status?.id ?? "",
|
|
17
|
+
category: categoryOf(issue.fields.status?.statusCategory?.key),
|
|
18
|
+
type: issue.fields.issuetype?.name ?? "",
|
|
19
|
+
container: isContainerType(issue.fields.issuetype),
|
|
20
|
+
priority: issue.fields.priority?.name ?? null,
|
|
21
|
+
...issue.fields.parent ? {
|
|
22
|
+
parent: {
|
|
23
|
+
key: issue.fields.parent.key,
|
|
24
|
+
summary: issue.fields.parent.fields?.summary ?? ""
|
|
25
|
+
}
|
|
26
|
+
} : {},
|
|
27
|
+
assignee: issue.fields.assignee?.displayName ?? "unassigned",
|
|
28
|
+
updated: issue.fields.updated ?? ""
|
|
29
|
+
});
|
|
30
|
+
var OFF_BOARD = "__off_board__";
|
|
31
|
+
var CATEGORY_TABS = [
|
|
32
|
+
{ value: "todo", label: "To do", category: "new" },
|
|
33
|
+
{ value: "doing", label: "In progress", category: "indeterminate" },
|
|
34
|
+
{ value: "done", label: "Done", category: "done" }
|
|
35
|
+
];
|
|
36
|
+
var withOffBoard = (tabs) => [
|
|
37
|
+
...tabs,
|
|
38
|
+
{ value: OFF_BOARD, label: "Off board", offBoard: true }
|
|
39
|
+
];
|
|
40
|
+
var tabsFromConfig = (configured) => ({
|
|
41
|
+
source: "config",
|
|
42
|
+
tabs: withOffBoard(
|
|
43
|
+
configured.map((t, i) => ({
|
|
44
|
+
value: `config:${i}`,
|
|
45
|
+
label: t.label,
|
|
46
|
+
statuses: new Set(t.statuses.map((s) => s.toLowerCase()))
|
|
47
|
+
}))
|
|
48
|
+
)
|
|
49
|
+
});
|
|
50
|
+
var tabsFromBoard = (configuration) => {
|
|
51
|
+
const columns = configuration.columnConfig?.columns ?? [];
|
|
52
|
+
if (columns.length === 0) return null;
|
|
53
|
+
return {
|
|
54
|
+
source: "board",
|
|
55
|
+
tabs: withOffBoard(
|
|
56
|
+
columns.map((c, i) => ({
|
|
57
|
+
value: `column:${i}`,
|
|
58
|
+
label: c.name,
|
|
59
|
+
statuses: new Set(c.statuses.map((s) => s.id))
|
|
60
|
+
}))
|
|
61
|
+
)
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
var tabsFromCategories = () => ({
|
|
65
|
+
source: "categories",
|
|
66
|
+
tabs: CATEGORY_TABS
|
|
67
|
+
});
|
|
68
|
+
var tabOf = (row, { tabs }) => {
|
|
69
|
+
const claimed = tabs.find(
|
|
70
|
+
(t) => t.statuses?.has(row.statusId) || t.statuses?.has(row.status.toLowerCase())
|
|
71
|
+
);
|
|
72
|
+
if (claimed) return claimed.value;
|
|
73
|
+
const byCategory = tabs.find((t) => t.category && t.category === row.category);
|
|
74
|
+
if (byCategory) return byCategory.value;
|
|
75
|
+
return tabs.find((t) => t.offBoard)?.value ?? "doing";
|
|
76
|
+
};
|
|
77
|
+
var countsFor = (rows, tabs) => {
|
|
78
|
+
const counts = Object.fromEntries(
|
|
79
|
+
tabs.tabs.map((t) => [t.value, 0])
|
|
80
|
+
);
|
|
81
|
+
for (const row of rows)
|
|
82
|
+
counts[tabOf(row, tabs)] = (counts[tabOf(row, tabs)] ?? 0) + 1;
|
|
83
|
+
return counts;
|
|
84
|
+
};
|
|
85
|
+
var visibleTabs = (tabs, counts) => tabs.tabs.filter((t) => !t.offBoard || (counts[t.value] ?? 0) > 0);
|
|
86
|
+
var blocksFor = (rows) => {
|
|
87
|
+
const hasStructure = rows.some((r) => r.parent || r.container);
|
|
88
|
+
if (!hasStructure)
|
|
89
|
+
return rows.map((row) => ({ kind: "issue", row, depth: 0 }));
|
|
90
|
+
const heads = new Map(rows.filter((r) => r.container).map((r) => [r.key, r]));
|
|
91
|
+
const children = /* @__PURE__ */ new Map();
|
|
92
|
+
const orphans = [];
|
|
93
|
+
for (const row of rows) {
|
|
94
|
+
if (row.container) continue;
|
|
95
|
+
const key = row.parent?.key;
|
|
96
|
+
if (!key) {
|
|
97
|
+
orphans.push(row);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
children.set(key, [...children.get(key) ?? [], row]);
|
|
101
|
+
}
|
|
102
|
+
const groups = [];
|
|
103
|
+
for (const [key, head] of heads)
|
|
104
|
+
groups.push([
|
|
105
|
+
{ kind: "issue", row: head, depth: 0 },
|
|
106
|
+
...(children.get(key) ?? []).map((row) => ({
|
|
107
|
+
kind: "issue",
|
|
108
|
+
row,
|
|
109
|
+
depth: 1
|
|
110
|
+
}))
|
|
111
|
+
]);
|
|
112
|
+
for (const [key, kids] of children) {
|
|
113
|
+
if (heads.has(key)) continue;
|
|
114
|
+
groups.push([
|
|
115
|
+
{ kind: "fence", key, summary: kids[0].parent?.summary ?? "" },
|
|
116
|
+
...kids.map((row) => ({ kind: "issue", row, depth: 1 }))
|
|
117
|
+
]);
|
|
118
|
+
}
|
|
119
|
+
if (orphans.length)
|
|
120
|
+
groups.push([
|
|
121
|
+
...groups.length ? [{ kind: "fence", key: null, summary: "No epic" }] : [],
|
|
122
|
+
...orphans.map((row) => ({ kind: "issue", row, depth: 0 }))
|
|
123
|
+
]);
|
|
124
|
+
return groups.flatMap(
|
|
125
|
+
(group, i) => i === 0 ? group : [{ kind: "gap" }, ...group]
|
|
126
|
+
);
|
|
127
|
+
};
|
|
128
|
+
var blockIndexOfIssue = (blocks, issueIndex) => {
|
|
129
|
+
let seen = -1;
|
|
130
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
131
|
+
if (blocks[i].kind === "issue" && ++seen === issueIndex) return i;
|
|
132
|
+
}
|
|
133
|
+
return 0;
|
|
134
|
+
};
|
|
135
|
+
var priorityGlyph = (name) => {
|
|
136
|
+
const n = (name ?? "").trim().toLowerCase();
|
|
137
|
+
if (/^(highest|blocker|critical|p[01])$/.test(n)) return "\u21C8";
|
|
138
|
+
if (/^(high|urgent|major|p2)$/.test(n)) return "\u2191";
|
|
139
|
+
if (/^(medium|normal|p3)$/.test(n)) return "=";
|
|
140
|
+
if (/^(low|minor|p4)$/.test(n)) return "\u2193";
|
|
141
|
+
if (/^(lowest|trivial|p5)$/.test(n)) return "\u21CA";
|
|
142
|
+
return " ";
|
|
143
|
+
};
|
|
144
|
+
var UNITS = [
|
|
145
|
+
[60, "m"],
|
|
146
|
+
[60, "h"],
|
|
147
|
+
[24, "d"],
|
|
148
|
+
[7, "w"]
|
|
149
|
+
];
|
|
150
|
+
var relativeAge = (iso, now = Date.now()) => {
|
|
151
|
+
const then = Date.parse(iso);
|
|
152
|
+
if (Number.isNaN(then)) return "";
|
|
153
|
+
let value = Math.max(0, Math.round((now - then) / 1e3));
|
|
154
|
+
let unit = "s";
|
|
155
|
+
for (const [size, next] of UNITS) {
|
|
156
|
+
if (value < size) break;
|
|
157
|
+
value = Math.round(value / size);
|
|
158
|
+
unit = next;
|
|
159
|
+
}
|
|
160
|
+
return `${value}${unit}`;
|
|
161
|
+
};
|
|
162
|
+
var pillVariantFor = (row) => {
|
|
163
|
+
if (row.container) return "group";
|
|
164
|
+
const t = row.type.toLowerCase();
|
|
165
|
+
if (t === "bug") return "error";
|
|
166
|
+
if (t === "story") return "info";
|
|
167
|
+
return "muted";
|
|
168
|
+
};
|
|
169
|
+
var boardOf = async (client, jql, { tabs, board, limit = 200 } = {}) => {
|
|
170
|
+
const [issues, derived] = await Promise.all([
|
|
171
|
+
client.searchIssues(jql, { limit }),
|
|
172
|
+
tabs?.length ? Promise.resolve(tabsFromConfig(tabs)) : board !== void 0 ? client.getBoardConfiguration(board).then(tabsFromBoard) : Promise.resolve(null)
|
|
173
|
+
]);
|
|
174
|
+
return { rows: issues.map(toBoardRow), tabs: derived ?? tabsFromCategories() };
|
|
175
|
+
};
|
|
176
|
+
var COLOUR = {
|
|
177
|
+
"\u21C8": colors.warning,
|
|
178
|
+
"\u2191": colors.warning,
|
|
179
|
+
"=": colors.muted,
|
|
180
|
+
"\u2193": colors.info,
|
|
181
|
+
"\u21CA": colors.info,
|
|
182
|
+
" ": void 0
|
|
183
|
+
};
|
|
184
|
+
var priorityMarker = (name) => {
|
|
185
|
+
const marker = priorityGlyph(name);
|
|
186
|
+
const color = COLOUR[marker];
|
|
187
|
+
return color ? { marker, color } : { marker };
|
|
188
|
+
};
|
|
10
189
|
var truncate = (s, max) => s.length <= max ? s : `${s.slice(0, Math.max(0, max - 1))}\u2026`;
|
|
11
190
|
var originOf = (a) => a.origins.map(
|
|
12
191
|
(o) => o.kind === "comment" ? `comment by ${o.author ?? "unknown"}` : o.kind === "description" ? "description" : "issue"
|
|
@@ -20,14 +199,52 @@ var humanSize = (bytes) => {
|
|
|
20
199
|
const value = bytes / 1024 ** e;
|
|
21
200
|
return `${e === 0 ? value : value.toFixed(1)}${units[e]}`;
|
|
22
201
|
};
|
|
23
|
-
var
|
|
202
|
+
var ago = (iso) => {
|
|
203
|
+
const rel = relativeAge(iso);
|
|
204
|
+
return rel ? `${rel} ago` : iso;
|
|
205
|
+
};
|
|
206
|
+
var documentOf = (issue) => {
|
|
207
|
+
const parts = [issue.description || "_No description._"];
|
|
208
|
+
if (issue.comments.length > 0)
|
|
209
|
+
parts.push(
|
|
210
|
+
`## Comments (${issue.comments.length})`,
|
|
211
|
+
...issue.comments.map(
|
|
212
|
+
(c) => `**${c.author}** \xB7 ${ago(c.created)}
|
|
24
213
|
|
|
25
|
-
${c.body}`
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
214
|
+
${c.body}`
|
|
215
|
+
)
|
|
216
|
+
);
|
|
217
|
+
if (issue.attachments.length > 0)
|
|
218
|
+
parts.push(
|
|
219
|
+
`## Attachments (${issue.attachments.length})`,
|
|
220
|
+
issue.attachments.map(
|
|
221
|
+
(a) => `- \`${a.filename}\` \u2014 ${a.mimeType}, ${humanSize(a.size)} \xB7 from ${originOf(a)}
|
|
29
222
|
\`jira attachment read ${issue.key} ${a.filename}\``
|
|
30
|
-
).join("\n")
|
|
223
|
+
).join("\n")
|
|
224
|
+
);
|
|
225
|
+
return parts.join("\n\n");
|
|
226
|
+
};
|
|
227
|
+
var FactsLine = ({ issue }) => {
|
|
228
|
+
const priority = priorityMarker(issue.priority ?? null);
|
|
229
|
+
const lead = priority.marker !== " " && priority.marker !== "=" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
230
|
+
/* @__PURE__ */ jsx(Text, { color: priority.color, children: priority.marker }),
|
|
231
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
232
|
+
" ",
|
|
233
|
+
issue.priority
|
|
234
|
+
] })
|
|
235
|
+
] }) : null;
|
|
236
|
+
const plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
237
|
+
const facts = [
|
|
238
|
+
`by ${issue.reporter}`,
|
|
239
|
+
...issue.labels.length > 0 ? [`labels ${issue.labels.join(", ")}`] : [],
|
|
240
|
+
plural(issue.comments.length, "comment"),
|
|
241
|
+
...issue.attachments.length > 0 ? [plural(issue.attachments.length, "attachment")] : []
|
|
242
|
+
];
|
|
243
|
+
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
244
|
+
lead,
|
|
245
|
+
/* @__PURE__ */ jsx(Text, { color: colors.muted, children: (lead ? " \xB7 " : "") + facts.join(" \xB7 ") })
|
|
246
|
+
] });
|
|
247
|
+
};
|
|
31
248
|
var IssueDetailView = ({
|
|
32
249
|
issue,
|
|
33
250
|
height,
|
|
@@ -40,16 +257,6 @@ var IssueDetailView = ({
|
|
|
40
257
|
frame,
|
|
41
258
|
theme
|
|
42
259
|
}) => {
|
|
43
|
-
const tabs = [
|
|
44
|
-
{ value: "description", label: "Description" },
|
|
45
|
-
{ value: "comments", label: "Comments", count: issue.comments.length },
|
|
46
|
-
{
|
|
47
|
-
value: "attachments",
|
|
48
|
-
label: "Attachments",
|
|
49
|
-
count: issue.attachments.length
|
|
50
|
-
}
|
|
51
|
-
];
|
|
52
|
-
const { active } = useTabs(tabs, { initial: "description" });
|
|
53
260
|
const [scroll, setScroll] = useState(0);
|
|
54
261
|
useInput((input, key) => {
|
|
55
262
|
if (input === "t" && onTransition) return onTransition();
|
|
@@ -59,12 +266,11 @@ var IssueDetailView = ({
|
|
|
59
266
|
if (key.downArrow || input === "j") setScroll((s) => s + 1);
|
|
60
267
|
if (key.upArrow || input === "k") setScroll((s) => Math.max(0, s - 1));
|
|
61
268
|
});
|
|
62
|
-
const source =
|
|
63
|
-
const keyValueRows =
|
|
64
|
-
const chromeLines = keyValueRows + 1 +
|
|
269
|
+
const source = documentOf(issue);
|
|
270
|
+
const keyValueRows = 2 + (issue.parent ? 1 : 0) + (frame ? 0 : 1);
|
|
271
|
+
const chromeLines = keyValueRows + 1 + 1 + (frame ? 0 : 4);
|
|
65
272
|
const bodyHeight = Math.max(3, height - chromeLines);
|
|
66
273
|
const hints = [
|
|
67
|
-
["\u2190\u2192", "section"],
|
|
68
274
|
["\u2191\u2193", "scroll"],
|
|
69
275
|
...onTransition ? [["t", "move"]] : [],
|
|
70
276
|
...onComment ? [["c", "comment"]] : [],
|
|
@@ -74,20 +280,27 @@ var IssueDetailView = ({
|
|
|
74
280
|
];
|
|
75
281
|
const body = /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
76
282
|
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
283
|
+
/* @__PURE__ */ jsx(KeyValue, { label: "status", value: /* @__PURE__ */ jsx(Badge, { children: issue.status }) }),
|
|
284
|
+
frame ? null : /* @__PURE__ */ jsx(KeyValue, { label: "type", value: issue.type }),
|
|
285
|
+
/* @__PURE__ */ jsx(
|
|
286
|
+
KeyValue,
|
|
287
|
+
{
|
|
288
|
+
label: "assignee",
|
|
289
|
+
value: issue.assignee === "unassigned" ? /* @__PURE__ */ jsx(Text, { color: colors.muted, children: "unassigned" }) : issue.assignee
|
|
290
|
+
}
|
|
291
|
+
),
|
|
77
292
|
issue.parent && /* @__PURE__ */ jsx(
|
|
78
293
|
KeyValue,
|
|
79
294
|
{
|
|
80
295
|
label: "parent",
|
|
81
|
-
value:
|
|
296
|
+
value: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
297
|
+
/* @__PURE__ */ jsx(Text, { color: colors.secondary, children: issue.parent.key }),
|
|
298
|
+
/* @__PURE__ */ jsx(Text, { children: ` ${truncate(issue.parent.summary, 60)}` })
|
|
299
|
+
] })
|
|
82
300
|
}
|
|
83
301
|
),
|
|
84
|
-
/* @__PURE__ */ jsx(
|
|
85
|
-
frame ? null : /* @__PURE__ */ jsx(KeyValue, { label: "type", value: issue.type }),
|
|
86
|
-
/* @__PURE__ */ jsx(KeyValue, { label: "assignee", value: issue.assignee }),
|
|
87
|
-
/* @__PURE__ */ jsx(KeyValue, { label: "reporter", value: issue.reporter }),
|
|
88
|
-
issue.labels.length > 0 && /* @__PURE__ */ jsx(KeyValue, { label: "labels", value: issue.labels.join(", ") })
|
|
302
|
+
/* @__PURE__ */ jsx(FactsLine, { issue })
|
|
89
303
|
] }),
|
|
90
|
-
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Tabs, { items: tabs, active: active ?? "description" }) }),
|
|
91
304
|
/* @__PURE__ */ jsx(Box, { marginTop: 1, height: bodyHeight, overflow: "hidden", children: /* @__PURE__ */ jsx(
|
|
92
305
|
MarkdownViewport,
|
|
93
306
|
{
|
|
@@ -129,13 +342,14 @@ var issueDetailOf = async (client, baseUrl, key) => {
|
|
|
129
342
|
assignee: f.assignee?.displayName ?? "unassigned",
|
|
130
343
|
reporter: f.reporter?.displayName ?? "\u2014",
|
|
131
344
|
labels: f.labels ?? [],
|
|
345
|
+
...f.priority?.name ? { priority: f.priority.name } : {},
|
|
132
346
|
parent: f.parent ? { key: f.parent.key, summary: f.parent.fields?.summary ?? "" } : void 0,
|
|
133
347
|
url: `${baseUrl}/browse/${issue.key}`,
|
|
134
348
|
description: adfToMarkdown(f.description, filenameOf),
|
|
135
349
|
comments: (f.comment?.comments ?? []).map((c) => ({
|
|
136
350
|
id: c.id,
|
|
137
351
|
author: c.author?.displayName ?? "unknown",
|
|
138
|
-
created: c.created
|
|
352
|
+
created: c.created ?? "",
|
|
139
353
|
body: adfToMarkdown(c.body, filenameOf)
|
|
140
354
|
})),
|
|
141
355
|
attachments
|
|
@@ -169,6 +383,7 @@ var IssueBody = ({
|
|
|
169
383
|
client,
|
|
170
384
|
baseUrl,
|
|
171
385
|
issueKey,
|
|
386
|
+
summary,
|
|
172
387
|
onExit,
|
|
173
388
|
width,
|
|
174
389
|
height,
|
|
@@ -192,7 +407,7 @@ var IssueBody = ({
|
|
|
192
407
|
}, [client, baseUrl, issueKey]);
|
|
193
408
|
const framed = (body) => frame ? frame({
|
|
194
409
|
title: issueKey,
|
|
195
|
-
subtitle: "",
|
|
410
|
+
subtitle: summary ?? "",
|
|
196
411
|
hints: [["\u232B", "back"]],
|
|
197
412
|
body
|
|
198
413
|
}) : body;
|
|
@@ -219,177 +434,11 @@ var IssueBody = ({
|
|
|
219
434
|
}
|
|
220
435
|
);
|
|
221
436
|
};
|
|
222
|
-
var CATEGORIES = ["new", "indeterminate", "done"];
|
|
223
|
-
var categoryOf = (key) => CATEGORIES.find((c) => c === key) ?? "unknown";
|
|
224
|
-
var toBoardRow = (issue) => ({
|
|
225
|
-
key: issue.key,
|
|
226
|
-
summary: issue.fields.summary ?? "",
|
|
227
|
-
status: issue.fields.status?.name ?? "\u2014",
|
|
228
|
-
statusId: issue.fields.status?.id ?? "",
|
|
229
|
-
category: categoryOf(issue.fields.status?.statusCategory?.key),
|
|
230
|
-
type: issue.fields.issuetype?.name ?? "",
|
|
231
|
-
container: isContainerType(issue.fields.issuetype),
|
|
232
|
-
priority: issue.fields.priority?.name ?? null,
|
|
233
|
-
...issue.fields.parent ? {
|
|
234
|
-
parent: {
|
|
235
|
-
key: issue.fields.parent.key,
|
|
236
|
-
summary: issue.fields.parent.fields?.summary ?? ""
|
|
237
|
-
}
|
|
238
|
-
} : {},
|
|
239
|
-
assignee: issue.fields.assignee?.displayName ?? "unassigned",
|
|
240
|
-
updated: issue.fields.updated ?? ""
|
|
241
|
-
});
|
|
242
|
-
var OFF_BOARD = "__off_board__";
|
|
243
|
-
var CATEGORY_TABS = [
|
|
244
|
-
{ value: "todo", label: "To do", category: "new" },
|
|
245
|
-
{ value: "doing", label: "In progress", category: "indeterminate" },
|
|
246
|
-
{ value: "done", label: "Done", category: "done" }
|
|
247
|
-
];
|
|
248
|
-
var withOffBoard = (tabs) => [
|
|
249
|
-
...tabs,
|
|
250
|
-
{ value: OFF_BOARD, label: "Off board", offBoard: true }
|
|
251
|
-
];
|
|
252
|
-
var tabsFromConfig = (configured) => ({
|
|
253
|
-
source: "config",
|
|
254
|
-
tabs: withOffBoard(
|
|
255
|
-
configured.map((t, i) => ({
|
|
256
|
-
value: `config:${i}`,
|
|
257
|
-
label: t.label,
|
|
258
|
-
statuses: new Set(t.statuses.map((s) => s.toLowerCase()))
|
|
259
|
-
}))
|
|
260
|
-
)
|
|
261
|
-
});
|
|
262
|
-
var tabsFromBoard = (configuration) => {
|
|
263
|
-
const columns = configuration.columnConfig?.columns ?? [];
|
|
264
|
-
if (columns.length === 0) return null;
|
|
265
|
-
return {
|
|
266
|
-
source: "board",
|
|
267
|
-
tabs: withOffBoard(
|
|
268
|
-
columns.map((c, i) => ({
|
|
269
|
-
value: `column:${i}`,
|
|
270
|
-
label: c.name,
|
|
271
|
-
statuses: new Set(c.statuses.map((s) => s.id))
|
|
272
|
-
}))
|
|
273
|
-
)
|
|
274
|
-
};
|
|
275
|
-
};
|
|
276
|
-
var tabsFromCategories = () => ({
|
|
277
|
-
source: "categories",
|
|
278
|
-
tabs: CATEGORY_TABS
|
|
279
|
-
});
|
|
280
|
-
var tabOf = (row, { tabs }) => {
|
|
281
|
-
const claimed = tabs.find(
|
|
282
|
-
(t) => t.statuses?.has(row.statusId) || t.statuses?.has(row.status.toLowerCase())
|
|
283
|
-
);
|
|
284
|
-
if (claimed) return claimed.value;
|
|
285
|
-
const byCategory = tabs.find((t) => t.category && t.category === row.category);
|
|
286
|
-
if (byCategory) return byCategory.value;
|
|
287
|
-
return tabs.find((t) => t.offBoard)?.value ?? "doing";
|
|
288
|
-
};
|
|
289
|
-
var countsFor = (rows, tabs) => {
|
|
290
|
-
const counts = Object.fromEntries(
|
|
291
|
-
tabs.tabs.map((t) => [t.value, 0])
|
|
292
|
-
);
|
|
293
|
-
for (const row of rows)
|
|
294
|
-
counts[tabOf(row, tabs)] = (counts[tabOf(row, tabs)] ?? 0) + 1;
|
|
295
|
-
return counts;
|
|
296
|
-
};
|
|
297
|
-
var visibleTabs = (tabs, counts) => tabs.tabs.filter((t) => !t.offBoard || (counts[t.value] ?? 0) > 0);
|
|
298
|
-
var blocksFor = (rows) => {
|
|
299
|
-
const hasStructure = rows.some((r) => r.parent || r.container);
|
|
300
|
-
if (!hasStructure)
|
|
301
|
-
return rows.map((row) => ({ kind: "issue", row, depth: 0 }));
|
|
302
|
-
const heads = new Map(rows.filter((r) => r.container).map((r) => [r.key, r]));
|
|
303
|
-
const children = /* @__PURE__ */ new Map();
|
|
304
|
-
const orphans = [];
|
|
305
|
-
for (const row of rows) {
|
|
306
|
-
if (row.container) continue;
|
|
307
|
-
const key = row.parent?.key;
|
|
308
|
-
if (!key) {
|
|
309
|
-
orphans.push(row);
|
|
310
|
-
continue;
|
|
311
|
-
}
|
|
312
|
-
children.set(key, [...children.get(key) ?? [], row]);
|
|
313
|
-
}
|
|
314
|
-
const groups = [];
|
|
315
|
-
for (const [key, head] of heads)
|
|
316
|
-
groups.push([
|
|
317
|
-
{ kind: "issue", row: head, depth: 0 },
|
|
318
|
-
...(children.get(key) ?? []).map((row) => ({
|
|
319
|
-
kind: "issue",
|
|
320
|
-
row,
|
|
321
|
-
depth: 1
|
|
322
|
-
}))
|
|
323
|
-
]);
|
|
324
|
-
for (const [key, kids] of children) {
|
|
325
|
-
if (heads.has(key)) continue;
|
|
326
|
-
groups.push([
|
|
327
|
-
{ kind: "fence", key, summary: kids[0].parent?.summary ?? "" },
|
|
328
|
-
...kids.map((row) => ({ kind: "issue", row, depth: 1 }))
|
|
329
|
-
]);
|
|
330
|
-
}
|
|
331
|
-
if (orphans.length)
|
|
332
|
-
groups.push([
|
|
333
|
-
...groups.length ? [{ kind: "fence", key: null, summary: "No epic" }] : [],
|
|
334
|
-
...orphans.map((row) => ({ kind: "issue", row, depth: 0 }))
|
|
335
|
-
]);
|
|
336
|
-
return groups.flatMap(
|
|
337
|
-
(group, i) => i === 0 ? group : [{ kind: "gap" }, ...group]
|
|
338
|
-
);
|
|
339
|
-
};
|
|
340
|
-
var blockIndexOfIssue = (blocks, issueIndex) => {
|
|
341
|
-
let seen = -1;
|
|
342
|
-
for (let i = 0; i < blocks.length; i++) {
|
|
343
|
-
if (blocks[i].kind === "issue" && ++seen === issueIndex) return i;
|
|
344
|
-
}
|
|
345
|
-
return 0;
|
|
346
|
-
};
|
|
347
|
-
var priorityGlyph = (name) => {
|
|
348
|
-
const n = (name ?? "").trim().toLowerCase();
|
|
349
|
-
if (/highest|high|critical|blocker|urgent|^p[01]$/.test(n)) return "\u25B2";
|
|
350
|
-
if (/lowest|low|minor|trivial|^p[3-5]$/.test(n)) return "\u25BC";
|
|
351
|
-
return " ";
|
|
352
|
-
};
|
|
353
|
-
var UNITS = [
|
|
354
|
-
[60, "m"],
|
|
355
|
-
[60, "h"],
|
|
356
|
-
[24, "d"],
|
|
357
|
-
[7, "w"]
|
|
358
|
-
];
|
|
359
|
-
var relativeAge = (iso, now = Date.now()) => {
|
|
360
|
-
const then = Date.parse(iso);
|
|
361
|
-
if (Number.isNaN(then)) return "";
|
|
362
|
-
let value = Math.max(0, Math.round((now - then) / 1e3));
|
|
363
|
-
let unit = "s";
|
|
364
|
-
for (const [size, next] of UNITS) {
|
|
365
|
-
if (value < size) break;
|
|
366
|
-
value = Math.round(value / size);
|
|
367
|
-
unit = next;
|
|
368
|
-
}
|
|
369
|
-
return `${value}${unit}`;
|
|
370
|
-
};
|
|
371
|
-
var pillVariantFor = (row) => {
|
|
372
|
-
if (row.container) return "group";
|
|
373
|
-
const t = row.type.toLowerCase();
|
|
374
|
-
if (t === "bug") return "error";
|
|
375
|
-
if (t === "story") return "info";
|
|
376
|
-
return "muted";
|
|
377
|
-
};
|
|
378
|
-
var boardOf = async (client, jql, { tabs, board, limit = 200 } = {}) => {
|
|
379
|
-
const [issues, derived] = await Promise.all([
|
|
380
|
-
client.searchIssues(jql, { limit }),
|
|
381
|
-
tabs?.length ? Promise.resolve(tabsFromConfig(tabs)) : board !== void 0 ? client.getBoardConfiguration(board).then(tabsFromBoard) : Promise.resolve(null)
|
|
382
|
-
]);
|
|
383
|
-
return { rows: issues.map(toBoardRow), tabs: derived ?? tabsFromCategories() };
|
|
384
|
-
};
|
|
385
|
-
var priorityMarker = (name) => {
|
|
386
|
-
const marker = priorityGlyph(name);
|
|
387
|
-
return marker === "\u25B2" ? { marker, color: colors.warning } : marker === "\u25BC" ? { marker, color: colors.muted } : { marker };
|
|
388
|
-
};
|
|
389
437
|
var CHROME = 6;
|
|
390
438
|
var LEGEND = [
|
|
391
|
-
["\
|
|
392
|
-
["
|
|
439
|
+
["\u21C8 \u2191", "priority above the default"],
|
|
440
|
+
["=", "default priority"],
|
|
441
|
+
["\u2193 \u21CA", "priority below the default"],
|
|
393
442
|
["\u2500\u2500 epic \u2500\u2500", "a parent not in this tab; its rows hang beneath"],
|
|
394
443
|
["\u2514\u2500", "a row under the container above it"],
|
|
395
444
|
["story \xB7 bug \xB7 task", "issue type"],
|
package/package.json
CHANGED