@kud/gh-ink 0.66.0 → 0.67.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 +29 -0
- package/dist/index.d.ts +54 -9
- package/dist/index.js +323 -116
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -32,6 +32,35 @@ the same panel drops into a full-screen CLI or a single pane of a dashboard. The
|
|
|
32
32
|
core (`@kud/gh`) decides the semantic token; this layer maps it to a colour. Same
|
|
33
33
|
seam as `@kud/jenkins` → `@kud/jenkins-ink`.
|
|
34
34
|
|
|
35
|
+
## Inbox extensions
|
|
36
|
+
|
|
37
|
+
A host contributes verbs and screens to the inbox through `InboxExtension`:
|
|
38
|
+
|
|
39
|
+
| Field | What |
|
|
40
|
+
| ----------- | ----------------------------------------------------------------------------------------------------------- |
|
|
41
|
+
| `key` | The keypress that opens it, matched after the inbox's own bindings, so an extension never shadows them. |
|
|
42
|
+
| `scope` | `"item"` acts on the selected row and earns a place in its action menu; `"global"` does not. |
|
|
43
|
+
| `menuGroup` | `"act"` (does something to the row) or `"open"` (opens something); absent or `"other"` joins the quiet end. |
|
|
44
|
+
| `hint` | Short footer label. The action menu capitalises it for the row ("submit" → "Submit"); the legend keeps `title`. |
|
|
45
|
+
| `icon` | Nerd-font glyph for the menu row, as a `"\uF4FA"` escape. Shown only in nerd icon mode (see below). |
|
|
46
|
+
| `body` | `(onExit, target) => ReactNode` — rendered inside the inbox frame; claims header/footer via `useChrome`. |
|
|
47
|
+
|
|
48
|
+
Nerd-font icons render only when the host calls `setIconMode("nerd")` from
|
|
49
|
+
`@kud/ink-ui` before the first render. In the default text mode the menu's icon
|
|
50
|
+
column is dropped entirely — no stand-in, nothing to align — so a terminal
|
|
51
|
+
without a Nerd Font loses nothing but decoration. The built-ins use these
|
|
52
|
+
codepoints; sibling host verbs should match them rather than invent neighbours:
|
|
53
|
+
|
|
54
|
+
| Verb | Codepoint |
|
|
55
|
+
| ----------------------------- | --------- |
|
|
56
|
+
| Submit / Resubmit | U+F4FA / U+F46A |
|
|
57
|
+
| Land | U+F419 |
|
|
58
|
+
| Open PR / in browser / ticket | U+F440 / U+F465 / U+F41B |
|
|
59
|
+
| Switch here / tab / pane | U+EBCB / U+EAE4 / U+EB56 |
|
|
60
|
+
| Copy URL / repo / branch | U+F44C / U+F401 / U+F418 |
|
|
61
|
+
| Unsubscribe / Mute / Remove reviewer | U+F478 / U+F466 / U+F468 |
|
|
62
|
+
| Close PR / and delete / issue | U+F4DC / U+F48E / U+F41D |
|
|
63
|
+
|
|
35
64
|
## Development
|
|
36
65
|
|
|
37
66
|
```sh
|
package/dist/index.d.ts
CHANGED
|
@@ -296,14 +296,15 @@ type TrailingShed = Partial<Record<(typeof TRAILING)[number], boolean>>;
|
|
|
296
296
|
declare const trailingColumnsOf: (items: readonly GHItem[]) => TrailingColumns;
|
|
297
297
|
/**
|
|
298
298
|
* Width of the `#n` cell across these rows: the widest number plus a two-cell
|
|
299
|
-
* gutter, left-aligned.
|
|
300
|
-
* same move as `markerCols` and `trailingColumnsOf`, so
|
|
301
|
-
* column and that column
|
|
299
|
+
* gutter, left-aligned. The inbox measures it once over the rows of EVERY tab
|
|
300
|
+
* (`listColumnsOf`), the same move as `markerCols` and `trailingColumnsOf`, so
|
|
301
|
+
* the titles start on one column and that column moves neither as you scroll
|
|
302
|
+
* nor as you switch tabs. Measured per tab, it put `#1234`'s titles a column
|
|
303
|
+
* right of `#157`'s in the next tab, and every switch shifted the list.
|
|
302
304
|
*
|
|
303
305
|
* It replaced `padEnd(7)`, a constant that fitted the repos it was written
|
|
304
306
|
* against and nothing else: `#31805` left a one-cell gutter where `#172` left
|
|
305
|
-
* three, and a six-digit number would have run straight into its title.
|
|
306
|
-
* section of short numbers no longer pays for a long one it does not hold.
|
|
307
|
+
* three, and a six-digit number would have run straight into its title.
|
|
307
308
|
*/
|
|
308
309
|
declare const numberColumnsOf: (items: readonly GHItem[]) => number;
|
|
309
310
|
type LayoutInput = Pick<PrRowProps, "item" | "login" | "cols" | "prefix" | "uniformLabels" | "announcements" | "columns" | "shed" | "numberCols" | "gutter">;
|
|
@@ -416,6 +417,25 @@ declare const healthLegend: [Health, string][];
|
|
|
416
417
|
*/
|
|
417
418
|
declare const truncate: (str: string, max: number) => string;
|
|
418
419
|
|
|
420
|
+
type ChromeHints = [string, string][];
|
|
421
|
+
type ChromeSpec = {
|
|
422
|
+
scope?: string;
|
|
423
|
+
hints?: ChromeHints;
|
|
424
|
+
};
|
|
425
|
+
type ChromeState = ChromeSpec & {
|
|
426
|
+
setChrome: (spec: ChromeSpec) => void;
|
|
427
|
+
};
|
|
428
|
+
/**
|
|
429
|
+
* Claim a word in the inbox chrome while mounted: `scope` becomes the header
|
|
430
|
+
* breadcrumb (e.g. `Resubmit · acme/api-gateway#1234`), `hints` the footer's
|
|
431
|
+
* verbs. The shell appends its own back/quit tail, so a body passes only its
|
|
432
|
+
* own keys — never `esc`, `q` or `?`.
|
|
433
|
+
*
|
|
434
|
+
* Returns whether a shell claimed it: a view mounted outside the inbox (where
|
|
435
|
+
* the context is null) draws its own chrome instead, so the same component
|
|
436
|
+
* renders framed standalone and chromeless inside the shell.
|
|
437
|
+
*/
|
|
438
|
+
declare const useChrome: (spec: ChromeSpec) => boolean;
|
|
419
439
|
type ExtensionTarget = {
|
|
420
440
|
item?: AnyItem;
|
|
421
441
|
ciJob?: string;
|
|
@@ -437,6 +457,8 @@ interface InboxExtension {
|
|
|
437
457
|
key: string;
|
|
438
458
|
hint?: string;
|
|
439
459
|
scope?: "item" | "global";
|
|
460
|
+
menuGroup?: "act" | "open" | "other";
|
|
461
|
+
icon?: string;
|
|
440
462
|
drills?: AnyItem["kind"][];
|
|
441
463
|
body: (onExit: () => void, target?: ExtensionTarget) => ReactNode;
|
|
442
464
|
commands?: (target: ExtensionTarget) => Command[];
|
|
@@ -524,12 +546,20 @@ type SettledResult = {
|
|
|
524
546
|
ok: false;
|
|
525
547
|
reason: string;
|
|
526
548
|
};
|
|
549
|
+
type ActionGroup = "act" | "open" | "switch" | "copy" | "quiet" | "close" | "confirm";
|
|
527
550
|
type Action = {
|
|
528
551
|
label: string;
|
|
529
552
|
hint: string;
|
|
530
553
|
run: () => void;
|
|
531
554
|
subActions?: Action[];
|
|
555
|
+
group?: ActionGroup;
|
|
556
|
+
icon?: string;
|
|
532
557
|
};
|
|
558
|
+
type MenuGap = {
|
|
559
|
+
gap: true;
|
|
560
|
+
};
|
|
561
|
+
type MenuRow = Action | MenuGap;
|
|
562
|
+
declare const isMenuGap: (row: MenuRow) => row is MenuGap;
|
|
533
563
|
type JiraTransition = {
|
|
534
564
|
/** What the action menu shows. Yours to word; nothing matches on it. */
|
|
535
565
|
label: string;
|
|
@@ -700,8 +730,16 @@ declare const CiStatusLine: ({ state, job, }: {
|
|
|
700
730
|
state: CiStatusState;
|
|
701
731
|
job?: string;
|
|
702
732
|
}) => React.JSX.Element;
|
|
733
|
+
declare const menuRowsOf: (actions: Action[]) => MenuRow[];
|
|
734
|
+
/**
|
|
735
|
+
* What tone a menu row wears: `"error"` for the close rows (top-level and
|
|
736
|
+
* confirm alike, never `Cancel`), nothing for everything else. The menu's one
|
|
737
|
+
* meaningful colour, and the only one — every other distinction on the menu
|
|
738
|
+
* is glyph, word or weight, never hue alone.
|
|
739
|
+
*/
|
|
740
|
+
declare const menuTone: (row: Action) => "error" | undefined;
|
|
703
741
|
declare const useActionMenu: () => {
|
|
704
|
-
actions:
|
|
742
|
+
actions: MenuRow[] | null;
|
|
705
743
|
cursor: number;
|
|
706
744
|
open: (next: Action[]) => void;
|
|
707
745
|
close: () => void;
|
|
@@ -712,10 +750,17 @@ declare const useActionMenu: () => {
|
|
|
712
750
|
escape?: boolean;
|
|
713
751
|
}) => boolean;
|
|
714
752
|
};
|
|
715
|
-
declare const ActionMenu: ({ item, actions, cursor, }: {
|
|
753
|
+
declare const ActionMenu: ({ item, actions, cursor, cols, }: {
|
|
716
754
|
item: AnyItem;
|
|
717
|
-
actions:
|
|
755
|
+
actions: MenuRow[];
|
|
718
756
|
cursor: number;
|
|
757
|
+
/**
|
|
758
|
+
* Content columns the menu may take. The width is fixed from the longest
|
|
759
|
+
* row — clamp(longest + 4, 40, 56), capped at `cols - 4` — so it never
|
|
760
|
+
* resizes as the cursor moves. A host beside a rail passes what the list
|
|
761
|
+
* has; everywhere else the frame's own width is the right answer.
|
|
762
|
+
*/
|
|
763
|
+
cols?: number;
|
|
719
764
|
}) => React.JSX.Element;
|
|
720
765
|
declare const signatureOf: (sections: Section[]) => string;
|
|
721
766
|
declare const App: ({ fetcher, cacheKey, title, detailFor, origin, jiraBase, jiraKeyRe, jiraSetupHint, jiraTransitions, hasCiStatus, ciJob, ciFetcher, ciPollMs, stripFetcher, stripLabel, stripPollMs, watchPath, watchDebounceMs, watchJitterMs, extensions, tabHelp, emptyHint, liveLabel, onSettled, }: {
|
|
@@ -826,4 +871,4 @@ declare const matchesFilter: (repo: string, filter: RepoFilter) => boolean;
|
|
|
826
871
|
*/
|
|
827
872
|
declare const parsePatterns: (value: string) => string[];
|
|
828
873
|
|
|
829
|
-
export { type Action, ActionMenu, App, COLS, CiStatusLine, type CiStatusState, type Command, CommentsPanel, type CommentsPanelProps, type DetailContext, type ExplainSection, type ExtensionTarget, HealthPanel, type HealthPanelProps, type InboxBudget, type InboxExtension, type JiraTransition, type LiveLabel, MIN_SIDEBAR_COLS, PrRow, type PrRowProps, type Rails, type RepoFilter, type RowAnnouncement, type RowMotion, SIDEBAR_COLS, type SettledResult, SidePanel, type Sidebar, type SidebarRow, type StatusStrip, StatusStripLine, type StripItem, type StripState, type StripStatusState, type TrailingColumns, type TrailingShed, ageLabel, budgetNotice, buildActions, buildCheckoutCmd, clipboard, counts, drillCmd, explainGhAction, explainItem, fitCount, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, itermRun, jumpToRepo, jumpToRepoPane, matchesFilter, maxViewStart, moveCursor, numberColumnsOf, openInTab, parsePatterns, railCapacity, railHeights, railRowsOf, railWidth, railsOf, readCache, renderMarkdown, reposInSections, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, sectionShedOf, signatureOf, stripGlyph, stripLayout, stripSignature, toCiStatusState, trailingColumnsOf, truncate, useActionMenu, windowCount, withHeaders, withoutItem, workCount, writeCache };
|
|
874
|
+
export { type Action, type ActionGroup, ActionMenu, App, COLS, type ChromeHints, type ChromeSpec, type ChromeState, CiStatusLine, type CiStatusState, type Command, CommentsPanel, type CommentsPanelProps, type DetailContext, type ExplainSection, type ExtensionTarget, HealthPanel, type HealthPanelProps, type InboxBudget, type InboxExtension, type JiraTransition, type LiveLabel, MIN_SIDEBAR_COLS, type MenuGap, type MenuRow, PrRow, type PrRowProps, type Rails, type RepoFilter, type RowAnnouncement, type RowMotion, SIDEBAR_COLS, type SettledResult, SidePanel, type Sidebar, type SidebarRow, type StatusStrip, StatusStripLine, type StripItem, type StripState, type StripStatusState, type TrailingColumns, type TrailingShed, ageLabel, budgetNotice, buildActions, buildCheckoutCmd, clipboard, counts, drillCmd, explainGhAction, explainItem, fitCount, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, isMenuGap, itermRun, jumpToRepo, jumpToRepoPane, matchesFilter, maxViewStart, menuRowsOf, menuTone, moveCursor, numberColumnsOf, openInTab, parsePatterns, railCapacity, railHeights, railRowsOf, railWidth, railsOf, readCache, renderMarkdown, reposInSections, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, sectionShedOf, signatureOf, stripGlyph, stripLayout, stripSignature, toCiStatusState, trailingColumnsOf, truncate, useActionMenu, useChrome, windowCount, withHeaders, withoutItem, workCount, writeCache };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import React2, { createContext, useState, useRef, useEffect,
|
|
1
|
+
import React2, { createContext, useState, useRef, useContext, useEffect, useCallback, useMemo } from 'react';
|
|
2
2
|
import { useWindowSize, useInput, Text, Box } from 'ink';
|
|
3
|
-
import { colors, ScrollView, TextInput, Pill, useAppKeys, LoadingScreen, StatusMessage,
|
|
3
|
+
import { colors, ScrollView, TextInput, Pill, getIconMode, FooterHints, useAppKeys, LoadingScreen, StatusMessage, useFilterMode, useListCursor, CommandPalette, Tabs, ProgressBar, pillWidth } from '@kud/ink-ui';
|
|
4
4
|
import { isPassCheck, isFailCheck, isInconclusiveCheck, resolveThread, unresolveThread, replyToThread, rerunFailedRun, mergePr, reRequestReviewer } from '@kud/gh';
|
|
5
5
|
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
6
6
|
import { PIN_MARK, sizeOf, sizePartsOf, depthOf, impliedLabels, labelPriority, checkoutDirs, profileOf, inboxConfig, relativeTime, filterBySearch, filterByRepos, filterByOrigin } from '@kud/gh-workflow';
|
|
@@ -1161,6 +1161,18 @@ var StatusStripLine = ({
|
|
|
1161
1161
|
age ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` ${age}` }) : null
|
|
1162
1162
|
] });
|
|
1163
1163
|
};
|
|
1164
|
+
var InboxChromeContext = createContext(null);
|
|
1165
|
+
var useChrome = (spec) => {
|
|
1166
|
+
const chrome = useContext(InboxChromeContext);
|
|
1167
|
+
const setChrome = chrome?.setChrome;
|
|
1168
|
+
const scope = spec.scope;
|
|
1169
|
+
const hintsKey = JSON.stringify(spec.hints ?? null);
|
|
1170
|
+
useEffect(() => {
|
|
1171
|
+
if (!setChrome) return;
|
|
1172
|
+
setChrome({ scope, hints: spec.hints });
|
|
1173
|
+
}, [setChrome, scope, hintsKey]);
|
|
1174
|
+
return chrome !== null;
|
|
1175
|
+
};
|
|
1164
1176
|
|
|
1165
1177
|
// src/inbox/cache.ts
|
|
1166
1178
|
var cacheTtlMs = () => inboxConfig().cacheTtlMs;
|
|
@@ -1412,6 +1424,7 @@ var reconcile = (prev, fresh, now, holdMs = HOLD_MS) => {
|
|
|
1412
1424
|
return out;
|
|
1413
1425
|
};
|
|
1414
1426
|
var quietly = $({ quiet: true });
|
|
1427
|
+
var isMenuGap = (row) => "gap" in row;
|
|
1415
1428
|
var healthSentence = (item) => {
|
|
1416
1429
|
const glyph = displayFor(item.health).glyph.trim();
|
|
1417
1430
|
const d = item.detail;
|
|
@@ -1531,6 +1544,13 @@ var reposInSections = (sections) => [
|
|
|
1531
1544
|
sections.flatMap((s) => s.items).filter((i) => i.kind === "pr" || i.kind === "issue").map((i) => i.repo)
|
|
1532
1545
|
)
|
|
1533
1546
|
].sort();
|
|
1547
|
+
var listColumnsOf = (sections) => {
|
|
1548
|
+
const rows = sections.flatMap((s) => s.items).filter((i) => i.kind === "pr" || i.kind === "issue");
|
|
1549
|
+
return {
|
|
1550
|
+
numberCols: numberColumnsOf(rows),
|
|
1551
|
+
trailingColumns: trailingColumnsOf(rows)
|
|
1552
|
+
};
|
|
1553
|
+
};
|
|
1534
1554
|
var moveCursor = (items, current, dir) => {
|
|
1535
1555
|
const next = current + dir;
|
|
1536
1556
|
if (next < 0 || next >= items.length) return current;
|
|
@@ -1891,6 +1911,14 @@ var drillLabel = (item) => {
|
|
|
1891
1911
|
if (item.kind === "pr") return "Open PR";
|
|
1892
1912
|
return "Drill in";
|
|
1893
1913
|
};
|
|
1914
|
+
var sameChrome = (a, b) => {
|
|
1915
|
+
if (a.scope !== b.scope) return false;
|
|
1916
|
+
if (a.hints === b.hints) return true;
|
|
1917
|
+
if (!a.hints || !b.hints || a.hints.length !== b.hints.length) return false;
|
|
1918
|
+
return a.hints.every(
|
|
1919
|
+
([key, label], i) => b.hints[i]?.[0] === key && b.hints[i]?.[1] === label
|
|
1920
|
+
);
|
|
1921
|
+
};
|
|
1894
1922
|
var UNSUBSCRIBE_MUTATION = `
|
|
1895
1923
|
mutation($id: ID!) {
|
|
1896
1924
|
updateSubscription(input: { subscribableId: $id, state: UNSUBSCRIBED }) {
|
|
@@ -1941,6 +1969,8 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
|
|
|
1941
1969
|
const open = {
|
|
1942
1970
|
label: "Open in browser",
|
|
1943
1971
|
hint: "o",
|
|
1972
|
+
group: "open",
|
|
1973
|
+
icon: "\uF465",
|
|
1944
1974
|
run: () => {
|
|
1945
1975
|
quietly`open ${item.url}`.catch(() => {
|
|
1946
1976
|
});
|
|
@@ -1950,6 +1980,8 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
|
|
|
1950
1980
|
const copyUrl = {
|
|
1951
1981
|
label: "Copy URL",
|
|
1952
1982
|
hint: "c",
|
|
1983
|
+
group: "copy",
|
|
1984
|
+
icon: "\uF44C",
|
|
1953
1985
|
run: () => {
|
|
1954
1986
|
clipboard(item.url);
|
|
1955
1987
|
const label = item.kind === "task" ? item.key : `#${item.number}`;
|
|
@@ -1961,6 +1993,11 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
|
|
|
1961
1993
|
const drillAction = drill || mountable && onOpenView ? {
|
|
1962
1994
|
label: drillLabel(item),
|
|
1963
1995
|
hint: "d",
|
|
1996
|
+
group: "open",
|
|
1997
|
+
// Only the PR drill names its glyph: "View issue" and "View ticket"
|
|
1998
|
+
// have no nerd-font verb of their own, and an icon that says nothing
|
|
1999
|
+
// the label does not already say is decoration, not information.
|
|
2000
|
+
icon: item.kind === "pr" ? "\uF440" : void 0,
|
|
1964
2001
|
run: () => {
|
|
1965
2002
|
if (onOpenView?.(item)) return;
|
|
1966
2003
|
if (drill) {
|
|
@@ -1976,6 +2013,10 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
|
|
|
1976
2013
|
base.push({
|
|
1977
2014
|
label: "Move status",
|
|
1978
2015
|
hint: "t",
|
|
2016
|
+
// The task's act: changing its state, the one mutation this menu owns.
|
|
2017
|
+
// Guarded by the caller's own condition above — without configured
|
|
2018
|
+
// transitions there is no such row, and none is invented here.
|
|
2019
|
+
group: "act",
|
|
1979
2020
|
run: () => {
|
|
1980
2021
|
},
|
|
1981
2022
|
subActions: jiraTransitions.map(
|
|
@@ -2009,21 +2050,40 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
|
|
|
2009
2050
|
)
|
|
2010
2051
|
});
|
|
2011
2052
|
}
|
|
2012
|
-
return base;
|
|
2053
|
+
return byGroup(base);
|
|
2013
2054
|
}
|
|
2014
2055
|
const actions = drillAction ? [drillAction, open, copyUrl] : [open, copyUrl];
|
|
2056
|
+
const closing = [];
|
|
2015
2057
|
actions.push({
|
|
2016
2058
|
label: "Copy repo name",
|
|
2017
2059
|
hint: "r",
|
|
2060
|
+
group: "copy",
|
|
2061
|
+
icon: "\uF401",
|
|
2018
2062
|
run: () => {
|
|
2019
2063
|
clipboard(item.repo);
|
|
2020
2064
|
showFlash(`\u2713 Copied ${item.repo}`);
|
|
2021
2065
|
}
|
|
2022
2066
|
});
|
|
2067
|
+
actions.push({
|
|
2068
|
+
label: "Unsubscribe",
|
|
2069
|
+
hint: "u",
|
|
2070
|
+
group: "quiet",
|
|
2071
|
+
icon: "\uF478",
|
|
2072
|
+
run: () => {
|
|
2073
|
+
showFlash(`\u22EF Unsubscribing from #${item.number}\u2026`);
|
|
2074
|
+
void unsubscribeFrom(item).then(() => showFlash(`\u2713 Unsubscribed from #${item.number}`)).catch((e) => showFlash(`\u2717 #${item.number}: ${explainGhAction(e)}`));
|
|
2075
|
+
}
|
|
2076
|
+
});
|
|
2023
2077
|
if (item.kind === "pr" && item.standing === "queued" && login) {
|
|
2024
2078
|
actions.push({
|
|
2025
2079
|
label: "Remove me as reviewer",
|
|
2026
2080
|
hint: "x",
|
|
2081
|
+
// Quiet, beside Unsubscribe: both are "less of this PR for me" — no more
|
|
2082
|
+
// review requests, no more notifications. Guarded by the caller's own
|
|
2083
|
+
// condition above, like "Move status" is by its: without a standing
|
|
2084
|
+
// review request there is no such row, and none is invented here.
|
|
2085
|
+
group: "quiet",
|
|
2086
|
+
icon: "\uF468",
|
|
2027
2087
|
run: () => {
|
|
2028
2088
|
onRemove?.(item);
|
|
2029
2089
|
showFlash(`\u22EF Removing you from #${item.number}\u2026`);
|
|
@@ -2045,18 +2105,12 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
|
|
|
2045
2105
|
}
|
|
2046
2106
|
});
|
|
2047
2107
|
}
|
|
2048
|
-
actions.push({
|
|
2049
|
-
label: "Unsubscribe",
|
|
2050
|
-
hint: "u",
|
|
2051
|
-
run: () => {
|
|
2052
|
-
showFlash(`\u22EF Unsubscribing from #${item.number}\u2026`);
|
|
2053
|
-
void unsubscribeFrom(item).then(() => showFlash(`\u2713 Unsubscribed from #${item.number}`)).catch((e) => showFlash(`\u2717 #${item.number}: ${explainGhAction(e)}`));
|
|
2054
|
-
}
|
|
2055
|
-
});
|
|
2056
2108
|
if (item.kind === "pr" && item.branch) {
|
|
2057
2109
|
actions.push({
|
|
2058
2110
|
label: "Copy branch name",
|
|
2059
2111
|
hint: "b",
|
|
2112
|
+
group: "copy",
|
|
2113
|
+
icon: "\uF418",
|
|
2060
2114
|
run: () => {
|
|
2061
2115
|
clipboard(item.branch);
|
|
2062
2116
|
showFlash(`\u2713 Copied ${item.branch}`);
|
|
@@ -2067,6 +2121,8 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
|
|
|
2067
2121
|
actions.push({
|
|
2068
2122
|
label: "Switch here",
|
|
2069
2123
|
hint: "s",
|
|
2124
|
+
group: "switch",
|
|
2125
|
+
icon: "\uEBCB",
|
|
2070
2126
|
run: () => {
|
|
2071
2127
|
const script = [
|
|
2072
2128
|
"delay 0.5",
|
|
@@ -2087,16 +2143,20 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
|
|
|
2087
2143
|
}
|
|
2088
2144
|
if (item.kind === "issue") {
|
|
2089
2145
|
actions.push({
|
|
2090
|
-
label: "Open
|
|
2146
|
+
label: "Open in new tab",
|
|
2091
2147
|
hint: "j",
|
|
2148
|
+
group: "switch",
|
|
2149
|
+
icon: "\uEAE4",
|
|
2092
2150
|
run: () => {
|
|
2093
2151
|
showFlash(`\u22EF Opening ${item.repo}\u2026`);
|
|
2094
2152
|
void jumpToRepo(item.repo, "", login).then(() => showFlash(`\u2197 Opened ${item.repo} in new tab`)).catch(() => showFlash("\u2717 Jump failed"));
|
|
2095
2153
|
}
|
|
2096
2154
|
});
|
|
2097
2155
|
actions.push({
|
|
2098
|
-
label: "Open
|
|
2156
|
+
label: "Open in new pane",
|
|
2099
2157
|
hint: "p",
|
|
2158
|
+
group: "switch",
|
|
2159
|
+
icon: "\uEB56",
|
|
2100
2160
|
run: () => {
|
|
2101
2161
|
showFlash(`\u22EF Opening pane for ${item.repo}\u2026`);
|
|
2102
2162
|
void jumpToRepoPane(item.repo, "", login).then(() => showFlash(`\u2197 Opened ${item.repo} in new pane`)).catch(() => showFlash("\u2717 Pane failed"));
|
|
@@ -2105,12 +2165,15 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
|
|
|
2105
2165
|
actions.push({
|
|
2106
2166
|
label: "Close issue",
|
|
2107
2167
|
hint: "",
|
|
2168
|
+
group: "close",
|
|
2169
|
+
icon: "\uF41D",
|
|
2108
2170
|
run: () => {
|
|
2109
2171
|
},
|
|
2110
2172
|
subActions: [
|
|
2111
2173
|
{
|
|
2112
2174
|
label: `Close #${item.number}`,
|
|
2113
2175
|
hint: "",
|
|
2176
|
+
group: "confirm",
|
|
2114
2177
|
run: () => {
|
|
2115
2178
|
onRemove?.(item);
|
|
2116
2179
|
showFlash(`\u2713 Closed #${item.number}`);
|
|
@@ -2129,7 +2192,7 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
|
|
|
2129
2192
|
);
|
|
2130
2193
|
}
|
|
2131
2194
|
},
|
|
2132
|
-
{ label: "Cancel", hint: "", run: () => {
|
|
2195
|
+
{ label: "Cancel", hint: "", group: "confirm", run: () => {
|
|
2133
2196
|
} }
|
|
2134
2197
|
]
|
|
2135
2198
|
});
|
|
@@ -2138,6 +2201,8 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
|
|
|
2138
2201
|
actions.push({
|
|
2139
2202
|
label: "Switch in new tab",
|
|
2140
2203
|
hint: "j",
|
|
2204
|
+
group: "switch",
|
|
2205
|
+
icon: "\uEAE4",
|
|
2141
2206
|
run: () => {
|
|
2142
2207
|
showFlash(`\u22EF Jumping to ${item.repo}\u2026`);
|
|
2143
2208
|
void jumpToRepo(item.repo, item.branch ?? "", login).then(() => showFlash(`\u2197 Opened ${item.repo} in new tab`)).catch(() => showFlash("\u2717 Jump failed"));
|
|
@@ -2146,6 +2211,8 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
|
|
|
2146
2211
|
actions.push({
|
|
2147
2212
|
label: "Switch in new pane",
|
|
2148
2213
|
hint: "p",
|
|
2214
|
+
group: "switch",
|
|
2215
|
+
icon: "\uEB56",
|
|
2149
2216
|
run: () => {
|
|
2150
2217
|
showFlash(`\u22EF Opening pane for ${item.repo}\u2026`);
|
|
2151
2218
|
void jumpToRepoPane(item.repo, item.branch ?? "", login).then(() => showFlash(`\u2197 Opened ${item.repo} in new pane`)).catch(() => showFlash("\u2717 Pane failed"));
|
|
@@ -2157,19 +2224,23 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
|
|
|
2157
2224
|
actions.push({
|
|
2158
2225
|
label: `Open ${jiraKey} in Jira`,
|
|
2159
2226
|
hint: "t",
|
|
2227
|
+
group: "open",
|
|
2160
2228
|
run: () => openInJira(jiraBase, jiraKey, showFlash)
|
|
2161
2229
|
});
|
|
2162
2230
|
}
|
|
2163
2231
|
}
|
|
2164
|
-
|
|
2232
|
+
closing.push({
|
|
2165
2233
|
label: "Close PR",
|
|
2166
2234
|
hint: "",
|
|
2235
|
+
group: "close",
|
|
2236
|
+
icon: "\uF4DC",
|
|
2167
2237
|
run: () => {
|
|
2168
2238
|
},
|
|
2169
2239
|
subActions: [
|
|
2170
2240
|
{
|
|
2171
2241
|
label: `Close #${item.number}`,
|
|
2172
2242
|
hint: "",
|
|
2243
|
+
group: "confirm",
|
|
2173
2244
|
run: () => {
|
|
2174
2245
|
onRemove?.(item);
|
|
2175
2246
|
showFlash(`\u2713 Closed #${item.number}`);
|
|
@@ -2188,20 +2259,25 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
|
|
|
2188
2259
|
);
|
|
2189
2260
|
}
|
|
2190
2261
|
},
|
|
2191
|
-
{ label: "Cancel", hint: "", run: () => {
|
|
2262
|
+
{ label: "Cancel", hint: "", group: "confirm", run: () => {
|
|
2192
2263
|
} }
|
|
2193
2264
|
]
|
|
2194
2265
|
});
|
|
2195
2266
|
if (item.branch) {
|
|
2196
|
-
|
|
2197
|
-
|
|
2267
|
+
closing.push({
|
|
2268
|
+
// "and", not "+": the row is a sentence like every other, and the key
|
|
2269
|
+
// column carries the hint — symbols are not shortcuts here.
|
|
2270
|
+
label: "Close PR and delete branch",
|
|
2198
2271
|
hint: "",
|
|
2272
|
+
group: "close",
|
|
2273
|
+
icon: "\uF48E",
|
|
2199
2274
|
run: () => {
|
|
2200
2275
|
},
|
|
2201
2276
|
subActions: [
|
|
2202
2277
|
{
|
|
2203
|
-
label: `Close #${item.number}
|
|
2278
|
+
label: `Close #${item.number} and delete ${item.branch}`,
|
|
2204
2279
|
hint: "",
|
|
2280
|
+
group: "confirm",
|
|
2205
2281
|
run: () => {
|
|
2206
2282
|
onRemove?.(item);
|
|
2207
2283
|
showFlash(`\u2713 Closed #${item.number} and deleted ${item.branch}`);
|
|
@@ -2213,16 +2289,20 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
|
|
|
2213
2289
|
});
|
|
2214
2290
|
}
|
|
2215
2291
|
},
|
|
2216
|
-
{ label: "Cancel", hint: "", run: () => {
|
|
2292
|
+
{ label: "Cancel", hint: "", group: "confirm", run: () => {
|
|
2217
2293
|
} }
|
|
2218
2294
|
]
|
|
2219
2295
|
});
|
|
2220
2296
|
}
|
|
2221
2297
|
}
|
|
2298
|
+
const extensionLabel = (e) => e.hint ? e.hint.charAt(0).toUpperCase() + e.hint.slice(1) : e.title;
|
|
2299
|
+
const extensionGroup = (e) => e.menuGroup === "act" ? "act" : e.menuGroup === "open" ? "open" : "quiet";
|
|
2222
2300
|
for (const e of itemExtensions(ext?.extensions))
|
|
2223
2301
|
actions.push({
|
|
2224
|
-
label: e
|
|
2302
|
+
label: extensionLabel(e),
|
|
2225
2303
|
hint: e.key,
|
|
2304
|
+
group: extensionGroup(e),
|
|
2305
|
+
icon: e.icon,
|
|
2226
2306
|
run: () => ext?.onOpenExt?.(e.id, {
|
|
2227
2307
|
item,
|
|
2228
2308
|
login,
|
|
@@ -2230,7 +2310,8 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
|
|
|
2230
2310
|
showFlash
|
|
2231
2311
|
})
|
|
2232
2312
|
});
|
|
2233
|
-
|
|
2313
|
+
actions.push(...closing);
|
|
2314
|
+
return byGroup(actions);
|
|
2234
2315
|
};
|
|
2235
2316
|
var agoText = (ms) => {
|
|
2236
2317
|
const d = (Date.now() - ms) / 1e3;
|
|
@@ -2517,26 +2598,60 @@ var ItemRow = ({
|
|
|
2517
2598
|
}
|
|
2518
2599
|
);
|
|
2519
2600
|
};
|
|
2601
|
+
var GROUP_RANK = {
|
|
2602
|
+
act: 0,
|
|
2603
|
+
open: 1,
|
|
2604
|
+
switch: 2,
|
|
2605
|
+
copy: 3,
|
|
2606
|
+
quiet: 4,
|
|
2607
|
+
close: 5,
|
|
2608
|
+
confirm: 6
|
|
2609
|
+
};
|
|
2610
|
+
var groupRank = (action) => action.group === void 0 ? 7 : GROUP_RANK[action.group];
|
|
2611
|
+
var menuRowsOf = (actions) => {
|
|
2612
|
+
const sorted = byGroup(actions);
|
|
2613
|
+
const rows = [];
|
|
2614
|
+
sorted.forEach((action, i) => {
|
|
2615
|
+
if (i > 0 && sorted[i - 1].group !== action.group) rows.push({ gap: true });
|
|
2616
|
+
rows.push(action);
|
|
2617
|
+
});
|
|
2618
|
+
return rows;
|
|
2619
|
+
};
|
|
2620
|
+
var byGroup = (actions) => [...actions].sort((a, b) => groupRank(a) - groupRank(b));
|
|
2621
|
+
var isConfirmRows = (rows) => rows.length > 0 && rows.every((row) => isMenuGap(row) || row.group === "confirm");
|
|
2622
|
+
var menuTone = (row) => row.group === "close" || row.group === "confirm" && row.label !== "Cancel" ? "error" : void 0;
|
|
2520
2623
|
var useActionMenu = () => {
|
|
2521
2624
|
const [actions, setActions] = useState(null);
|
|
2522
2625
|
const [cursor, setCursor] = useState(0);
|
|
2523
2626
|
const open = (next) => {
|
|
2524
2627
|
if (next.length === 0) return;
|
|
2525
2628
|
setCursor(0);
|
|
2526
|
-
setActions(next);
|
|
2629
|
+
setActions(menuRowsOf(next));
|
|
2527
2630
|
};
|
|
2528
2631
|
const handleKey = (key) => {
|
|
2529
2632
|
if (!actions) return false;
|
|
2530
|
-
if (key.upArrow)
|
|
2531
|
-
|
|
2633
|
+
if (key.upArrow)
|
|
2634
|
+
setCursor((c) => {
|
|
2635
|
+
let n = c - 1;
|
|
2636
|
+
while (n > 0 && isMenuGap(actions[n])) n -= 1;
|
|
2637
|
+
return isMenuGap(actions[n]) ? c : n;
|
|
2638
|
+
});
|
|
2639
|
+
if (key.downArrow)
|
|
2640
|
+
setCursor((c) => {
|
|
2641
|
+
let n = c + 1;
|
|
2642
|
+
while (n < actions.length - 1 && isMenuGap(actions[n])) n += 1;
|
|
2643
|
+
return isMenuGap(actions[n]) ? c : n;
|
|
2644
|
+
});
|
|
2532
2645
|
if (key.return) {
|
|
2533
|
-
const
|
|
2534
|
-
if (
|
|
2535
|
-
|
|
2536
|
-
|
|
2646
|
+
const row = actions[cursor];
|
|
2647
|
+
if (!row || isMenuGap(row)) return true;
|
|
2648
|
+
if (row.subActions) {
|
|
2649
|
+
const rows = menuRowsOf(row.subActions);
|
|
2650
|
+
setCursor(isConfirmRows(rows) ? rows.length - 1 : 0);
|
|
2651
|
+
setActions(rows);
|
|
2537
2652
|
} else {
|
|
2538
2653
|
setActions(null);
|
|
2539
|
-
|
|
2654
|
+
row.run();
|
|
2540
2655
|
}
|
|
2541
2656
|
}
|
|
2542
2657
|
if (key.escape) setActions(null);
|
|
@@ -2547,29 +2662,81 @@ var useActionMenu = () => {
|
|
|
2547
2662
|
var ActionMenu = ({
|
|
2548
2663
|
item,
|
|
2549
2664
|
actions,
|
|
2550
|
-
cursor
|
|
2665
|
+
cursor,
|
|
2666
|
+
cols = COLS
|
|
2551
2667
|
}) => {
|
|
2552
|
-
const
|
|
2668
|
+
const showIcons = getIconMode() === "nerd" && actions.some((row) => !isMenuGap(row) && row.icon !== void 0);
|
|
2669
|
+
const keyWidth = Math.max(
|
|
2670
|
+
0,
|
|
2671
|
+
...actions.flatMap(
|
|
2672
|
+
(row) => isMenuGap(row) ? [] : [[...row.hint ?? ""].length]
|
|
2673
|
+
)
|
|
2674
|
+
);
|
|
2675
|
+
const contentWidth = Math.max(
|
|
2676
|
+
0,
|
|
2677
|
+
...actions.flatMap(
|
|
2678
|
+
(row) => isMenuGap(row) ? [] : [
|
|
2679
|
+
2 + (showIcons ? 2 : 0) + [...row.label].length + (keyWidth > 0 ? 1 + keyWidth : 0)
|
|
2680
|
+
]
|
|
2681
|
+
)
|
|
2682
|
+
);
|
|
2683
|
+
const width = Math.min(
|
|
2684
|
+
Math.max(contentWidth + 4, 40),
|
|
2685
|
+
56,
|
|
2686
|
+
Math.max(20, cols - 4)
|
|
2687
|
+
);
|
|
2688
|
+
const inner = Math.max(1, width - 4);
|
|
2689
|
+
const confirming = isConfirmRows(actions);
|
|
2690
|
+
const isCloseRow = (row) => menuTone(row) === "error";
|
|
2691
|
+
const number = item.kind === "pr" || item.kind === "issue" ? item.number : null;
|
|
2692
|
+
const leadNum = item.kind === "task" ? item.key : number !== null ? `#${number}` : "";
|
|
2693
|
+
const lead = leadNum ? `${leadNum} \xB7 ` : "";
|
|
2694
|
+
const rest = confirming ? `Close #${number}?` : item.kind === "task" ? item.summary : item.kind === "pr" || item.kind === "issue" ? item.title : "";
|
|
2695
|
+
const restText = [...lead].length + [...rest].length <= inner ? rest : [...rest].slice(0, Math.max(0, inner - [...lead].length - 1)).join("") + "\u2026";
|
|
2553
2696
|
return /* @__PURE__ */ jsxs(
|
|
2554
2697
|
Box,
|
|
2555
2698
|
{
|
|
2556
2699
|
flexDirection: "column",
|
|
2557
2700
|
flexShrink: 0,
|
|
2701
|
+
width,
|
|
2558
2702
|
borderStyle: "round",
|
|
2559
2703
|
borderColor: colors.info,
|
|
2560
2704
|
backgroundColor: OVERLAY_BG,
|
|
2561
2705
|
paddingX: 1,
|
|
2562
2706
|
marginTop: 1,
|
|
2563
2707
|
children: [
|
|
2564
|
-
/* @__PURE__ */ jsx(Text3, { color: colors.
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
/* @__PURE__ */ jsx(Text3, {
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2708
|
+
confirming ? /* @__PURE__ */ jsx(Text3, { color: colors.error, bold: true, children: restText }) : /* @__PURE__ */ jsxs(Text3, { children: [
|
|
2709
|
+
/* @__PURE__ */ jsx(Text3, { color: colors.info, bold: true, children: leadNum }),
|
|
2710
|
+
leadNum ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: " \xB7 " }) : null,
|
|
2711
|
+
/* @__PURE__ */ jsx(Text3, { children: restText })
|
|
2712
|
+
] }),
|
|
2713
|
+
/* @__PURE__ */ jsx(Text3, { dimColor: true, children: "\u2500".repeat(inner) }),
|
|
2714
|
+
actions.map(
|
|
2715
|
+
(row, i) => isMenuGap(row) ? /* @__PURE__ */ jsx(Text3, { children: " " }, `gap-${i}`) : /* @__PURE__ */ jsxs(Box, { width: inner, children: [
|
|
2716
|
+
/* @__PURE__ */ jsx(Box, { width: 2, flexShrink: 0, children: /* @__PURE__ */ jsx(Text3, { color: colors.info, children: i === cursor ? "\u276F " : " " }) }),
|
|
2717
|
+
showIcons ? /* @__PURE__ */ jsx(Box, { width: 2, flexShrink: 0, children: row.icon ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: row.icon }) : /* @__PURE__ */ jsx(Text3, { children: " " }) }) : null,
|
|
2718
|
+
/* @__PURE__ */ jsx(Box, { flexGrow: 1, children: /* @__PURE__ */ jsx(
|
|
2719
|
+
Text3,
|
|
2720
|
+
{
|
|
2721
|
+
bold: i === cursor,
|
|
2722
|
+
color: isCloseRow(row) ? colors.error : void 0,
|
|
2723
|
+
children: row.label
|
|
2724
|
+
}
|
|
2725
|
+
) }),
|
|
2726
|
+
keyWidth > 0 ? /* @__PURE__ */ jsx(Box, { width: keyWidth, flexShrink: 0, justifyContent: "flex-end", children: /* @__PURE__ */ jsx(Text3, { color: colors.secondary, children: row.hint ?? "" }) }) : null
|
|
2727
|
+
] }, `${i}:${row.label}`)
|
|
2728
|
+
),
|
|
2729
|
+
/* @__PURE__ */ jsx(Text3, { dimColor: true, children: "\u2500".repeat(inner) }),
|
|
2730
|
+
/* @__PURE__ */ jsx(
|
|
2731
|
+
FooterHints,
|
|
2732
|
+
{
|
|
2733
|
+
hints: [
|
|
2734
|
+
["\u2191\u2193", "move"],
|
|
2735
|
+
["\u21B5", "run"],
|
|
2736
|
+
["esc", "close"]
|
|
2737
|
+
]
|
|
2738
|
+
}
|
|
2739
|
+
)
|
|
2573
2740
|
]
|
|
2574
2741
|
}
|
|
2575
2742
|
);
|
|
@@ -2845,6 +3012,7 @@ var BrowseScreen = ({
|
|
|
2845
3012
|
fetchedAt,
|
|
2846
3013
|
refreshError,
|
|
2847
3014
|
hidden,
|
|
3015
|
+
overlayBody,
|
|
2848
3016
|
peelRef,
|
|
2849
3017
|
onTyping,
|
|
2850
3018
|
onOpenPr,
|
|
@@ -3023,8 +3191,7 @@ var BrowseScreen = ({
|
|
|
3023
3191
|
}, [searchInput, palette, onTyping]);
|
|
3024
3192
|
const railCols = railWidth(COLS);
|
|
3025
3193
|
const listCols = showRail ? COLS - railCols : COLS;
|
|
3026
|
-
const trailingColumns =
|
|
3027
|
-
const numberCols = numberColumnsOf(labelBearing);
|
|
3194
|
+
const { trailingColumns, numberCols } = listColumnsOf(localSections);
|
|
3028
3195
|
const rowCols = Math.min(listCols, ROW_MAX_COLS);
|
|
3029
3196
|
const trailingShed = sectionShedOf(
|
|
3030
3197
|
section.items.flatMap(
|
|
@@ -3595,7 +3762,8 @@ var BrowseScreen = ({
|
|
|
3595
3762
|
}));
|
|
3596
3763
|
return { items, run, message };
|
|
3597
3764
|
}, [palette, activeItem, extensions, jiraBase, jiraKeyRe, jiraSetupHint]);
|
|
3598
|
-
|
|
3765
|
+
const chrome = useContext(InboxChromeContext);
|
|
3766
|
+
const overlayOpen = overlayBody != null;
|
|
3599
3767
|
const overlay = palette !== null ? /* @__PURE__ */ jsx(
|
|
3600
3768
|
CommandPalette,
|
|
3601
3769
|
{
|
|
@@ -3640,7 +3808,8 @@ var BrowseScreen = ({
|
|
|
3640
3808
|
{
|
|
3641
3809
|
item: activeItem,
|
|
3642
3810
|
actions: menu.actions,
|
|
3643
|
-
cursor: menu.cursor
|
|
3811
|
+
cursor: menu.cursor,
|
|
3812
|
+
cols: listCols
|
|
3644
3813
|
}
|
|
3645
3814
|
) : null;
|
|
3646
3815
|
return /* @__PURE__ */ jsxs(
|
|
@@ -3659,7 +3828,7 @@ var BrowseScreen = ({
|
|
|
3659
3828
|
brand,
|
|
3660
3829
|
sections: localSections,
|
|
3661
3830
|
login,
|
|
3662
|
-
scopeLabel: origin?.label,
|
|
3831
|
+
scopeLabel: overlayOpen ? chrome?.scope ?? origin?.label : origin?.label,
|
|
3663
3832
|
budgetLabel: budgetNotice(budget)?.label,
|
|
3664
3833
|
budgetCritical: budgetNotice(budget)?.critical,
|
|
3665
3834
|
refreshing,
|
|
@@ -3682,7 +3851,7 @@ var BrowseScreen = ({
|
|
|
3682
3851
|
/* @__PURE__ */ jsx(Text3, { bold: true, color: colors.accent, children: " \u26A1 auto-refresh paused to save API budget" }),
|
|
3683
3852
|
/* @__PURE__ */ jsx(Text3, { dimColor: true, children: " r refreshes anyway" })
|
|
3684
3853
|
] }) : null,
|
|
3685
|
-
/* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(
|
|
3854
|
+
overlayOpen ? null : /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(
|
|
3686
3855
|
Tabs,
|
|
3687
3856
|
{
|
|
3688
3857
|
active: section.id,
|
|
@@ -3715,18 +3884,24 @@ var BrowseScreen = ({
|
|
|
3715
3884
|
}))
|
|
3716
3885
|
}
|
|
3717
3886
|
) }),
|
|
3718
|
-
search != null ? /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
|
|
3887
|
+
!overlayOpen && search != null ? /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
|
|
3719
3888
|
/* @__PURE__ */ jsx(Text3, { color: colors.info, children: " / " }),
|
|
3720
3889
|
/* @__PURE__ */ jsx(Text3, { children: search }),
|
|
3721
3890
|
searchInput ? /* @__PURE__ */ jsx(Text3, { color: colors.info, children: "\u258F" }) : null,
|
|
3722
3891
|
/* @__PURE__ */ jsx(Text3, { dimColor: true, children: ` ${matchCount} match${matchCount !== 1 ? "es" : ""} ${filter.hints.map(([k, label]) => `${k} ${label}`).join(" \xB7 ")}` })
|
|
3723
|
-
] }) : repoFilter.size > 0 ? /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
|
|
3892
|
+
] }) : !overlayOpen && repoFilter.size > 0 ? /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
|
|
3724
3893
|
/* @__PURE__ */ jsx(Text3, { color: colors.accent, children: " \u25C9 " }),
|
|
3725
3894
|
/* @__PURE__ */ jsx(Text3, { children: `${repoFilter.size} repo${repoFilter.size !== 1 ? "s" : ""}` }),
|
|
3726
3895
|
/* @__PURE__ */ jsx(Text3, { dimColor: true, children: " f edit \xB7 esc clear" })
|
|
3727
3896
|
] }) : null,
|
|
3728
3897
|
/* @__PURE__ */ jsxs(Box, { children: [
|
|
3729
|
-
|
|
3898
|
+
overlayOpen ? (
|
|
3899
|
+
// The overlay owns this band: the same frame, the same header above
|
|
3900
|
+
// and footer below, the body where the list was. Fixed to the list's
|
|
3901
|
+
// own height so the frame holds still between browse and drill, and
|
|
3902
|
+
// clipped rather than spilling past the footer on a long body.
|
|
3903
|
+
/* @__PURE__ */ jsx(Box, { flexDirection: "column", height: listHeight, overflow: "hidden", children: overlayBody })
|
|
3904
|
+
) : /* @__PURE__ */ jsxs(
|
|
3730
3905
|
Box,
|
|
3731
3906
|
{
|
|
3732
3907
|
flexDirection: "column",
|
|
@@ -3786,7 +3961,7 @@ var BrowseScreen = ({
|
|
|
3786
3961
|
]
|
|
3787
3962
|
}
|
|
3788
3963
|
),
|
|
3789
|
-
showRail ? /* @__PURE__ */ jsx(
|
|
3964
|
+
showRail && !overlayOpen ? /* @__PURE__ */ jsx(
|
|
3790
3965
|
SidePanel,
|
|
3791
3966
|
{
|
|
3792
3967
|
sidebar: rails,
|
|
@@ -3798,7 +3973,20 @@ var BrowseScreen = ({
|
|
|
3798
3973
|
}
|
|
3799
3974
|
) : null
|
|
3800
3975
|
] }),
|
|
3801
|
-
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: flash ? /* @__PURE__ */ jsx(Text3, { color: colors.success, children: flash }) :
|
|
3976
|
+
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: flash ? /* @__PURE__ */ jsx(Text3, { color: colors.success, children: flash }) : overlayOpen ? (
|
|
3977
|
+
// The overlay's own verbs, plus the way back. `page="nested"` is what
|
|
3978
|
+
// appends `⌫ back` and `q quit`, so a body passes only its own keys
|
|
3979
|
+
// through `useChrome` — the same contract ink-ui's `Page` offers its
|
|
3980
|
+
// nested pages, without re-laying-out the browse frame as one.
|
|
3981
|
+
/* @__PURE__ */ jsx(
|
|
3982
|
+
FooterHints,
|
|
3983
|
+
{
|
|
3984
|
+
hints: chrome?.hints ?? [],
|
|
3985
|
+
page: "nested",
|
|
3986
|
+
help: false
|
|
3987
|
+
}
|
|
3988
|
+
)
|
|
3989
|
+
) : /* @__PURE__ */ jsx(FooterHints, { hints }) })
|
|
3802
3990
|
]
|
|
3803
3991
|
}
|
|
3804
3992
|
);
|
|
@@ -3865,6 +4053,15 @@ var App = ({
|
|
|
3865
4053
|
const browsePeel = useRef(null);
|
|
3866
4054
|
const drillPeel = useRef(null);
|
|
3867
4055
|
const [typing, setTyping] = useState(false);
|
|
4056
|
+
const [chromeSpec, setChromeSpec] = useState({});
|
|
4057
|
+
const setChrome = useCallback(
|
|
4058
|
+
(next) => setChromeSpec((prev) => sameChrome(prev, next) ? prev : next),
|
|
4059
|
+
[]
|
|
4060
|
+
);
|
|
4061
|
+
const chromeValue = useMemo(
|
|
4062
|
+
() => ({ scope: chromeSpec.scope, hints: chromeSpec.hints, setChrome }),
|
|
4063
|
+
[chromeSpec, setChrome]
|
|
4064
|
+
);
|
|
3868
4065
|
useAppKeys({
|
|
3869
4066
|
isActive: !typing,
|
|
3870
4067
|
onBack: () => {
|
|
@@ -4228,7 +4425,10 @@ var App = ({
|
|
|
4228
4425
|
extId: state.extId,
|
|
4229
4426
|
target: state.target
|
|
4230
4427
|
} : null;
|
|
4231
|
-
const toBrowse = () =>
|
|
4428
|
+
const toBrowse = () => {
|
|
4429
|
+
setChromeSpec({});
|
|
4430
|
+
setState({ phase: "browse", sections: state.sections, login: state.login });
|
|
4431
|
+
};
|
|
4232
4432
|
const markLeaving = (target) => {
|
|
4233
4433
|
if (state.phase !== "browse") toBrowse();
|
|
4234
4434
|
if (!leavingSince.current.has(target.url))
|
|
@@ -4256,82 +4456,89 @@ var App = ({
|
|
|
4256
4456
|
);
|
|
4257
4457
|
}, MERGED_HOLD_MS);
|
|
4258
4458
|
};
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4459
|
+
const overlayBody = overlay?.kind === "pr" || overlay?.kind === "issue" ? detailFor?.({
|
|
4460
|
+
item: overlay.item,
|
|
4461
|
+
kind: overlay.kind,
|
|
4462
|
+
login: state.login,
|
|
4463
|
+
registerPeel: (peel) => {
|
|
4464
|
+
drillPeel.current = peel;
|
|
4465
|
+
},
|
|
4466
|
+
onTyping: setTyping,
|
|
4467
|
+
onBack: toBrowse,
|
|
4468
|
+
onRefresh: applyOrRefresh,
|
|
4469
|
+
onRemove: markLeaving,
|
|
4470
|
+
onMerged: markMerged
|
|
4471
|
+
}) : overlay?.kind === "ext" ? extensions?.find((e) => e.id === overlay.extId)?.body(toBrowse, overlay.target) : null;
|
|
4472
|
+
return /* @__PURE__ */ jsx(InboxChromeContext.Provider, { value: chromeValue, children: /* @__PURE__ */ jsx(
|
|
4473
|
+
BrowseScreen,
|
|
4474
|
+
{
|
|
4475
|
+
brand: brandOf(title),
|
|
4476
|
+
sections: state.sections,
|
|
4477
|
+
login: state.login,
|
|
4478
|
+
jiraBase,
|
|
4479
|
+
jiraKeyRe,
|
|
4480
|
+
jiraSetupHint,
|
|
4481
|
+
jiraTransitions,
|
|
4482
|
+
onRefresh: applyOrRefresh,
|
|
4483
|
+
onActed,
|
|
4484
|
+
onSettled,
|
|
4485
|
+
refreshing,
|
|
4486
|
+
hasPending: pending !== null,
|
|
4487
|
+
pendingSummary,
|
|
4488
|
+
appliedSummary: transients.size > 0 ? appliedSummary : "",
|
|
4489
|
+
fetchedAt,
|
|
4490
|
+
refreshError: refreshError ?? void 0,
|
|
4491
|
+
origin,
|
|
4492
|
+
budget,
|
|
4493
|
+
skippedForBudget,
|
|
4494
|
+
hidden: overlay !== null,
|
|
4495
|
+
overlayBody: overlayBody ?? void 0,
|
|
4496
|
+
peelRef: browsePeel,
|
|
4497
|
+
onTyping: setTyping,
|
|
4498
|
+
mergedUrls,
|
|
4499
|
+
leavingUrls,
|
|
4500
|
+
transitSince,
|
|
4501
|
+
onLeave: markLeaving,
|
|
4502
|
+
transients,
|
|
4503
|
+
onTabChange: setVisibleTab,
|
|
4504
|
+
sidebar,
|
|
4505
|
+
liveLabel,
|
|
4506
|
+
ciStatusState: hasCiStatus ? ciStatusState : void 0,
|
|
4507
|
+
ciJob,
|
|
4508
|
+
stripState: hasStrip ? stripState : void 0,
|
|
4509
|
+
stripLabel,
|
|
4510
|
+
tabHelp,
|
|
4511
|
+
onOpenPr: (item) => {
|
|
4512
|
+
setChromeSpec({});
|
|
4513
|
+
setState({
|
|
4299
4514
|
phase: "pr",
|
|
4300
4515
|
item,
|
|
4301
4516
|
sections: state.sections,
|
|
4302
4517
|
login: state.login
|
|
4303
|
-
})
|
|
4304
|
-
|
|
4518
|
+
});
|
|
4519
|
+
},
|
|
4520
|
+
onOpenIssue: (item) => {
|
|
4521
|
+
setChromeSpec({});
|
|
4522
|
+
setState({
|
|
4305
4523
|
phase: "issue",
|
|
4306
4524
|
item,
|
|
4307
4525
|
sections: state.sections,
|
|
4308
4526
|
login: state.login
|
|
4309
|
-
})
|
|
4310
|
-
|
|
4527
|
+
});
|
|
4528
|
+
},
|
|
4529
|
+
onOpenExt: (id, target) => {
|
|
4530
|
+
setChromeSpec({});
|
|
4531
|
+
setState({
|
|
4311
4532
|
phase: "ext",
|
|
4312
4533
|
extId: id,
|
|
4313
4534
|
target,
|
|
4314
4535
|
sections: state.sections,
|
|
4315
4536
|
login: state.login
|
|
4316
|
-
})
|
|
4317
|
-
extensions
|
|
4318
|
-
}
|
|
4319
|
-
),
|
|
4320
|
-
(overlay?.kind === "pr" || overlay?.kind === "issue") && detailFor?.({
|
|
4321
|
-
item: overlay.item,
|
|
4322
|
-
kind: overlay.kind,
|
|
4323
|
-
login: state.login,
|
|
4324
|
-
registerPeel: (peel) => {
|
|
4325
|
-
drillPeel.current = peel;
|
|
4537
|
+
});
|
|
4326
4538
|
},
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
onRemove: markLeaving,
|
|
4331
|
-
onMerged: markMerged
|
|
4332
|
-
}),
|
|
4333
|
-
overlay?.kind === "ext" && extensions?.find((e) => e.id === overlay.extId)?.body(toBrowse, overlay.target)
|
|
4334
|
-
] });
|
|
4539
|
+
extensions
|
|
4540
|
+
}
|
|
4541
|
+
) });
|
|
4335
4542
|
};
|
|
4336
4543
|
|
|
4337
4544
|
// src/inbox/filter.ts
|
|
@@ -4348,4 +4555,4 @@ var matchesFilter = (repo, filter) => {
|
|
|
4348
4555
|
};
|
|
4349
4556
|
var parsePatterns = (value) => value.split(",").map((p) => p.trim()).filter(Boolean);
|
|
4350
4557
|
|
|
4351
|
-
export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, HealthPanel, MIN_SIDEBAR_COLS, PrRow, SIDEBAR_COLS, SidePanel, StatusStripLine, ageLabel, budgetNotice, buildActions, buildCheckoutCmd, clipboard, counts, drillCmd, explainGhAction, explainItem, fitCount, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, itermRun, jumpToRepo, jumpToRepoPane, matchesFilter, maxViewStart, moveCursor, numberColumnsOf, openInTab, parsePatterns, railCapacity, railHeights, railRowsOf, railWidth, railsOf, readCache, renderMarkdown, reposInSections, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, sectionShedOf, signatureOf, stripGlyph, stripLayout, stripSignature, toCiStatusState, trailingColumnsOf, truncate, useActionMenu, windowCount, withHeaders, withoutItem, workCount, writeCache };
|
|
4558
|
+
export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, HealthPanel, MIN_SIDEBAR_COLS, PrRow, SIDEBAR_COLS, SidePanel, StatusStripLine, ageLabel, budgetNotice, buildActions, buildCheckoutCmd, clipboard, counts, drillCmd, explainGhAction, explainItem, fitCount, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, isMenuGap, itermRun, jumpToRepo, jumpToRepoPane, matchesFilter, maxViewStart, menuRowsOf, menuTone, moveCursor, numberColumnsOf, openInTab, parsePatterns, railCapacity, railHeights, railRowsOf, railWidth, railsOf, readCache, renderMarkdown, reposInSections, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, sectionShedOf, signatureOf, stripGlyph, stripLayout, stripSignature, toCiStatusState, trailingColumnsOf, truncate, useActionMenu, useChrome, windowCount, withHeaders, withoutItem, workCount, writeCache };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kud/gh-ink",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.67.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",
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
"react": ">=19"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@kud/gh": "0.
|
|
52
|
-
"@kud/gh-workflow": "0.14.
|
|
51
|
+
"@kud/gh": "0.20.0",
|
|
52
|
+
"@kud/gh-workflow": "0.14.1",
|
|
53
53
|
"@kud/ink-ui": "0.33.0",
|
|
54
54
|
"zx": "8.8.5"
|
|
55
55
|
},
|