@kitlangton/ghui 0.3.0 → 0.3.3
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 +13 -3
- package/bin/ghui.js +6 -1
- package/dist/index.js +9456 -0
- package/package.json +7 -4
- package/src/App.tsx +0 -2779
- package/src/appCommands.ts +0 -409
- package/src/commands.ts +0 -73
- package/src/config.ts +0 -20
- package/src/date.ts +0 -20
- package/src/domain.ts +0 -149
- package/src/errors.ts +0 -10
- package/src/index.tsx +0 -50
- package/src/keyboard/opentuiAdapter.ts +0 -43
- package/src/keymap/all.ts +0 -116
- package/src/keymap/changedFilesModal.ts +0 -23
- package/src/keymap/closeModal.ts +0 -13
- package/src/keymap/commandPalette.ts +0 -16
- package/src/keymap/commentModal.ts +0 -73
- package/src/keymap/commentThreadModal.ts +0 -24
- package/src/keymap/detailView.ts +0 -30
- package/src/keymap/diffView.ts +0 -93
- package/src/keymap/filterMode.ts +0 -13
- package/src/keymap/helpers.ts +0 -24
- package/src/keymap/labelModal.ts +0 -16
- package/src/keymap/listNav.ts +0 -119
- package/src/keymap/mergeModal.ts +0 -23
- package/src/keymap/openRepositoryModal.ts +0 -13
- package/src/keymap/submitReviewModal.ts +0 -48
- package/src/keymap/themeModal.ts +0 -49
- package/src/mergeActions.ts +0 -88
- package/src/observability.ts +0 -46
- package/src/pullRequestCache.ts +0 -19
- package/src/pullRequestViews.ts +0 -43
- package/src/services/BrowserOpener.ts +0 -22
- package/src/services/Clipboard.ts +0 -46
- package/src/services/CommandRunner.ts +0 -91
- package/src/services/GitHubService.ts +0 -756
- package/src/services/MockGitHubService.ts +0 -182
- package/src/themeStore.ts +0 -60
- package/src/ui/CommandPalette.tsx +0 -176
- package/src/ui/DetailsPane.tsx +0 -656
- package/src/ui/FooterHints.tsx +0 -90
- package/src/ui/LoadingLogo.tsx +0 -75
- package/src/ui/PullRequestDiffPane.tsx +0 -249
- package/src/ui/PullRequestList.tsx +0 -194
- package/src/ui/colors.ts +0 -821
- package/src/ui/commentEditor.ts +0 -126
- package/src/ui/comments.tsx +0 -143
- package/src/ui/diff.ts +0 -650
- package/src/ui/diffStats.tsx +0 -25
- package/src/ui/modals.tsx +0 -964
- package/src/ui/primitives.tsx +0 -350
- package/src/ui/pullRequests.ts +0 -106
- package/src/ui/singleLineInput.ts +0 -26
- package/src/ui/spinner.ts +0 -1
package/src/appCommands.ts
DELETED
|
@@ -1,409 +0,0 @@
|
|
|
1
|
-
import type { AppCommand } from "./commands.js"
|
|
2
|
-
import { defineCommand } from "./commands.js"
|
|
3
|
-
import type { LoadStatus, PullRequestItem } from "./domain.js"
|
|
4
|
-
import type { DiffView, DiffWhitespaceMode, DiffWrapMode } from "./ui/diff.js"
|
|
5
|
-
import { type PullRequestView, viewEquals, viewLabel, viewMode } from "./pullRequestViews.js"
|
|
6
|
-
|
|
7
|
-
interface AppCommandActions {
|
|
8
|
-
readonly openCommandPalette: () => void
|
|
9
|
-
readonly refreshPullRequests: (message?: string, options?: { readonly resetTransientState?: boolean }) => void
|
|
10
|
-
readonly openFilter: () => void
|
|
11
|
-
readonly clearFilter: () => void
|
|
12
|
-
readonly openThemeModal: () => void
|
|
13
|
-
readonly openRepositoryPicker: () => void
|
|
14
|
-
readonly loadMorePullRequests: () => void
|
|
15
|
-
readonly switchViewTo: (view: PullRequestView) => void
|
|
16
|
-
readonly openDetails: () => void
|
|
17
|
-
readonly closeDetails: () => void
|
|
18
|
-
readonly openDiffView: () => void
|
|
19
|
-
readonly closeDiffView: () => void
|
|
20
|
-
readonly reloadDiff: () => void
|
|
21
|
-
readonly toggleDiffRenderView: () => void
|
|
22
|
-
readonly toggleDiffWrapMode: () => void
|
|
23
|
-
readonly toggleDiffWhitespaceMode: () => void
|
|
24
|
-
readonly openChangedFilesModal: () => void
|
|
25
|
-
readonly jumpDiffFile: (delta: 1 | -1) => void
|
|
26
|
-
readonly openSelectedDiffComment: () => void
|
|
27
|
-
readonly toggleDiffCommentRange: () => void
|
|
28
|
-
readonly moveDiffCommentThread: (delta: 1 | -1) => void
|
|
29
|
-
readonly openDiffCommentModal: () => void
|
|
30
|
-
readonly openSubmitReviewModal: () => void
|
|
31
|
-
readonly togglePullRequestDraftStatus: () => void
|
|
32
|
-
readonly openLabelModal: () => void
|
|
33
|
-
readonly openMergeModal: () => void
|
|
34
|
-
readonly openCloseModal: () => void
|
|
35
|
-
readonly openPullRequestInBrowser: () => void
|
|
36
|
-
readonly copyPullRequestMetadata: () => void
|
|
37
|
-
readonly quit: () => void
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
interface BuildAppCommandsInput {
|
|
41
|
-
readonly pullRequestStatus: LoadStatus
|
|
42
|
-
readonly filterQuery: string
|
|
43
|
-
readonly filterMode: boolean
|
|
44
|
-
readonly selectedRepository: string | null
|
|
45
|
-
readonly activeViews: readonly PullRequestView[]
|
|
46
|
-
readonly activeView: PullRequestView
|
|
47
|
-
readonly loadedPullRequestCount: number
|
|
48
|
-
readonly hasMorePullRequests: boolean
|
|
49
|
-
readonly isLoadingMorePullRequests: boolean
|
|
50
|
-
readonly selectedPullRequest: PullRequestItem | null
|
|
51
|
-
readonly detailFullView: boolean
|
|
52
|
-
readonly diffFullView: boolean
|
|
53
|
-
readonly diffReady: boolean
|
|
54
|
-
readonly effectiveDiffRenderView: DiffView
|
|
55
|
-
readonly diffWrapMode: DiffWrapMode
|
|
56
|
-
readonly diffWhitespaceMode: DiffWhitespaceMode
|
|
57
|
-
readonly readyDiffFileCount: number
|
|
58
|
-
readonly diffFileIndex: number
|
|
59
|
-
readonly diffRangeActive: boolean
|
|
60
|
-
readonly selectedDiffCommentAnchorLabel: string | null
|
|
61
|
-
readonly selectedDiffCommentThreadCount: number
|
|
62
|
-
readonly hasDiffCommentThreads: boolean
|
|
63
|
-
readonly actions: AppCommandActions
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export const buildAppCommands = ({
|
|
67
|
-
pullRequestStatus,
|
|
68
|
-
filterQuery,
|
|
69
|
-
filterMode,
|
|
70
|
-
selectedRepository,
|
|
71
|
-
activeViews,
|
|
72
|
-
activeView,
|
|
73
|
-
loadedPullRequestCount,
|
|
74
|
-
hasMorePullRequests,
|
|
75
|
-
isLoadingMorePullRequests,
|
|
76
|
-
selectedPullRequest,
|
|
77
|
-
detailFullView,
|
|
78
|
-
diffFullView,
|
|
79
|
-
diffReady,
|
|
80
|
-
effectiveDiffRenderView,
|
|
81
|
-
diffWrapMode,
|
|
82
|
-
diffWhitespaceMode,
|
|
83
|
-
readyDiffFileCount,
|
|
84
|
-
diffFileIndex,
|
|
85
|
-
diffRangeActive,
|
|
86
|
-
selectedDiffCommentAnchorLabel,
|
|
87
|
-
selectedDiffCommentThreadCount,
|
|
88
|
-
hasDiffCommentThreads,
|
|
89
|
-
actions,
|
|
90
|
-
}: BuildAppCommandsInput): readonly AppCommand[] => {
|
|
91
|
-
const selectedPullRequestLabel = selectedPullRequest ? `#${selectedPullRequest.number} ${selectedPullRequest.repository}` : "No pull request selected"
|
|
92
|
-
const noPullRequestReason = selectedPullRequest ? null : "Select a pull request first."
|
|
93
|
-
const noOpenPullRequestReason = selectedPullRequest?.state === "open" ? null : selectedPullRequest ? "Pull request is not open." : noPullRequestReason
|
|
94
|
-
const diffReadyReason = selectedPullRequest
|
|
95
|
-
? diffReady ? null : "Load the diff before running this command."
|
|
96
|
-
: noPullRequestReason
|
|
97
|
-
const diffOpenReadyReason = diffFullView ? diffReadyReason : "Open a diff first."
|
|
98
|
-
const selectedDiffLineReason = diffFullView && diffReady
|
|
99
|
-
? selectedDiffCommentAnchorLabel ? null : "No diff line selected."
|
|
100
|
-
: diffOpenReadyReason
|
|
101
|
-
const diffThreadReason = diffFullView && diffReady
|
|
102
|
-
? hasDiffCommentThreads ? null : "No diff comments loaded."
|
|
103
|
-
: diffOpenReadyReason
|
|
104
|
-
const changedFilesReason = diffFullView && diffReady
|
|
105
|
-
? readyDiffFileCount > 0 ? null : "No changed files loaded."
|
|
106
|
-
: diffOpenReadyReason
|
|
107
|
-
const submitReviewReason = diffFullView && diffReady ? noOpenPullRequestReason : diffOpenReadyReason
|
|
108
|
-
const loadMoreDisabledReason = isLoadingMorePullRequests
|
|
109
|
-
? "Already loading more pull requests."
|
|
110
|
-
: hasMorePullRequests ? null : "No more pull requests loaded by this view."
|
|
111
|
-
|
|
112
|
-
const forSelected = (
|
|
113
|
-
command: Omit<AppCommand, "subtitle" | "disabledReason"> & { readonly requireOpen?: boolean },
|
|
114
|
-
): AppCommand => {
|
|
115
|
-
const { requireOpen, ...rest } = command
|
|
116
|
-
return defineCommand({
|
|
117
|
-
...rest,
|
|
118
|
-
subtitle: selectedPullRequestLabel,
|
|
119
|
-
disabledReason: requireOpen ? noOpenPullRequestReason : noPullRequestReason,
|
|
120
|
-
})
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return [
|
|
124
|
-
defineCommand({
|
|
125
|
-
id: "command.open",
|
|
126
|
-
title: "Open command palette",
|
|
127
|
-
scope: "Global",
|
|
128
|
-
subtitle: "Search every available route through ghui",
|
|
129
|
-
shortcut: "ctrl-p/cmd-k",
|
|
130
|
-
keywords: ["palette", "commands", "deck"],
|
|
131
|
-
run: actions.openCommandPalette,
|
|
132
|
-
}),
|
|
133
|
-
defineCommand({
|
|
134
|
-
id: "pull.refresh",
|
|
135
|
-
title: pullRequestStatus === "error" ? "Retry loading pull requests" : "Refresh pull requests",
|
|
136
|
-
scope: "Global",
|
|
137
|
-
subtitle: "Fetch the latest queue from GitHub",
|
|
138
|
-
shortcut: "r",
|
|
139
|
-
keywords: ["reload", "sync"],
|
|
140
|
-
run: () => actions.refreshPullRequests("Refreshed", { resetTransientState: true }),
|
|
141
|
-
}),
|
|
142
|
-
defineCommand({
|
|
143
|
-
id: "filter.open",
|
|
144
|
-
title: "Filter pull requests",
|
|
145
|
-
scope: "Global",
|
|
146
|
-
subtitle: "Search the visible queue",
|
|
147
|
-
shortcut: "/",
|
|
148
|
-
keywords: ["search"],
|
|
149
|
-
run: actions.openFilter,
|
|
150
|
-
}),
|
|
151
|
-
defineCommand({
|
|
152
|
-
id: "filter.clear",
|
|
153
|
-
title: "Clear pull request filter",
|
|
154
|
-
scope: "Global",
|
|
155
|
-
subtitle: "Show every pull request in the current queue",
|
|
156
|
-
shortcut: "esc",
|
|
157
|
-
disabledReason: filterQuery.length > 0 || filterMode ? null : "No filter is active.",
|
|
158
|
-
run: actions.clearFilter,
|
|
159
|
-
}),
|
|
160
|
-
defineCommand({
|
|
161
|
-
id: "theme.open",
|
|
162
|
-
title: "Choose theme",
|
|
163
|
-
scope: "Global",
|
|
164
|
-
subtitle: "Preview and persist a terminal color theme",
|
|
165
|
-
shortcut: "t",
|
|
166
|
-
keywords: ["colors", "appearance"],
|
|
167
|
-
run: actions.openThemeModal,
|
|
168
|
-
}),
|
|
169
|
-
defineCommand({
|
|
170
|
-
id: "repository.open",
|
|
171
|
-
title: "Open repository...",
|
|
172
|
-
scope: "View",
|
|
173
|
-
subtitle: selectedRepository ? `Current repository: ${selectedRepository}` : "Enter owner/name or a GitHub URL",
|
|
174
|
-
keywords: ["repo", "repository", "owner", "github"],
|
|
175
|
-
run: actions.openRepositoryPicker,
|
|
176
|
-
}),
|
|
177
|
-
...activeViews.map((view) => defineCommand({
|
|
178
|
-
id: view._tag === "Repository" ? "view.repository" : `view.${view.mode}`,
|
|
179
|
-
title: `Show ${viewLabel(view)} view`,
|
|
180
|
-
scope: "View" as const,
|
|
181
|
-
subtitle: viewEquals(view, activeView) ? "Already showing this view" : "Switch pull request view",
|
|
182
|
-
keywords: [viewMode(view), viewLabel(view), "queue", "view"],
|
|
183
|
-
disabledReason: viewEquals(view, activeView) ? "Already showing this view." : null,
|
|
184
|
-
run: () => actions.switchViewTo(view),
|
|
185
|
-
})),
|
|
186
|
-
defineCommand({
|
|
187
|
-
id: "pull.load-more",
|
|
188
|
-
title: "Load more pull requests",
|
|
189
|
-
scope: "Navigation",
|
|
190
|
-
subtitle: `${loadedPullRequestCount} loaded`,
|
|
191
|
-
disabledReason: loadMoreDisabledReason,
|
|
192
|
-
keywords: ["next page", "pagination", "more"],
|
|
193
|
-
run: actions.loadMorePullRequests,
|
|
194
|
-
}),
|
|
195
|
-
forSelected({
|
|
196
|
-
id: "detail.open",
|
|
197
|
-
title: "Open pull request details",
|
|
198
|
-
scope: "Pull request",
|
|
199
|
-
shortcut: "enter",
|
|
200
|
-
run: actions.openDetails,
|
|
201
|
-
}),
|
|
202
|
-
defineCommand({
|
|
203
|
-
id: "detail.close",
|
|
204
|
-
title: "Close details view",
|
|
205
|
-
scope: "Pull request",
|
|
206
|
-
subtitle: "Return to the queue",
|
|
207
|
-
shortcut: "esc",
|
|
208
|
-
disabledReason: detailFullView ? null : "Details view is not open.",
|
|
209
|
-
run: actions.closeDetails,
|
|
210
|
-
}),
|
|
211
|
-
forSelected({
|
|
212
|
-
id: "diff.open",
|
|
213
|
-
title: "Open diff",
|
|
214
|
-
scope: "Diff",
|
|
215
|
-
shortcut: "d",
|
|
216
|
-
keywords: ["files", "patch"],
|
|
217
|
-
run: actions.openDiffView,
|
|
218
|
-
}),
|
|
219
|
-
defineCommand({
|
|
220
|
-
id: "diff.close",
|
|
221
|
-
title: "Close diff view",
|
|
222
|
-
scope: "Diff",
|
|
223
|
-
subtitle: "Return to the queue or detail view",
|
|
224
|
-
shortcut: "esc",
|
|
225
|
-
disabledReason: diffFullView ? null : "Diff view is not open.",
|
|
226
|
-
run: actions.closeDiffView,
|
|
227
|
-
}),
|
|
228
|
-
defineCommand({
|
|
229
|
-
id: "diff.reload",
|
|
230
|
-
title: "Reload diff",
|
|
231
|
-
scope: "Diff",
|
|
232
|
-
subtitle: selectedPullRequestLabel,
|
|
233
|
-
shortcut: "r",
|
|
234
|
-
disabledReason: diffFullView && selectedPullRequest ? null : "Open a pull request diff first.",
|
|
235
|
-
keywords: ["refresh", "comments"],
|
|
236
|
-
run: actions.reloadDiff,
|
|
237
|
-
}),
|
|
238
|
-
defineCommand({
|
|
239
|
-
id: "diff.toggle-view",
|
|
240
|
-
title: "Toggle diff split/unified view",
|
|
241
|
-
scope: "Diff",
|
|
242
|
-
subtitle: effectiveDiffRenderView === "split" ? "Switch to unified view" : "Switch to split view",
|
|
243
|
-
shortcut: "shift-v",
|
|
244
|
-
disabledReason: diffFullView ? null : "Open a diff first.",
|
|
245
|
-
run: actions.toggleDiffRenderView,
|
|
246
|
-
}),
|
|
247
|
-
defineCommand({
|
|
248
|
-
id: "diff.toggle-wrap",
|
|
249
|
-
title: "Toggle diff word wrap",
|
|
250
|
-
scope: "Diff",
|
|
251
|
-
subtitle: diffWrapMode === "none" ? "Wrap long diff lines" : "Keep diff lines unwrapped",
|
|
252
|
-
shortcut: "w",
|
|
253
|
-
disabledReason: diffFullView ? null : "Open a diff first.",
|
|
254
|
-
run: actions.toggleDiffWrapMode,
|
|
255
|
-
}),
|
|
256
|
-
defineCommand({
|
|
257
|
-
id: "diff.toggle-whitespace",
|
|
258
|
-
title: diffWhitespaceMode === "ignore" ? "Show whitespace changes" : "Ignore whitespace changes",
|
|
259
|
-
scope: "Diff",
|
|
260
|
-
subtitle: diffWhitespaceMode === "ignore" ? "Display the original GitHub patch" : "Hide whitespace-only line changes",
|
|
261
|
-
disabledReason: diffFullView ? null : "Open a diff first.",
|
|
262
|
-
keywords: ["whitespace", "spacing", "ignore", "show"],
|
|
263
|
-
run: actions.toggleDiffWhitespaceMode,
|
|
264
|
-
}),
|
|
265
|
-
defineCommand({
|
|
266
|
-
id: "diff.changed-files",
|
|
267
|
-
title: "Open changed files navigator",
|
|
268
|
-
scope: "Diff",
|
|
269
|
-
subtitle: readyDiffFileCount > 0 ? `${readyDiffFileCount} changed files` : "No diff files loaded",
|
|
270
|
-
shortcut: "f",
|
|
271
|
-
disabledReason: changedFilesReason,
|
|
272
|
-
keywords: ["files", "navigator", "search"],
|
|
273
|
-
run: actions.openChangedFilesModal,
|
|
274
|
-
}),
|
|
275
|
-
defineCommand({
|
|
276
|
-
id: "diff.next-file",
|
|
277
|
-
title: "Next diff file",
|
|
278
|
-
scope: "Diff",
|
|
279
|
-
subtitle: readyDiffFileCount > 0 ? `${diffFileIndex + 1}/${readyDiffFileCount}` : "No diff files loaded",
|
|
280
|
-
shortcut: "]",
|
|
281
|
-
disabledReason: changedFilesReason,
|
|
282
|
-
run: () => actions.jumpDiffFile(1),
|
|
283
|
-
}),
|
|
284
|
-
defineCommand({
|
|
285
|
-
id: "diff.previous-file",
|
|
286
|
-
title: "Previous diff file",
|
|
287
|
-
scope: "Diff",
|
|
288
|
-
subtitle: readyDiffFileCount > 0 ? `${diffFileIndex + 1}/${readyDiffFileCount}` : "No diff files loaded",
|
|
289
|
-
shortcut: "[",
|
|
290
|
-
disabledReason: changedFilesReason,
|
|
291
|
-
run: () => actions.jumpDiffFile(-1),
|
|
292
|
-
}),
|
|
293
|
-
defineCommand({
|
|
294
|
-
id: "diff.open-comment-target",
|
|
295
|
-
title: selectedDiffCommentThreadCount > 0 ? "Open selected diff thread" : "Comment on selected diff line",
|
|
296
|
-
scope: "Diff",
|
|
297
|
-
subtitle: selectedDiffCommentAnchorLabel ?? "No diff line selected",
|
|
298
|
-
shortcut: "enter",
|
|
299
|
-
disabledReason: selectedDiffLineReason,
|
|
300
|
-
keywords: ["review", "comment", "thread", "line"],
|
|
301
|
-
run: actions.openSelectedDiffComment,
|
|
302
|
-
}),
|
|
303
|
-
defineCommand({
|
|
304
|
-
id: "diff.toggle-range",
|
|
305
|
-
title: diffRangeActive ? "Clear diff comment range" : "Start diff comment range",
|
|
306
|
-
scope: "Diff",
|
|
307
|
-
subtitle: selectedDiffCommentAnchorLabel ?? "No diff line selected",
|
|
308
|
-
shortcut: "v",
|
|
309
|
-
disabledReason: selectedDiffLineReason,
|
|
310
|
-
keywords: ["review", "comment", "range", "visual"],
|
|
311
|
-
run: actions.toggleDiffCommentRange,
|
|
312
|
-
}),
|
|
313
|
-
defineCommand({
|
|
314
|
-
id: "diff.next-thread",
|
|
315
|
-
title: "Next diff thread",
|
|
316
|
-
scope: "Diff",
|
|
317
|
-
subtitle: hasDiffCommentThreads ? "Jump to the next commented line" : "No diff comments loaded",
|
|
318
|
-
shortcut: "n",
|
|
319
|
-
disabledReason: diffThreadReason,
|
|
320
|
-
keywords: ["review", "comment", "thread"],
|
|
321
|
-
run: () => actions.moveDiffCommentThread(1),
|
|
322
|
-
}),
|
|
323
|
-
defineCommand({
|
|
324
|
-
id: "diff.previous-thread",
|
|
325
|
-
title: "Previous diff thread",
|
|
326
|
-
scope: "Diff",
|
|
327
|
-
subtitle: hasDiffCommentThreads ? "Jump to the previous commented line" : "No diff comments loaded",
|
|
328
|
-
shortcut: "p",
|
|
329
|
-
disabledReason: diffThreadReason,
|
|
330
|
-
keywords: ["review", "comment", "thread"],
|
|
331
|
-
run: () => actions.moveDiffCommentThread(-1),
|
|
332
|
-
}),
|
|
333
|
-
defineCommand({
|
|
334
|
-
id: "diff.add-comment",
|
|
335
|
-
title: "Add comment on selected diff line",
|
|
336
|
-
scope: "Diff",
|
|
337
|
-
subtitle: selectedDiffCommentAnchorLabel ?? "No diff line selected",
|
|
338
|
-
disabledReason: selectedDiffLineReason,
|
|
339
|
-
keywords: ["review", "reply"],
|
|
340
|
-
run: actions.openDiffCommentModal,
|
|
341
|
-
}),
|
|
342
|
-
defineCommand({
|
|
343
|
-
id: "diff.submit-review",
|
|
344
|
-
title: "Submit pull request review",
|
|
345
|
-
scope: "Diff",
|
|
346
|
-
subtitle: selectedPullRequestLabel,
|
|
347
|
-
shortcut: "R",
|
|
348
|
-
disabledReason: submitReviewReason,
|
|
349
|
-
keywords: ["review", "approve", "request changes"],
|
|
350
|
-
run: actions.openSubmitReviewModal,
|
|
351
|
-
}),
|
|
352
|
-
forSelected({
|
|
353
|
-
id: "pull.toggle-draft",
|
|
354
|
-
title: selectedPullRequest?.reviewStatus === "draft" ? "Mark ready for review" : "Mark as draft",
|
|
355
|
-
scope: "Pull request",
|
|
356
|
-
shortcut: "s",
|
|
357
|
-
keywords: ["state", "ready"],
|
|
358
|
-
run: actions.togglePullRequestDraftStatus,
|
|
359
|
-
}),
|
|
360
|
-
forSelected({
|
|
361
|
-
id: "pull.labels",
|
|
362
|
-
title: "Manage labels",
|
|
363
|
-
scope: "Pull request",
|
|
364
|
-
shortcut: "l",
|
|
365
|
-
run: actions.openLabelModal,
|
|
366
|
-
}),
|
|
367
|
-
forSelected({
|
|
368
|
-
id: "pull.merge",
|
|
369
|
-
title: "Merge pull request",
|
|
370
|
-
scope: "Pull request",
|
|
371
|
-
shortcut: "m",
|
|
372
|
-
keywords: ["auto merge", "squash"],
|
|
373
|
-
run: actions.openMergeModal,
|
|
374
|
-
}),
|
|
375
|
-
forSelected({
|
|
376
|
-
id: "pull.close",
|
|
377
|
-
title: "Close pull request",
|
|
378
|
-
scope: "Pull request",
|
|
379
|
-
shortcut: "x",
|
|
380
|
-
requireOpen: true,
|
|
381
|
-
run: actions.openCloseModal,
|
|
382
|
-
}),
|
|
383
|
-
forSelected({
|
|
384
|
-
id: "pull.open-browser",
|
|
385
|
-
title: "Open pull request in browser",
|
|
386
|
-
scope: "Pull request",
|
|
387
|
-
shortcut: "o",
|
|
388
|
-
keywords: ["github", "web"],
|
|
389
|
-
run: actions.openPullRequestInBrowser,
|
|
390
|
-
}),
|
|
391
|
-
forSelected({
|
|
392
|
-
id: "pull.copy-metadata",
|
|
393
|
-
title: "Copy pull request metadata",
|
|
394
|
-
scope: "Pull request",
|
|
395
|
-
shortcut: "y",
|
|
396
|
-
keywords: ["clipboard", "url", "title"],
|
|
397
|
-
run: actions.copyPullRequestMetadata,
|
|
398
|
-
}),
|
|
399
|
-
defineCommand({
|
|
400
|
-
id: "app.quit",
|
|
401
|
-
title: "Quit ghui",
|
|
402
|
-
scope: "System",
|
|
403
|
-
subtitle: "Leave the terminal UI",
|
|
404
|
-
shortcut: "q",
|
|
405
|
-
keywords: ["exit"],
|
|
406
|
-
run: actions.quit,
|
|
407
|
-
}),
|
|
408
|
-
]
|
|
409
|
-
}
|
package/src/commands.ts
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
export type CommandScope = "Global" | "View" | "Pull request" | "Diff" | "Navigation" | "System"
|
|
2
|
-
|
|
3
|
-
const SCOPE_ORDER: readonly CommandScope[] = ["Global", "View", "Pull request", "Diff", "Navigation", "System"]
|
|
4
|
-
|
|
5
|
-
export const sortCommandsByScope = (commands: readonly AppCommand[]) =>
|
|
6
|
-
[...commands].sort((left, right) => SCOPE_ORDER.indexOf(left.scope) - SCOPE_ORDER.indexOf(right.scope))
|
|
7
|
-
|
|
8
|
-
export interface AppCommand {
|
|
9
|
-
readonly id: string
|
|
10
|
-
readonly title: string
|
|
11
|
-
readonly scope: CommandScope
|
|
12
|
-
readonly run: () => void
|
|
13
|
-
readonly subtitle?: string
|
|
14
|
-
readonly shortcut?: string
|
|
15
|
-
readonly keywords?: readonly string[]
|
|
16
|
-
readonly disabledReason?: string | null
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export const defineCommand = (command: AppCommand): AppCommand => command
|
|
20
|
-
|
|
21
|
-
export const commandEnabled = (command: AppCommand) => !command.disabledReason
|
|
22
|
-
|
|
23
|
-
const normalize = (text: string) => text.toLowerCase().replace(/[^a-z0-9#]+/g, " ").trim()
|
|
24
|
-
|
|
25
|
-
const acronym = (text: string) => normalize(text).split(" ").filter(Boolean).map((word) => word[0]).join("")
|
|
26
|
-
|
|
27
|
-
const fuzzyIncludes = (text: string, query: string) => {
|
|
28
|
-
let index = 0
|
|
29
|
-
for (const char of text) {
|
|
30
|
-
if (char === query[index]) index++
|
|
31
|
-
if (index >= query.length) return true
|
|
32
|
-
}
|
|
33
|
-
return query.length === 0
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const commandSearchText = (command: AppCommand) => normalize([
|
|
37
|
-
command.title,
|
|
38
|
-
command.subtitle,
|
|
39
|
-
command.scope,
|
|
40
|
-
command.shortcut,
|
|
41
|
-
...(command.keywords ?? []),
|
|
42
|
-
].filter(Boolean).join(" "))
|
|
43
|
-
|
|
44
|
-
const commandScore = (command: AppCommand, query: string) => {
|
|
45
|
-
const normalizedQuery = normalize(query)
|
|
46
|
-
if (normalizedQuery.length === 0) return 0
|
|
47
|
-
|
|
48
|
-
const title = normalize(command.title)
|
|
49
|
-
const searchText = commandSearchText(command)
|
|
50
|
-
const titleAcronym = acronym(command.title)
|
|
51
|
-
if (title.startsWith(normalizedQuery)) return 0
|
|
52
|
-
if (searchText.startsWith(normalizedQuery)) return 1
|
|
53
|
-
if (title.includes(normalizedQuery)) return 2
|
|
54
|
-
if (searchText.includes(normalizedQuery)) return 3
|
|
55
|
-
if (titleAcronym.startsWith(normalizedQuery)) return 4
|
|
56
|
-
if (fuzzyIncludes(searchText, normalizedQuery.replaceAll(" ", ""))) return 5
|
|
57
|
-
return null
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export const filterCommands = (commands: readonly AppCommand[], query: string) => {
|
|
61
|
-
return commands.flatMap((command, index) => {
|
|
62
|
-
const score = commandScore(command, query)
|
|
63
|
-
return score === null ? [] : [{ command, index, score }]
|
|
64
|
-
}).sort((left, right) => {
|
|
65
|
-
const enabled = Number(commandEnabled(right.command)) - Number(commandEnabled(left.command))
|
|
66
|
-
return enabled || left.score - right.score || left.index - right.index
|
|
67
|
-
}).map(({ command }) => command)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export const clampCommandIndex = (index: number, commands: readonly AppCommand[]) => {
|
|
71
|
-
if (commands.length === 0) return 0
|
|
72
|
-
return Math.max(0, Math.min(commands.length - 1, index))
|
|
73
|
-
}
|
package/src/config.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { Config, Effect } from "effect"
|
|
2
|
-
|
|
3
|
-
const positiveIntOr = (fallback: number) => (value: number) => Number.isFinite(value) && value > 0 ? value : fallback
|
|
4
|
-
|
|
5
|
-
const pageSizeOr = (fallback: number) => (value: number) => Math.min(100, positiveIntOr(fallback)(value))
|
|
6
|
-
|
|
7
|
-
const appConfig = Config.all({
|
|
8
|
-
prFetchLimit: Config.int("GHUI_PR_FETCH_LIMIT").pipe(
|
|
9
|
-
Config.withDefault(200),
|
|
10
|
-
Config.map(positiveIntOr(200)),
|
|
11
|
-
),
|
|
12
|
-
prPageSize: Config.int("GHUI_PR_PAGE_SIZE").pipe(
|
|
13
|
-
Config.withDefault(50),
|
|
14
|
-
Config.map(pageSizeOr(50)),
|
|
15
|
-
),
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
export const config = Effect.runSync(Effect.gen(function*() {
|
|
19
|
-
return yield* appConfig
|
|
20
|
-
}))
|
package/src/date.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
const DAY_MS = 24 * 60 * 60 * 1000
|
|
2
|
-
|
|
3
|
-
export const formatShortDate = (date: Date) =>
|
|
4
|
-
date.toLocaleDateString("en-US", { weekday: "short", month: "numeric", day: "numeric" })
|
|
5
|
-
|
|
6
|
-
export const formatTimestamp = (date: Date) =>
|
|
7
|
-
date.toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" }).toLowerCase()
|
|
8
|
-
|
|
9
|
-
export const daysOpen = (date: Date) => Math.max(0, Math.floor((Date.now() - date.getTime()) / DAY_MS))
|
|
10
|
-
|
|
11
|
-
export const formatRelativeDate = (date: Date): string => {
|
|
12
|
-
const days = daysOpen(date)
|
|
13
|
-
if (days === 0) return "today"
|
|
14
|
-
if (days === 1) return "yesterday"
|
|
15
|
-
if (days < 7) return `${days} days ago`
|
|
16
|
-
if (days < 14) return "last week"
|
|
17
|
-
if (days < 30) return `${Math.floor(days / 7)} weeks ago`
|
|
18
|
-
if (days < 60) return "last month"
|
|
19
|
-
return `${Math.floor(days / 30)} months ago`
|
|
20
|
-
}
|
package/src/domain.ts
DELETED
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import { Schema } from "effect"
|
|
2
|
-
|
|
3
|
-
export type LoadStatus = "loading" | "ready" | "error"
|
|
4
|
-
|
|
5
|
-
export type PullRequestState = "open" | "closed" | "merged"
|
|
6
|
-
|
|
7
|
-
export const pullRequestQueueModes = ["authored", "review", "assigned", "mentioned"] as const
|
|
8
|
-
export type PullRequestUserQueueMode = (typeof pullRequestQueueModes)[number]
|
|
9
|
-
export type PullRequestQueueMode = "repository" | PullRequestUserQueueMode
|
|
10
|
-
|
|
11
|
-
export const pullRequestQueueLabels = {
|
|
12
|
-
repository: "repository",
|
|
13
|
-
authored: "authored",
|
|
14
|
-
review: "review requested",
|
|
15
|
-
assigned: "assigned",
|
|
16
|
-
mentioned: "mentioned",
|
|
17
|
-
} as const satisfies Record<PullRequestQueueMode, string>
|
|
18
|
-
|
|
19
|
-
export const pullRequestQueueSearchQualifier = (mode: PullRequestQueueMode, repository: string | null) => {
|
|
20
|
-
const qualifiers = {
|
|
21
|
-
repository: repository ? `repo:${repository}` : "author:@me",
|
|
22
|
-
authored: "author:@me",
|
|
23
|
-
review: "review-requested:@me",
|
|
24
|
-
assigned: "assignee:@me",
|
|
25
|
-
mentioned: "mentions:@me",
|
|
26
|
-
} as const satisfies Record<PullRequestQueueMode, string>
|
|
27
|
-
return qualifiers[mode]
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export type CheckConclusion = "success" | "failure" | "neutral" | "skipped" | "cancelled" | "timed_out"
|
|
31
|
-
|
|
32
|
-
export type CheckRunStatus = "completed" | "in_progress" | "queued" | "pending"
|
|
33
|
-
|
|
34
|
-
export type CheckRollupStatus = "passing" | "pending" | "failing" | "none"
|
|
35
|
-
|
|
36
|
-
export type ReviewStatus = "draft" | "approved" | "changes" | "review" | "none"
|
|
37
|
-
|
|
38
|
-
export type Mergeable = "mergeable" | "conflicting" | "unknown"
|
|
39
|
-
|
|
40
|
-
// DiffCommentSide is the only literal type still consumed at runtime — GitHubService
|
|
41
|
-
// uses it as a Schema inside PullRequestCommentSchema.
|
|
42
|
-
export const DiffCommentSide = Schema.Literals(["LEFT", "RIGHT"])
|
|
43
|
-
export type DiffCommentSide = Schema.Schema.Type<typeof DiffCommentSide>
|
|
44
|
-
|
|
45
|
-
export type PullRequestMergeAction = "squash" | "auto" | "admin" | "disable-auto"
|
|
46
|
-
|
|
47
|
-
export const pullRequestReviewEvents = ["COMMENT", "APPROVE", "REQUEST_CHANGES"] as const
|
|
48
|
-
export type PullRequestReviewEvent = (typeof pullRequestReviewEvents)[number]
|
|
49
|
-
|
|
50
|
-
export interface CheckItem {
|
|
51
|
-
readonly name: string
|
|
52
|
-
readonly status: CheckRunStatus
|
|
53
|
-
readonly conclusion: CheckConclusion | null
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export interface PullRequestLabel {
|
|
57
|
-
readonly name: string
|
|
58
|
-
readonly color: string | null
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export interface CreatePullRequestCommentInput {
|
|
62
|
-
readonly repository: string
|
|
63
|
-
readonly number: number
|
|
64
|
-
readonly commitId: string
|
|
65
|
-
readonly path: string
|
|
66
|
-
readonly line: number
|
|
67
|
-
readonly side: DiffCommentSide
|
|
68
|
-
readonly startLine?: number
|
|
69
|
-
readonly startSide?: DiffCommentSide
|
|
70
|
-
readonly body: string
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export interface SubmitPullRequestReviewInput {
|
|
74
|
-
readonly repository: string
|
|
75
|
-
readonly number: number
|
|
76
|
-
readonly event: PullRequestReviewEvent
|
|
77
|
-
readonly body: string
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export interface PullRequestReviewComment {
|
|
81
|
-
readonly id: string
|
|
82
|
-
readonly path: string
|
|
83
|
-
readonly line: number
|
|
84
|
-
readonly side: DiffCommentSide
|
|
85
|
-
readonly author: string
|
|
86
|
-
readonly body: string
|
|
87
|
-
readonly createdAt: Date | null
|
|
88
|
-
readonly url: string | null
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export type PullRequestConversationItem =
|
|
92
|
-
| {
|
|
93
|
-
readonly _tag: "comment"
|
|
94
|
-
readonly id: string
|
|
95
|
-
readonly author: string
|
|
96
|
-
readonly body: string
|
|
97
|
-
readonly createdAt: Date | null
|
|
98
|
-
readonly url: string | null
|
|
99
|
-
}
|
|
100
|
-
| ({ readonly _tag: "review-comment" } & PullRequestReviewComment)
|
|
101
|
-
|
|
102
|
-
export interface PullRequestItem {
|
|
103
|
-
readonly repository: string
|
|
104
|
-
readonly author: string
|
|
105
|
-
readonly headRefOid: string
|
|
106
|
-
readonly number: number
|
|
107
|
-
readonly title: string
|
|
108
|
-
readonly body: string
|
|
109
|
-
readonly labels: readonly PullRequestLabel[]
|
|
110
|
-
readonly additions: number
|
|
111
|
-
readonly deletions: number
|
|
112
|
-
readonly changedFiles: number
|
|
113
|
-
readonly state: PullRequestState
|
|
114
|
-
readonly reviewStatus: ReviewStatus
|
|
115
|
-
readonly checkStatus: CheckRollupStatus
|
|
116
|
-
readonly checkSummary: string | null
|
|
117
|
-
readonly checks: readonly CheckItem[]
|
|
118
|
-
readonly autoMergeEnabled: boolean
|
|
119
|
-
readonly detailLoaded: boolean
|
|
120
|
-
readonly createdAt: Date
|
|
121
|
-
readonly closedAt: Date | null
|
|
122
|
-
readonly url: string
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export interface PullRequestPage {
|
|
126
|
-
readonly items: readonly PullRequestItem[]
|
|
127
|
-
readonly endCursor: string | null
|
|
128
|
-
readonly hasNextPage: boolean
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export interface ListPullRequestPageInput {
|
|
132
|
-
readonly mode: PullRequestQueueMode
|
|
133
|
-
readonly repository: string | null
|
|
134
|
-
readonly cursor: string | null
|
|
135
|
-
readonly pageSize: number
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export interface PullRequestMergeInfo {
|
|
139
|
-
readonly repository: string
|
|
140
|
-
readonly number: number
|
|
141
|
-
readonly title: string
|
|
142
|
-
readonly state: PullRequestState
|
|
143
|
-
readonly isDraft: boolean
|
|
144
|
-
readonly mergeable: Mergeable
|
|
145
|
-
readonly reviewStatus: ReviewStatus
|
|
146
|
-
readonly checkStatus: CheckRollupStatus
|
|
147
|
-
readonly checkSummary: string | null
|
|
148
|
-
readonly autoMergeEnabled: boolean
|
|
149
|
-
}
|
package/src/errors.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
const hasStringField = <K extends string>(value: object, key: K): value is { [P in K]: string } & object =>
|
|
2
|
-
key in value && typeof (value as Record<string, unknown>)[key] === "string"
|
|
3
|
-
|
|
4
|
-
export const errorMessage = (error: unknown): string => {
|
|
5
|
-
if (typeof error === "object" && error !== null) {
|
|
6
|
-
if (hasStringField(error, "detail") && error.detail.length > 0) return error.detail
|
|
7
|
-
if (hasStringField(error, "message") && error.message.length > 0) return error.message
|
|
8
|
-
}
|
|
9
|
-
return error instanceof Error ? error.message : String(error)
|
|
10
|
-
}
|