@dacsar/prview 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -0
- package/dist/app.d.ts +1 -0
- package/dist/app.js +81 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +49389 -0
- package/dist/components/filter-bar.d.ts +7 -0
- package/dist/components/filter-bar.js +9 -0
- package/dist/components/header.d.ts +1 -0
- package/dist/components/header.js +5 -0
- package/dist/components/help-bar.d.ts +5 -0
- package/dist/components/help-bar.js +8 -0
- package/dist/components/loading.d.ts +5 -0
- package/dist/components/loading.js +9 -0
- package/dist/components/pr-row.d.ts +7 -0
- package/dist/components/pr-row.js +10 -0
- package/dist/components/pr-table.d.ts +7 -0
- package/dist/components/pr-table.js +9 -0
- package/dist/components/status-badge.d.ts +7 -0
- package/dist/components/status-badge.js +21 -0
- package/dist/components/tab-bar.d.ts +8 -0
- package/dist/components/tab-bar.js +6 -0
- package/dist/hooks/use-filter-sort.d.ts +2 -0
- package/dist/hooks/use-filter-sort.js +24 -0
- package/dist/hooks/use-pull-requests.d.ts +10 -0
- package/dist/hooks/use-pull-requests.js +38 -0
- package/dist/types.d.ts +32 -0
- package/dist/types.js +1 -0
- package/dist/utils/fetch-prs.d.ts +4 -0
- package/dist/utils/fetch-prs.js +127 -0
- package/dist/utils/format-time.d.ts +3 -0
- package/dist/utils/format-time.js +27 -0
- package/dist/utils/open-url.d.ts +1 -0
- package/dist/utils/open-url.js +6 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# prview
|
|
2
|
+
|
|
3
|
+
A terminal dashboard for your GitHub pull requests.
|
|
4
|
+
|
|
5
|
+
`prview` lets you monitor pull requests across all your GitHub repositories from a single TUI, powered by React and Ink.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- View pull requests across multiple repositories in one place
|
|
10
|
+
- Two tabs: **Review Requested** and **My PRs**
|
|
11
|
+
- Real-time filtering and sorting
|
|
12
|
+
- Auto-refresh every 60 seconds
|
|
13
|
+
- Status badges (Approved, Changes Requested, Draft, etc.)
|
|
14
|
+
- Press Enter to open a PR in your browser
|
|
15
|
+
|
|
16
|
+
## Prerequisites
|
|
17
|
+
|
|
18
|
+
[GitHub CLI](https://cli.github.com/) (`gh`) must be installed and authenticated:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
gh auth login
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
npm install -g @dacsar/prview
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
pv
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Keybindings
|
|
37
|
+
|
|
38
|
+
| Key | Action |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
| `Tab` | Switch between Review Requested / My PRs |
|
|
41
|
+
| `j` / `Down` | Move selection down |
|
|
42
|
+
| `k` / `Up` | Move selection up |
|
|
43
|
+
| `Enter` | Open selected PR in browser |
|
|
44
|
+
| `/` | Enter filter mode |
|
|
45
|
+
| `Esc` | Exit filter mode / Clear filter |
|
|
46
|
+
| `r` | Refresh |
|
|
47
|
+
| `q` | Quit |
|
|
48
|
+
|
|
49
|
+
## Tech Stack
|
|
50
|
+
|
|
51
|
+
- [React](https://react.dev/) + [Ink](https://github.com/vadimdemedes/ink) — Terminal UI
|
|
52
|
+
- [TypeScript](https://www.typescriptlang.org/)
|
|
53
|
+
- [esbuild](https://esbuild.github.io/) — Bundler
|
|
54
|
+
- [Biome](https://biomejs.dev/) — Linter & Formatter
|
|
55
|
+
|
|
56
|
+
## Development
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
git clone https://github.com/your-username/prview.git
|
|
60
|
+
cd prview
|
|
61
|
+
make setup
|
|
62
|
+
npm run dev
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## License
|
|
66
|
+
|
|
67
|
+
MIT
|
package/dist/app.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function App(): import("react/jsx-runtime").JSX.Element;
|
package/dist/app.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useCallback } from 'react';
|
|
3
|
+
import { Box, Text, useApp, useInput } from 'ink';
|
|
4
|
+
import { usePullRequests } from './hooks/use-pull-requests.js';
|
|
5
|
+
import { useFilterSort } from './hooks/use-filter-sort.js';
|
|
6
|
+
import { openUrl } from './utils/open-url.js';
|
|
7
|
+
import { TabBar } from './components/tab-bar.js';
|
|
8
|
+
import { PrTable } from './components/pr-table.js';
|
|
9
|
+
import { FilterBar } from './components/filter-bar.js';
|
|
10
|
+
import { HelpBar } from './components/help-bar.js';
|
|
11
|
+
import { Loading } from './components/loading.js';
|
|
12
|
+
export function App() {
|
|
13
|
+
const { exit } = useApp();
|
|
14
|
+
const { reviewRequested, myPRs, loading, error, refresh } = usePullRequests();
|
|
15
|
+
const [activeTab, setActiveTab] = useState('review-requested');
|
|
16
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
17
|
+
const [filter, setFilter] = useState('');
|
|
18
|
+
const [isFilterMode, setIsFilterMode] = useState(false);
|
|
19
|
+
const [sort, setSort] = useState({ key: 'time', direction: 'desc' });
|
|
20
|
+
const currentPRs = activeTab === 'review-requested' ? reviewRequested : myPRs;
|
|
21
|
+
const filteredPRs = useFilterSort(currentPRs, filter, sort);
|
|
22
|
+
const clampIndex = useCallback((index) => Math.max(0, Math.min(index, filteredPRs.length - 1)), [filteredPRs.length]);
|
|
23
|
+
useInput((_input, key) => {
|
|
24
|
+
if (key.escape) {
|
|
25
|
+
setIsFilterMode(false);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
}, { isActive: isFilterMode });
|
|
29
|
+
useInput((input, key) => {
|
|
30
|
+
if (input === 'q') {
|
|
31
|
+
exit();
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (key.tab) {
|
|
35
|
+
setActiveTab(prev => prev === 'review-requested' ? 'my-prs' : 'review-requested');
|
|
36
|
+
setSelectedIndex(0);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (input === 'j' || key.downArrow) {
|
|
40
|
+
setSelectedIndex(prev => clampIndex(prev + 1));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (input === 'k' || key.upArrow) {
|
|
44
|
+
setSelectedIndex(prev => clampIndex(prev - 1));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (key.return) {
|
|
48
|
+
const pr = filteredPRs[selectedIndex];
|
|
49
|
+
if (pr) {
|
|
50
|
+
openUrl(pr.url);
|
|
51
|
+
}
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (input === 's') {
|
|
55
|
+
setSort(prev => {
|
|
56
|
+
if (prev.key === 'title') {
|
|
57
|
+
return { key: 'time', direction: prev.direction };
|
|
58
|
+
}
|
|
59
|
+
if (prev.key === 'time' && prev.direction === 'desc') {
|
|
60
|
+
return { key: 'time', direction: 'asc' };
|
|
61
|
+
}
|
|
62
|
+
return { key: 'title', direction: 'asc' };
|
|
63
|
+
});
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (input === '/') {
|
|
67
|
+
setIsFilterMode(true);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (key.escape) {
|
|
71
|
+
setFilter('');
|
|
72
|
+
setIsFilterMode(false);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (input === 'r') {
|
|
76
|
+
refresh();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
}, { isActive: !isFilterMode });
|
|
80
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(TabBar, { activeTab: activeTab, reviewCount: reviewRequested.length, myCount: myPRs.length }), error && (_jsx(Box, { paddingX: 2, children: _jsxs(Text, { color: "red", children: ["Error: ", error] }) })), loading && filteredPRs.length === 0 ? (_jsx(Loading, { error: error })) : (_jsx(PrTable, { prs: filteredPRs, selectedIndex: selectedIndex })), _jsx(FilterBar, { isActive: isFilterMode, filter: filter, onFilterChange: setFilter }), _jsx(Box, { paddingX: 1, children: _jsxs(Text, { dimColor: true, children: ["Sort: ", sort.key, " (", sort.direction, ")"] }) }), _jsx(HelpBar, { isFilterMode: isFilterMode })] }));
|
|
81
|
+
}
|
package/dist/cli.d.ts
ADDED