@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/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.open) {
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.4'
1
+ export const VERSION = '0.7.1'