@kud/gh-ink 0.32.2 → 0.34.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 +53 -6
- package/dist/index.js +141 -30
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -140,7 +140,23 @@ type GHItem = {
|
|
|
140
140
|
* handing back a `lastActor` that no longer claims a turn.
|
|
141
141
|
*/
|
|
142
142
|
pinned?: boolean;
|
|
143
|
-
|
|
143
|
+
/**
|
|
144
|
+
* How deep this row hangs in the tree: 0 top level, 1 a child, 2 a
|
|
145
|
+
* grandchild. Absent means 0.
|
|
146
|
+
*
|
|
147
|
+
* Always read it through `depthOf`, never off the row — `indent` is still a
|
|
148
|
+
* legal way to spell "depth 1", and that helper is the only place that knows
|
|
149
|
+
* both spellings. A site that reads `item.indent` directly prices a `depth: 2`
|
|
150
|
+
* row as top level, which is the bug this field exists to end rather than a
|
|
151
|
+
* new one to introduce.
|
|
152
|
+
*/
|
|
153
|
+
depth?: number;
|
|
154
|
+
/**
|
|
155
|
+
* @deprecated Legacy spelling of `depth: 1`. Write `depth`; this is kept so
|
|
156
|
+
* every existing producer still typechecks, and it is read in exactly one
|
|
157
|
+
* place (`depthOf`). Due to be removed at the next major.
|
|
158
|
+
*/
|
|
159
|
+
indent?: boolean;
|
|
144
160
|
};
|
|
145
161
|
type TaskRow = {
|
|
146
162
|
kind: "task";
|
|
@@ -149,7 +165,10 @@ type TaskRow = {
|
|
|
149
165
|
url: string;
|
|
150
166
|
status: string;
|
|
151
167
|
age: string;
|
|
152
|
-
|
|
168
|
+
/** How deep this row hangs. See `GHItem.depth`; read it through `depthOf`. */
|
|
169
|
+
depth?: number;
|
|
170
|
+
/** @deprecated Legacy spelling of `depth: 1`. See `GHItem.indent`. */
|
|
171
|
+
indent?: boolean;
|
|
153
172
|
instanceKey?: string;
|
|
154
173
|
/**
|
|
155
174
|
* Trailing annotation, rendered dim after the summary — a recurrence marker,
|
|
@@ -176,23 +195,35 @@ type RepoHeader = {
|
|
|
176
195
|
kind: "repo-header";
|
|
177
196
|
repo: string;
|
|
178
197
|
age: string;
|
|
179
|
-
|
|
198
|
+
/** How deep this row hangs. See `GHItem.depth`; read it through `depthOf`. */
|
|
199
|
+
depth?: number;
|
|
200
|
+
/** @deprecated Legacy spelling of `depth: 1`. See `GHItem.indent`. */
|
|
201
|
+
indent?: boolean;
|
|
180
202
|
};
|
|
181
203
|
type ShowMore = {
|
|
182
204
|
kind: "show-more";
|
|
183
205
|
hidden: GHItem[];
|
|
184
|
-
|
|
206
|
+
/** How deep this row hangs. See `GHItem.depth`; read it through `depthOf`. */
|
|
207
|
+
depth?: number;
|
|
208
|
+
/** @deprecated Legacy spelling of `depth: 1`. See `GHItem.indent`. */
|
|
209
|
+
indent?: boolean;
|
|
185
210
|
};
|
|
186
211
|
type ShowLess = {
|
|
187
212
|
kind: "show-less";
|
|
188
213
|
toHide: GHItem[];
|
|
189
|
-
|
|
214
|
+
/** How deep this row hangs. See `GHItem.depth`; read it through `depthOf`. */
|
|
215
|
+
depth?: number;
|
|
216
|
+
/** @deprecated Legacy spelling of `depth: 1`. See `GHItem.indent`. */
|
|
217
|
+
indent?: boolean;
|
|
190
218
|
};
|
|
191
219
|
type SubgroupHeader = {
|
|
192
220
|
kind: "subgroup-header";
|
|
193
221
|
label: string;
|
|
194
222
|
age: string;
|
|
195
|
-
|
|
223
|
+
/** How deep this row hangs. See `GHItem.depth`; read it through `depthOf`. */
|
|
224
|
+
depth?: number;
|
|
225
|
+
/** @deprecated Legacy spelling of `depth: 1`. See `GHItem.indent`. */
|
|
226
|
+
indent?: boolean;
|
|
196
227
|
};
|
|
197
228
|
type AnyItem = GHItem | TaskRow | RepoHeader | SubgroupHeader | ShowMore | ShowLess;
|
|
198
229
|
type CiStatus = {
|
|
@@ -295,6 +326,22 @@ declare const filterBySearch: (sections: Section[], query: string) => Section[];
|
|
|
295
326
|
declare const filterByRepos: (sections: Section[], repos: Set<string>) => Section[];
|
|
296
327
|
declare const withoutItem: (sections: Section[], target: GHItem) => Section[];
|
|
297
328
|
declare const reposInSections: (sections: Section[]) => string[];
|
|
329
|
+
/**
|
|
330
|
+
* The next selectable row in `dir`, or `current` when there is none.
|
|
331
|
+
*
|
|
332
|
+
* A repo-header is a STOP, and a subgroup-header is not. The two look alike and
|
|
333
|
+
* are not: the fence names a thing you can act on — open the checkout, copy every
|
|
334
|
+
* URL under it — where a band label ("Your move") names an arrangement of rows
|
|
335
|
+
* and has nothing behind it to open. Landing on the second would be a keystroke
|
|
336
|
+
* spent on a row whose every key is inert.
|
|
337
|
+
*
|
|
338
|
+
* Unconditional in both directions, deliberately. Skipping the fence going down
|
|
339
|
+
* and landing on it going up would make ↓ then ↑ end somewhere other than where
|
|
340
|
+
* it started, and an arrow pair that is not its own inverse reads as the list
|
|
341
|
+
* drifting rather than as a shortcut. The cost is one press per repo group; the
|
|
342
|
+
* fence is also the "you have crossed into another repo" beat the eye takes
|
|
343
|
+
* anyway, so the press buys orientation as well as position.
|
|
344
|
+
*/
|
|
298
345
|
declare const moveCursor: (items: AnyItem[], current: number, dir: 1 | -1) => number;
|
|
299
346
|
/**
|
|
300
347
|
* What to say about the remaining budget, or nothing at all.
|
package/dist/index.js
CHANGED
|
@@ -780,6 +780,11 @@ var tabsOfMarks = (sections, marks) => {
|
|
|
780
780
|
return out;
|
|
781
781
|
};
|
|
782
782
|
var quietly = $({ quiet: true });
|
|
783
|
+
var depthOf = (item) => {
|
|
784
|
+
if (!item) return 0;
|
|
785
|
+
if (typeof item.depth === "number") return Math.max(0, Math.floor(item.depth));
|
|
786
|
+
return item.indent === true ? 1 : 0;
|
|
787
|
+
};
|
|
783
788
|
var relativeTime = (iso) => {
|
|
784
789
|
const diff = (Date.now() - new Date(iso).getTime()) / 1e3;
|
|
785
790
|
if (diff < 3600) return `${Math.floor(diff / 60)}m`;
|
|
@@ -902,7 +907,7 @@ var insertRepoHeaders = (items) => {
|
|
|
902
907
|
const result = [];
|
|
903
908
|
let lastRepo = "";
|
|
904
909
|
for (const item of items) {
|
|
905
|
-
if (
|
|
910
|
+
if (depthOf(item) === 0 && item.repo !== lastRepo) {
|
|
906
911
|
lastRepo = item.repo;
|
|
907
912
|
result.push({
|
|
908
913
|
kind: "repo-header",
|
|
@@ -1057,7 +1062,7 @@ var reposInSections = (sections) => [
|
|
|
1057
1062
|
].sort();
|
|
1058
1063
|
var moveCursor = (items, current2, dir) => {
|
|
1059
1064
|
let next = current2 + dir;
|
|
1060
|
-
while (next >= 0 && next < items.length &&
|
|
1065
|
+
while (next >= 0 && next < items.length && items[next].kind === "subgroup-header")
|
|
1061
1066
|
next += dir;
|
|
1062
1067
|
if (next < 0 || next >= items.length) return current2;
|
|
1063
1068
|
return next;
|
|
@@ -1077,14 +1082,54 @@ var budgetNotice = (budget, now = Date.now()) => {
|
|
|
1077
1082
|
critical: false
|
|
1078
1083
|
};
|
|
1079
1084
|
};
|
|
1080
|
-
var
|
|
1085
|
+
var isLastAtDepth = (items, i) => {
|
|
1086
|
+
const depth = depthOf(items[i]);
|
|
1087
|
+
for (let j = i + 1; j < items.length; j += 1) {
|
|
1088
|
+
const d = depthOf(items[j]);
|
|
1089
|
+
if (d > depth) continue;
|
|
1090
|
+
return d < depth;
|
|
1091
|
+
}
|
|
1092
|
+
return true;
|
|
1093
|
+
};
|
|
1094
|
+
var treePrefix = (items, i) => {
|
|
1095
|
+
const depth = depthOf(items[i]);
|
|
1096
|
+
if (depth === 0) return "";
|
|
1097
|
+
const ancestorLast = [];
|
|
1098
|
+
let level = depth - 1;
|
|
1099
|
+
for (let j = i - 1; j >= 0 && level >= 1; j -= 1) {
|
|
1100
|
+
if (depthOf(items[j]) === level) {
|
|
1101
|
+
ancestorLast[level] = isLastAtDepth(items, j);
|
|
1102
|
+
level -= 1;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
let prefix = "";
|
|
1106
|
+
for (let k = 1; k < depth; k += 1) prefix += ancestorLast[k] ? " " : "\u2502 ";
|
|
1107
|
+
return prefix + (isLastAtDepth(items, i) ? "\u2514\u2500 " : "\u251C\u2500 ");
|
|
1108
|
+
};
|
|
1109
|
+
var groupUrls = (items, i) => {
|
|
1110
|
+
const urls = [];
|
|
1111
|
+
for (let j = i + 1; j < items.length; j += 1) {
|
|
1112
|
+
const item = items[j];
|
|
1113
|
+
if (item.kind === "repo-header" || item.kind === "subgroup-header") break;
|
|
1114
|
+
if (item.kind === "show-more") {
|
|
1115
|
+
for (const child of item.hidden) if (child.url) urls.push(child.url);
|
|
1116
|
+
continue;
|
|
1117
|
+
}
|
|
1118
|
+
const url = item.url;
|
|
1119
|
+
if (url) urls.push(url);
|
|
1120
|
+
}
|
|
1121
|
+
return urls;
|
|
1122
|
+
};
|
|
1081
1123
|
var treeUrls = (items, i) => {
|
|
1124
|
+
if (items[i]?.kind === "repo-header") return groupUrls(items, i);
|
|
1082
1125
|
let root = i;
|
|
1083
|
-
while (root > 0 &&
|
|
1126
|
+
while (root > 0 && depthOf(items[root]) > 0 && items[root]?.kind !== "task")
|
|
1127
|
+
root -= 1;
|
|
1128
|
+
const rootDepth = depthOf(items[root]);
|
|
1084
1129
|
const urls = [];
|
|
1085
1130
|
for (let j = root; j < items.length; j += 1) {
|
|
1086
1131
|
const item = items[j];
|
|
1087
|
-
if (j > root &&
|
|
1132
|
+
if (j > root && depthOf(item) <= rootDepth) break;
|
|
1088
1133
|
if (item.kind === "show-more") {
|
|
1089
1134
|
for (const child of item.hidden) if (child.url) urls.push(child.url);
|
|
1090
1135
|
continue;
|
|
@@ -1100,7 +1145,8 @@ var gapsAbove = (items, i) => {
|
|
|
1100
1145
|
if (item.kind === "repo-header" || item.kind === "subgroup-header")
|
|
1101
1146
|
return true;
|
|
1102
1147
|
if (item.kind !== "task") return false;
|
|
1103
|
-
|
|
1148
|
+
if (depthOf(item) > 0) return false;
|
|
1149
|
+
return depthOf(items[i + 1]) > 0 || depthOf(items[i - 1]) > 0;
|
|
1104
1150
|
};
|
|
1105
1151
|
var fitCount = (items, start, budget) => {
|
|
1106
1152
|
let lines = 0;
|
|
@@ -1345,7 +1391,7 @@ var Backdrop = ({
|
|
|
1345
1391
|
}
|
|
1346
1392
|
) : children });
|
|
1347
1393
|
var topLevelCount = (s) => s.items.filter(
|
|
1348
|
-
(i) => i.kind !== "repo-header" && i.kind !== "subgroup-header" &&
|
|
1394
|
+
(i) => i.kind !== "repo-header" && i.kind !== "subgroup-header" && depthOf(i) === 0
|
|
1349
1395
|
).length;
|
|
1350
1396
|
var drillCmd = (item) => {
|
|
1351
1397
|
if (item.kind === "task")
|
|
@@ -1593,7 +1639,7 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
|
|
|
1593
1639
|
}
|
|
1594
1640
|
});
|
|
1595
1641
|
if (jiraBase && jiraKeyRe) {
|
|
1596
|
-
const jiraKey =
|
|
1642
|
+
const jiraKey = depthOf(item) === 0 ? item.title.match(jiraKeyRe)?.[0] : null;
|
|
1597
1643
|
if (jiraKey) {
|
|
1598
1644
|
actions.push({
|
|
1599
1645
|
label: `Open ${jiraKey} in Jira`,
|
|
@@ -1761,10 +1807,21 @@ var CiStatusLine = ({
|
|
|
1761
1807
|
status.age ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: " " + status.age }) : null
|
|
1762
1808
|
] });
|
|
1763
1809
|
};
|
|
1764
|
-
var
|
|
1810
|
+
var FENCE_COLS = 46;
|
|
1811
|
+
var RepoHeaderRow = ({
|
|
1812
|
+
repo,
|
|
1813
|
+
gap,
|
|
1814
|
+
active
|
|
1815
|
+
}) => {
|
|
1765
1816
|
const label = `\u2500\u2500 ${repo} `;
|
|
1766
|
-
const fill = Math.max(4,
|
|
1767
|
-
|
|
1817
|
+
const fill = Math.max(4, FENCE_COLS - label.length);
|
|
1818
|
+
const rule = "\u2500".repeat(fill);
|
|
1819
|
+
return /* @__PURE__ */ jsxs(Box, { marginTop: gap ? 1 : 0, children: [
|
|
1820
|
+
/* @__PURE__ */ jsx(Text2, { color: "cyan", children: active ? "\u276F " : " " }),
|
|
1821
|
+
/* @__PURE__ */ jsx(Text2, { dimColor: !active, children: "\u2500\u2500 " }),
|
|
1822
|
+
/* @__PURE__ */ jsx(Text2, { dimColor: !active, bold: active, children: repo }),
|
|
1823
|
+
/* @__PURE__ */ jsx(Text2, { dimColor: !active, children: " " + rule })
|
|
1824
|
+
] });
|
|
1768
1825
|
};
|
|
1769
1826
|
var MERGED_HOLD_MS = 5e3;
|
|
1770
1827
|
var MERGED_FRAME_MS = 150;
|
|
@@ -1796,13 +1853,13 @@ var ItemRow = ({
|
|
|
1796
1853
|
merged,
|
|
1797
1854
|
transient: refreshMark,
|
|
1798
1855
|
leaving,
|
|
1799
|
-
|
|
1856
|
+
prefix = "",
|
|
1800
1857
|
parent,
|
|
1801
1858
|
sparkFrame = 0
|
|
1802
1859
|
}) => {
|
|
1803
1860
|
const transient = leaving ? "out" : refreshMark;
|
|
1804
1861
|
if (item.kind === "repo-header")
|
|
1805
|
-
return /* @__PURE__ */ jsx(RepoHeaderRow, { repo: item.repo, gap: gap ?? false });
|
|
1862
|
+
return /* @__PURE__ */ jsx(RepoHeaderRow, { repo: item.repo, gap: gap ?? false, active });
|
|
1806
1863
|
if (item.kind === "subgroup-header")
|
|
1807
1864
|
return /* @__PURE__ */ jsxs(Box, { marginTop: gap ? 1 : 0, children: [
|
|
1808
1865
|
/* @__PURE__ */ jsx(Text2, { color: "#FF8700", bold: true, children: " \xBB " }),
|
|
@@ -1811,14 +1868,14 @@ var ItemRow = ({
|
|
|
1811
1868
|
if (item.kind === "show-more")
|
|
1812
1869
|
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
1813
1870
|
/* @__PURE__ */ jsx(Text2, { color: "cyan", children: active ? "\u276F " : " " }),
|
|
1814
|
-
/* @__PURE__ */ jsx(Text2, { dimColor: true, children:
|
|
1871
|
+
/* @__PURE__ */ jsx(Text2, { dimColor: true, children: prefix }),
|
|
1815
1872
|
/* @__PURE__ */ jsx(Text2, { dimColor: true, children: active ? "\u21B5 " : " " }),
|
|
1816
1873
|
/* @__PURE__ */ jsx(Text2, { dimColor: true, children: `+${item.hidden.length} more` })
|
|
1817
1874
|
] });
|
|
1818
1875
|
if (item.kind === "show-less")
|
|
1819
1876
|
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
1820
1877
|
/* @__PURE__ */ jsx(Text2, { color: "cyan", children: active ? "\u276F " : " " }),
|
|
1821
|
-
/* @__PURE__ */ jsx(Text2, { dimColor: true, children:
|
|
1878
|
+
/* @__PURE__ */ jsx(Text2, { dimColor: true, children: prefix }),
|
|
1822
1879
|
/* @__PURE__ */ jsx(Text2, { dimColor: true, children: active ? "\u21B5 " : " " }),
|
|
1823
1880
|
/* @__PURE__ */ jsx(Text2, { dimColor: true, children: "show less" })
|
|
1824
1881
|
] });
|
|
@@ -1828,10 +1885,11 @@ var ItemRow = ({
|
|
|
1828
1885
|
const transitLabel2 = transient ? TRANSIT_LABEL[transient] : "";
|
|
1829
1886
|
const titleMax2 = Math.max(
|
|
1830
1887
|
20,
|
|
1831
|
-
COLS - item.key.length - note.length - transitLabel2.length - 12
|
|
1888
|
+
COLS - item.key.length - note.length - transitLabel2.length - prefix.length - 12
|
|
1832
1889
|
);
|
|
1833
1890
|
return /* @__PURE__ */ jsxs(Box, { marginTop: gap ? 1 : 0, children: [
|
|
1834
1891
|
/* @__PURE__ */ jsx(Text2, { color: "cyan", children: active ? "\u276F " : " " }),
|
|
1892
|
+
/* @__PURE__ */ jsx(Text2, { dimColor: true, children: prefix }),
|
|
1835
1893
|
/* @__PURE__ */ jsx(Text2, { dimColor: true, children: parent ? "\u252C " : " " }),
|
|
1836
1894
|
/* @__PURE__ */ jsx(Text2, { bold: true, color: transient ? TRANSIT_COLOUR[transient] : void 0, children: transitIcon + " " }),
|
|
1837
1895
|
/* @__PURE__ */ jsx(Text2, { color: "#FF8700", bold: active, children: item.key + " " }),
|
|
@@ -1866,12 +1924,12 @@ var ItemRow = ({
|
|
|
1866
1924
|
mergedLabel,
|
|
1867
1925
|
transitLabel
|
|
1868
1926
|
].filter(Boolean).join(" ");
|
|
1869
|
-
const repoLabel = item
|
|
1870
|
-
const fixedWidth = 2 +
|
|
1927
|
+
const repoLabel = depthOf(item) > 0 ? item.repo : "";
|
|
1928
|
+
const fixedWidth = 2 + prefix.length + 2 + 2 + 7 + repoLabel.length + suffix.length + 6;
|
|
1871
1929
|
const titleMax = Math.max(20, COLS - fixedWidth);
|
|
1872
1930
|
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
1873
1931
|
/* @__PURE__ */ jsx(Text2, { color: "cyan", children: active ? "\u276F " : " " }),
|
|
1874
|
-
|
|
1932
|
+
/* @__PURE__ */ jsx(Text2, { dimColor: true, children: prefix }),
|
|
1875
1933
|
/* @__PURE__ */ jsx(Text2, { color, bold: true, children: icon + " " }),
|
|
1876
1934
|
/* @__PURE__ */ jsx(Text2, { color: turnColor, bold: turnIcon === "\u2190", children: turnIcon + " " }),
|
|
1877
1935
|
/* @__PURE__ */ jsx(Text2, { color: "#FF8700", children: numStr }),
|
|
@@ -2016,13 +2074,13 @@ var HelpModal = ({
|
|
|
2016
2074
|
const keys = [
|
|
2017
2075
|
["\u2191 \u2193", "navigate"],
|
|
2018
2076
|
["\u2190 \u2192 \xB7 tab", "switch tab"],
|
|
2019
|
-
["\u21B5 \xB7 d", "open / drill in"],
|
|
2077
|
+
["\u21B5 \xB7 d", "open / drill in \xB7 repo: checkout"],
|
|
2020
2078
|
["m", "actions \xB7 close"],
|
|
2021
2079
|
["e", "explain this row"],
|
|
2022
2080
|
["o", "open in browser"],
|
|
2023
|
-
["O", "open
|
|
2081
|
+
["O", "open tree \xB7 whole repo group"],
|
|
2024
2082
|
["c", "copy URL"],
|
|
2025
|
-
["C", "copy
|
|
2083
|
+
["C", "copy tree \xB7 whole repo group"],
|
|
2026
2084
|
["b", "copy branch"],
|
|
2027
2085
|
["s", "switch to branch here"],
|
|
2028
2086
|
["j", "open repo in new tab"],
|
|
@@ -2462,7 +2520,11 @@ var BrowseScreen = ({
|
|
|
2462
2520
|
const ext = extensionFor(input, extensions);
|
|
2463
2521
|
if (ext) {
|
|
2464
2522
|
onOpenExt?.(ext.id, {
|
|
2465
|
-
item
|
|
2523
|
+
// ExtensionTarget.item has always been documented as absent on a header
|
|
2524
|
+
// row, and until the fence became selectable that was true for free.
|
|
2525
|
+
// Now it has to be said: every extension body narrows on `kind` for a
|
|
2526
|
+
// pr / issue / task and none of them expect a repo-header.
|
|
2527
|
+
item: activeItem && !isHeader(activeItem) ? activeItem : void 0,
|
|
2466
2528
|
ciJob: ciStatus?.job,
|
|
2467
2529
|
login,
|
|
2468
2530
|
onRemove: (row) => dismissItem(row),
|
|
@@ -2470,8 +2532,42 @@ var BrowseScreen = ({
|
|
|
2470
2532
|
});
|
|
2471
2533
|
return;
|
|
2472
2534
|
}
|
|
2473
|
-
if (
|
|
2535
|
+
if (activeItem?.kind === "repo-header") {
|
|
2536
|
+
const { repo } = activeItem;
|
|
2537
|
+
const repoUrl = `https://github.com/${repo}`;
|
|
2538
|
+
if (key.return || input === "d" || input === "j") {
|
|
2539
|
+
showFlash(`\u22EF Opening ${repo}\u2026`);
|
|
2540
|
+
void jumpToRepo(repo, "", login).then(() => showFlash(`\u2197 Opened ${repo} in new tab`)).catch(() => showFlash("\u2717 Jump failed"));
|
|
2541
|
+
return;
|
|
2542
|
+
}
|
|
2543
|
+
if (input === "o") {
|
|
2544
|
+
quietly`open ${repoUrl}`.catch(() => {
|
|
2545
|
+
});
|
|
2546
|
+
showFlash(`\u2197 Opened ${repo}`);
|
|
2547
|
+
return;
|
|
2548
|
+
}
|
|
2549
|
+
if (input === "c") {
|
|
2550
|
+
clipboard(repoUrl);
|
|
2551
|
+
showFlash(`\u2713 Copied URL for ${repo}`);
|
|
2552
|
+
return;
|
|
2553
|
+
}
|
|
2554
|
+
if (input === "C" || input === "O") {
|
|
2555
|
+
const urls = treeUrls(section.items, cursor);
|
|
2556
|
+
if (urls.length === 0) return;
|
|
2557
|
+
const n = `${urls.length} URL${urls.length === 1 ? "" : "s"}`;
|
|
2558
|
+
if (input === "C") {
|
|
2559
|
+
clipboard(urls.join("\n"));
|
|
2560
|
+
showFlash(`\u2713 Copied ${n} from ${repo}`);
|
|
2561
|
+
} else {
|
|
2562
|
+
quietly`open ${urls}`.catch(() => {
|
|
2563
|
+
});
|
|
2564
|
+
showFlash(`\u2197 Opened ${n} from ${repo}`);
|
|
2565
|
+
}
|
|
2566
|
+
return;
|
|
2567
|
+
}
|
|
2474
2568
|
return;
|
|
2569
|
+
}
|
|
2570
|
+
if (!activeItem || activeItem.kind === "subgroup-header") return;
|
|
2475
2571
|
if (key.return && activeItem.kind === "show-more") {
|
|
2476
2572
|
setLocalSections(
|
|
2477
2573
|
(prev) => prev.map((s) => ({
|
|
@@ -2482,7 +2578,12 @@ var BrowseScreen = ({
|
|
|
2482
2578
|
{
|
|
2483
2579
|
kind: "show-less",
|
|
2484
2580
|
toHide: activeItem.hidden,
|
|
2485
|
-
|
|
2581
|
+
// The depth of the row it replaces, never a constant. A
|
|
2582
|
+
// collapsed row sitting under a story is at depth 2, and
|
|
2583
|
+
// pinning it to 1 makes the walk in `treeUrls` stop one
|
|
2584
|
+
// level early and drop every row it holds — silently, since
|
|
2585
|
+
// nothing on screen reports what the clipboard missed.
|
|
2586
|
+
depth: depthOf(activeItem)
|
|
2486
2587
|
}
|
|
2487
2588
|
] : [i]
|
|
2488
2589
|
)
|
|
@@ -2499,7 +2600,9 @@ var BrowseScreen = ({
|
|
|
2499
2600
|
{
|
|
2500
2601
|
kind: "show-more",
|
|
2501
2602
|
hidden: activeItem.toHide,
|
|
2502
|
-
|
|
2603
|
+
// Same reasoning as the show-less above: carry the depth
|
|
2604
|
+
// of the row being replaced, not a constant.
|
|
2605
|
+
depth: depthOf(activeItem)
|
|
2503
2606
|
}
|
|
2504
2607
|
] : [i]
|
|
2505
2608
|
)
|
|
@@ -2651,7 +2754,15 @@ var BrowseScreen = ({
|
|
|
2651
2754
|
menu.open(actions);
|
|
2652
2755
|
}
|
|
2653
2756
|
});
|
|
2654
|
-
const hints = [
|
|
2757
|
+
const hints = activeItem?.kind === "repo-header" ? [
|
|
2758
|
+
["\u2191\u2193", "nav"],
|
|
2759
|
+
["\u2190\u2192", "tab"],
|
|
2760
|
+
["\u21B5", "open repo"],
|
|
2761
|
+
["o", "browser"],
|
|
2762
|
+
["C", "copy group"],
|
|
2763
|
+
["?", "help"],
|
|
2764
|
+
["q", "quit"]
|
|
2765
|
+
] : [
|
|
2655
2766
|
["\u2191\u2193", "nav"],
|
|
2656
2767
|
["\u2190\u2192", "tab"],
|
|
2657
2768
|
["\u21B5/d", "open"],
|
|
@@ -2743,8 +2854,8 @@ var BrowseScreen = ({
|
|
|
2743
2854
|
merged: (item.kind === "pr" || item.kind === "issue") && !!mergedUrls?.includes(item.url),
|
|
2744
2855
|
leaving: (item.kind === "pr" || item.kind === "issue") && !!leavingUrls?.includes(item.url),
|
|
2745
2856
|
transient: transientOf(transients, item),
|
|
2746
|
-
|
|
2747
|
-
parent: item.kind === "task" &&
|
|
2857
|
+
prefix: treePrefix(section.items, viewStart + i),
|
|
2858
|
+
parent: item.kind === "task" && depthOf(section.items[viewStart + i + 1]) > depthOf(item),
|
|
2748
2859
|
sparkFrame,
|
|
2749
2860
|
gap: i > 0 && gapsAbove(section.items, viewStart + i)
|
|
2750
2861
|
},
|
|
@@ -2952,7 +3063,7 @@ var App = ({
|
|
|
2952
3063
|
receive(fresh);
|
|
2953
3064
|
}).catch((err) => {
|
|
2954
3065
|
setRefreshing(false);
|
|
2955
|
-
const message = err
|
|
3066
|
+
const message = explainGhAction(err);
|
|
2956
3067
|
if (!displayedKey.current) {
|
|
2957
3068
|
setState({ phase: "failed", message });
|
|
2958
3069
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kud/gh-ink",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
4
4
|
"description": "Ink components for rendering GitHub PR review comments and health — controlled, presentation-only, built on @kud/ink-ui and fed by @kud/gh.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|