@profullstack/builtindie 0.1.4 → 0.7.1
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/OpenStack.md +1 -1
- package/README.md +33 -4
- package/package.json +16 -7
- package/public/app.js +58 -0
- package/public/style.css +113 -11
- package/src/auth.ts +10 -6
- package/src/browse.ts +243 -0
- package/src/cli.ts +131 -10
- package/src/client.ts +96 -5
- package/src/coinpay-signin.ts +118 -0
- package/src/db.ts +406 -9
- package/src/fetch.ts +15 -7
- package/src/html.ts +561 -82
- package/src/karma.ts +179 -0
- package/src/leaderboard.ts +59 -0
- package/src/mcp.ts +55 -4
- package/src/pit-footer.ts +39 -0
- package/src/profile.ts +34 -0
- package/src/ring-footer.ts +53 -0
- package/src/ring.ts +5 -1
- package/src/server.ts +607 -48
- package/src/service.ts +307 -74
- package/src/tags.ts +92 -0
- package/src/tui.ts +28 -6
- package/src/version.ts +1 -1
package/src/tui.ts
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
* `builtindie` with no arguments: the board in the terminal.
|
|
3
3
|
*
|
|
4
4
|
* ←/→ switch board (today, week, month, all, new), ↑/↓ pick, Enter shows it,
|
|
5
|
-
* v upvotes, l launches a URL you type, q quits. Glyphs come from the OpenIcon
|
|
5
|
+
* v upvotes, l launches a URL you type, k shows the karma leaderboard, q quits. Glyphs come from the OpenIcon
|
|
6
6
|
* set: Nerd Font, Unicode or ASCII, whichever this terminal can draw.
|
|
7
7
|
*/
|
|
8
8
|
const UP = glyph('arrow-up') || '^'
|
|
9
9
|
const OK = glyph('check') || '+'
|
|
10
10
|
import { createApp } from '@profullstack/hqtui'
|
|
11
|
-
import { BuiltIndieClient, type ApiProduct } from './client.js'
|
|
11
|
+
import { BuiltIndieClient, type ApiProduct, type BoardRows } from './client.js'
|
|
12
12
|
import { glyph } from './icons.js'
|
|
13
13
|
|
|
14
14
|
export const WINDOWS = ['today', 'week', 'month', 'all', 'new'] as const
|
|
@@ -21,12 +21,14 @@ export type TuiState = {
|
|
|
21
21
|
typing: string | null
|
|
22
22
|
busy: string | null
|
|
23
23
|
flash: string | null
|
|
24
|
+
/** The karma leaderboard, while it is open (k). */
|
|
25
|
+
board: BoardRows | null
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
export const initialState = (): TuiState => ({ window: 0, products: [], at: 0, open: null, typing: null, busy: null, flash: null })
|
|
28
|
+
export const initialState = (): TuiState => ({ window: 0, products: [], at: 0, open: null, typing: null, busy: null, flash: null, board: null })
|
|
27
29
|
|
|
28
30
|
/** Pure key handling, testable without a terminal. */
|
|
29
|
-
export function onKey(s: TuiState, key: { name: string; key: string; char?: string }): 'load' | 'vote' | 'launch' | 'quit' | null {
|
|
31
|
+
export function onKey(s: TuiState, key: { name: string; key: string; char?: string }): 'load' | 'vote' | 'launch' | 'karma' | 'quit' | null {
|
|
30
32
|
if (key.key === 'ctrl+c') return 'quit'
|
|
31
33
|
if (s.typing !== null) {
|
|
32
34
|
if (key.name === 'escape') s.typing = null
|
|
@@ -35,8 +37,14 @@ export function onKey(s: TuiState, key: { name: string; key: string; char?: stri
|
|
|
35
37
|
else if (key.char && key.char.length === 1 && !key.key.startsWith('ctrl+')) s.typing += key.char
|
|
36
38
|
return null
|
|
37
39
|
}
|
|
40
|
+
if (s.board) {
|
|
41
|
+
if (key.name === 'escape' || key.char === 'k') s.board = null
|
|
42
|
+
else if (key.char === 'q') return 'quit'
|
|
43
|
+
return null
|
|
44
|
+
}
|
|
38
45
|
if (key.name === 'escape') return s.open ? ((s.open = null), null) : 'quit'
|
|
39
46
|
if (key.char === 'q') return 'quit'
|
|
47
|
+
if (key.char === 'k') return 'karma'
|
|
40
48
|
if (key.char === 'l') return (s.typing = ''), null
|
|
41
49
|
if (key.char === 'v') return s.products[s.at] ? 'vote' : null
|
|
42
50
|
if (key.name === 'right' || key.name === 'left') {
|
|
@@ -72,6 +80,12 @@ export async function runTui(client = new BuiltIndieClient()): Promise<void> {
|
|
|
72
80
|
const action = onKey(s, event)
|
|
73
81
|
if (action === 'quit') app.quit()
|
|
74
82
|
else if (action === 'load') void load()
|
|
83
|
+
else if (action === 'karma') {
|
|
84
|
+
void run('Loading the karma board…', async () => {
|
|
85
|
+
const week = await client.leaderboard('karma', 'week', 25)
|
|
86
|
+
s.board = week.rows.length ? week : await client.leaderboard('karma', 'all', 25)
|
|
87
|
+
})
|
|
88
|
+
}
|
|
75
89
|
else if (action === 'vote') {
|
|
76
90
|
const p = s.products[s.at]!
|
|
77
91
|
void run('Voting…', async () => {
|
|
@@ -98,7 +112,14 @@ export async function runTui(client = new BuiltIndieClient()): Promise<void> {
|
|
|
98
112
|
h.text(WINDOWS.map((w, i) => (i === s.window ? `[${w}]` : w)).join(' '), { fg: theme.text })
|
|
99
113
|
h.text(`${client.base.replace(/^https?:\/\//, '')} `, { fg: theme.muted, align: 'right' })
|
|
100
114
|
})
|
|
101
|
-
if (s.
|
|
115
|
+
if (s.board) {
|
|
116
|
+
const r = s.board
|
|
117
|
+
ui.panel({ title: `Top karma (${r.period === 'all' ? 'all time' : 'this week'})` }, (b: any) => {
|
|
118
|
+
if (!r.rows.length) b.text('Nobody yet. Sign in at built.indie, then upvote, review, comment and share to earn karma.', { fg: theme.muted })
|
|
119
|
+
for (const x of r.rows) b.text(`${String(x.rank ?? '').padStart(3)}. ${String(x.value).padStart(5)} ${x.name} ${x.badges.map((g) => g.emoji).join('')}`, { fg: x.rank === 1 ? theme.accent : theme.text, bold: x.rank === 1 })
|
|
120
|
+
b.text(r.web, { fg: theme.muted })
|
|
121
|
+
})
|
|
122
|
+
} else if (s.open) {
|
|
102
123
|
const p = s.open
|
|
103
124
|
ui.panel({ title: `${p.name} ${UP} ${p.votes}` }, (b: any) => {
|
|
104
125
|
b.text(p.tagline, { fg: theme.accent, bold: true })
|
|
@@ -108,6 +129,7 @@ export async function runTui(client = new BuiltIndieClient()): Promise<void> {
|
|
|
108
129
|
if (p.tags.length) b.text(`tags ${p.tags.join(', ')}`, { fg: theme.muted })
|
|
109
130
|
if (p.videos.length) b.text(`video ${p.videos[0]!.url}`, { fg: theme.muted })
|
|
110
131
|
if (p.maker) b.text(`maker ${p.maker.name}${p.maker.verified ? ' ✓' : ''}`, { fg: theme.muted })
|
|
132
|
+
if (p.rating != null) b.text(`rated ★ ${p.rating} from ${p.reviews}`, { fg: theme.muted })
|
|
111
133
|
})
|
|
112
134
|
} else {
|
|
113
135
|
ui.panel({ title: s.busy ?? `${WINDOWS[s.window]} (${s.products.length})` }, (b: any) => {
|
|
@@ -120,7 +142,7 @@ export async function runTui(client = new BuiltIndieClient()): Promise<void> {
|
|
|
120
142
|
}
|
|
121
143
|
if (s.typing !== null) ui.panel({ title: 'Launch a URL', size: 3 }, (b: any) => b.text(`${s.typing}▏`, { fg: theme.accent }))
|
|
122
144
|
else if (s.flash) ui.panel({ title: 'built.indie', size: 3 }, (b: any) => b.text(s.flash, { fg: theme.accent }))
|
|
123
|
-
ui.statusBar({ items: s.typing !== null ? [{ key: 'Enter', label: 'launch' }, { key: 'Esc', label: 'cancel' }] : [{ key: '←→', label: 'board' }, { key: '↑↓', label: 'pick' }, { key: 'Enter', label: 'open' }, { key: 'v', label: 'upvote' }, { key: 'l', label: 'launch' }, { key: 'q', label: 'quit' }], right: [{ label: 'built.indie' }] })
|
|
145
|
+
ui.statusBar({ items: s.typing !== null ? [{ key: 'Enter', label: 'launch' }, { key: 'Esc', label: 'cancel' }] : [{ key: '←→', label: 'board' }, { key: '↑↓', label: 'pick' }, { key: 'Enter', label: 'open' }, { key: 'v', label: 'upvote' }, { key: 'l', label: 'launch' }, { key: 'k', label: 'karma' }, { key: 'q', label: 'quit' }], right: [{ label: 'built.indie' }] })
|
|
124
146
|
})
|
|
125
147
|
void load()
|
|
126
148
|
await app.start()
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.1
|
|
1
|
+
export const VERSION = '0.7.1'
|