codeblog-app 1.5.1 → 1.5.2
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/package.json +6 -6
- package/src/tui/app.tsx +4 -1
- package/src/tui/context/route.tsx +3 -1
- package/src/tui/routes/home.tsx +5 -25
- package/src/tui/routes/setup.tsx +150 -22
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "codeblog-app",
|
|
4
|
-
"version": "1.5.
|
|
4
|
+
"version": "1.5.2",
|
|
5
5
|
"description": "CLI client for CodeBlog — the forum where AI writes the posts",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|
|
@@ -56,11 +56,11 @@
|
|
|
56
56
|
"typescript": "5.8.2"
|
|
57
57
|
},
|
|
58
58
|
"optionalDependencies": {
|
|
59
|
-
"codeblog-app-darwin-arm64": "1.5.
|
|
60
|
-
"codeblog-app-darwin-x64": "1.5.
|
|
61
|
-
"codeblog-app-linux-arm64": "1.5.
|
|
62
|
-
"codeblog-app-linux-x64": "1.5.
|
|
63
|
-
"codeblog-app-windows-x64": "1.5.
|
|
59
|
+
"codeblog-app-darwin-arm64": "1.5.2",
|
|
60
|
+
"codeblog-app-darwin-x64": "1.5.2",
|
|
61
|
+
"codeblog-app-linux-arm64": "1.5.2",
|
|
62
|
+
"codeblog-app-linux-x64": "1.5.2",
|
|
63
|
+
"codeblog-app-windows-x64": "1.5.2"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
66
|
"@ai-sdk/amazon-bedrock": "^4.0.60",
|
package/src/tui/app.tsx
CHANGED
|
@@ -5,7 +5,7 @@ import { ExitProvider, useExit } from "./context/exit"
|
|
|
5
5
|
import { ThemeProvider, useTheme } from "./context/theme"
|
|
6
6
|
import { Home } from "./routes/home"
|
|
7
7
|
import { Chat } from "./routes/chat"
|
|
8
|
-
import { ThemeSetup } from "./routes/setup"
|
|
8
|
+
import { ThemeSetup, ThemePicker } from "./routes/setup"
|
|
9
9
|
|
|
10
10
|
import pkg from "../../package.json"
|
|
11
11
|
const VERSION = pkg.version
|
|
@@ -123,6 +123,9 @@ function App() {
|
|
|
123
123
|
<Match when={route.data.type === "chat"}>
|
|
124
124
|
<Chat />
|
|
125
125
|
</Match>
|
|
126
|
+
<Match when={route.data.type === "theme"}>
|
|
127
|
+
<ThemePicker onDone={() => route.navigate({ type: "home" })} />
|
|
128
|
+
</Match>
|
|
126
129
|
</Switch>
|
|
127
130
|
|
|
128
131
|
{/* Status bar */}
|
|
@@ -8,7 +8,9 @@ export type SearchRoute = { type: "search"; query: string }
|
|
|
8
8
|
export type TrendingRoute = { type: "trending" }
|
|
9
9
|
export type NotificationsRoute = { type: "notifications" }
|
|
10
10
|
|
|
11
|
-
export type
|
|
11
|
+
export type ThemeRoute = { type: "theme" }
|
|
12
|
+
|
|
13
|
+
export type Route = HomeRoute | ChatRoute | PostRoute | SearchRoute | TrendingRoute | NotificationsRoute | ThemeRoute
|
|
12
14
|
|
|
13
15
|
export const { use: useRoute, provider: RouteProvider } = createSimpleContext({
|
|
14
16
|
name: "Route",
|
package/src/tui/routes/home.tsx
CHANGED
|
@@ -2,7 +2,7 @@ import { createSignal, Show } from "solid-js"
|
|
|
2
2
|
import { useKeyboard } from "@opentui/solid"
|
|
3
3
|
import { useRoute } from "../context/route"
|
|
4
4
|
import { useExit } from "../context/exit"
|
|
5
|
-
import { useTheme
|
|
5
|
+
import { useTheme } from "../context/theme"
|
|
6
6
|
|
|
7
7
|
const LOGO = [
|
|
8
8
|
" ██████╗ ██████╗ ██████╗ ███████╗██████╗ ██╗ ██████╗ ██████╗ ",
|
|
@@ -82,30 +82,10 @@ export function Home(props: {
|
|
|
82
82
|
return
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
if (cmd === "/theme") {
|
|
86
|
-
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
-
return
|
|
90
|
-
}
|
|
91
|
-
if (THEME_NAMES.includes(name)) {
|
|
92
|
-
theme.set(name)
|
|
93
|
-
showMsg(`Theme set to ${name}`, theme.colors.success)
|
|
94
|
-
} else {
|
|
95
|
-
showMsg(`Unknown theme: ${name}. Available: ${THEME_NAMES.join(", ")}`, theme.colors.error)
|
|
96
|
-
}
|
|
97
|
-
return
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (cmd === "/dark") {
|
|
101
|
-
theme.setMode("dark")
|
|
102
|
-
showMsg("Switched to dark mode", theme.colors.success)
|
|
103
|
-
return
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (cmd === "/light") {
|
|
107
|
-
theme.setMode("light")
|
|
108
|
-
showMsg("Switched to light mode", theme.colors.success)
|
|
85
|
+
if (cmd === "/theme" || cmd === "/dark" || cmd === "/light") {
|
|
86
|
+
if (cmd === "/dark") theme.setMode("dark")
|
|
87
|
+
if (cmd === "/light") theme.setMode("light")
|
|
88
|
+
route.navigate({ type: "theme" })
|
|
109
89
|
return
|
|
110
90
|
}
|
|
111
91
|
|
package/src/tui/routes/setup.tsx
CHANGED
|
@@ -2,6 +2,14 @@ import { createSignal } from "solid-js"
|
|
|
2
2
|
import { useKeyboard } from "@opentui/solid"
|
|
3
3
|
import { useTheme, THEME_NAMES, THEMES } from "../context/theme"
|
|
4
4
|
|
|
5
|
+
// High-contrast colors that are visible on ANY terminal background
|
|
6
|
+
const HC = {
|
|
7
|
+
title: "#ff6600",
|
|
8
|
+
text: "#888888",
|
|
9
|
+
selected: "#00cc00",
|
|
10
|
+
dim: "#999999",
|
|
11
|
+
}
|
|
12
|
+
|
|
5
13
|
export function ThemeSetup() {
|
|
6
14
|
const theme = useTheme()
|
|
7
15
|
const modes = ["dark", "light"] as const
|
|
@@ -13,13 +21,11 @@ export function ThemeSetup() {
|
|
|
13
21
|
if (step() === "mode") {
|
|
14
22
|
if (evt.name === "up" || evt.name === "k") {
|
|
15
23
|
setModeIdx((i) => (i - 1 + modes.length) % modes.length)
|
|
16
|
-
theme.setMode(modes[(modeIdx() - 1 + modes.length) % modes.length])
|
|
17
24
|
evt.preventDefault()
|
|
18
25
|
return
|
|
19
26
|
}
|
|
20
27
|
if (evt.name === "down" || evt.name === "j") {
|
|
21
28
|
setModeIdx((i) => (i + 1) % modes.length)
|
|
22
|
-
theme.setMode(modes[(modeIdx() + 1) % modes.length])
|
|
23
29
|
evt.preventDefault()
|
|
24
30
|
return
|
|
25
31
|
}
|
|
@@ -33,14 +39,16 @@ export function ThemeSetup() {
|
|
|
33
39
|
|
|
34
40
|
if (step() === "theme") {
|
|
35
41
|
if (evt.name === "up" || evt.name === "k") {
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
const next = (themeIdx() - 1 + THEME_NAMES.length) % THEME_NAMES.length
|
|
43
|
+
setThemeIdx(next)
|
|
44
|
+
theme.set(THEME_NAMES[next])
|
|
38
45
|
evt.preventDefault()
|
|
39
46
|
return
|
|
40
47
|
}
|
|
41
48
|
if (evt.name === "down" || evt.name === "j") {
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
const next = (themeIdx() + 1) % THEME_NAMES.length
|
|
50
|
+
setThemeIdx(next)
|
|
51
|
+
theme.set(THEME_NAMES[next])
|
|
44
52
|
evt.preventDefault()
|
|
45
53
|
return
|
|
46
54
|
}
|
|
@@ -62,62 +70,62 @@ export function ThemeSetup() {
|
|
|
62
70
|
<box flexGrow={1} minHeight={0} />
|
|
63
71
|
|
|
64
72
|
<box flexShrink={0} flexDirection="column" alignItems="center">
|
|
65
|
-
<text fg={
|
|
66
|
-
<span style={{ bold: true }}>Welcome to CodeBlog</span>
|
|
73
|
+
<text fg={HC.title}>
|
|
74
|
+
<span style={{ bold: true }}>{"★ Welcome to CodeBlog ★"}</span>
|
|
67
75
|
</text>
|
|
68
76
|
<box height={1} />
|
|
69
|
-
<text fg={theme.colors.textMuted}>Let's set up your terminal theme</text>
|
|
70
|
-
<box height={1} />
|
|
71
77
|
</box>
|
|
72
78
|
|
|
73
79
|
{step() === "mode" ? (
|
|
74
80
|
<box flexShrink={0} flexDirection="column" width="100%" maxWidth={50}>
|
|
75
|
-
<text fg={
|
|
76
|
-
<span style={{ bold: true }}>
|
|
81
|
+
<text fg={HC.title}>
|
|
82
|
+
<span style={{ bold: true }}>{"What is your terminal background color?"}</span>
|
|
77
83
|
</text>
|
|
78
84
|
<box height={1} />
|
|
79
85
|
{modes.map((m, i) => (
|
|
80
86
|
<box flexDirection="row" paddingLeft={2}>
|
|
81
|
-
<text fg={modeIdx() === i ?
|
|
87
|
+
<text fg={modeIdx() === i ? HC.selected : HC.dim}>
|
|
82
88
|
{modeIdx() === i ? "❯ " : " "}
|
|
83
89
|
</text>
|
|
84
|
-
<text fg={modeIdx() === i ?
|
|
90
|
+
<text fg={modeIdx() === i ? HC.selected : HC.dim}>
|
|
85
91
|
<span style={{ bold: modeIdx() === i }}>
|
|
86
|
-
{m === "dark" ? "
|
|
92
|
+
{m === "dark" ? "Dark background (black/dark terminal)" : "Light background (white/light terminal)"}
|
|
87
93
|
</span>
|
|
88
94
|
</text>
|
|
89
95
|
</box>
|
|
90
96
|
))}
|
|
91
97
|
<box height={1} />
|
|
92
|
-
<text fg={
|
|
98
|
+
<text fg={HC.text}>{"↑↓ select · Enter confirm"}</text>
|
|
93
99
|
</box>
|
|
94
100
|
) : (
|
|
95
|
-
<box flexShrink={0} flexDirection="column" width="100%" maxWidth={
|
|
101
|
+
<box flexShrink={0} flexDirection="column" width="100%" maxWidth={60}>
|
|
96
102
|
<text fg={theme.colors.text}>
|
|
97
|
-
<span style={{ bold: true }}>
|
|
103
|
+
<span style={{ bold: true }}>{"Choose a color theme:"}</span>
|
|
98
104
|
</text>
|
|
99
105
|
<box height={1} />
|
|
100
106
|
{THEME_NAMES.map((name, i) => {
|
|
101
|
-
const
|
|
102
|
-
const c = def[theme.mode]
|
|
107
|
+
const c = THEMES[name][theme.mode]
|
|
103
108
|
return (
|
|
104
109
|
<box flexDirection="row" paddingLeft={2}>
|
|
105
110
|
<text fg={themeIdx() === i ? c.primary : theme.colors.textMuted}>
|
|
106
111
|
{themeIdx() === i ? "❯ " : " "}
|
|
107
112
|
</text>
|
|
108
113
|
<text fg={themeIdx() === i ? c.text : theme.colors.textMuted}>
|
|
109
|
-
<span style={{ bold: themeIdx() === i }}>
|
|
114
|
+
<span style={{ bold: themeIdx() === i }}>
|
|
115
|
+
{name.padEnd(14)}
|
|
116
|
+
</span>
|
|
110
117
|
</text>
|
|
111
118
|
<text fg={c.logo1}>{" ●"}</text>
|
|
112
119
|
<text fg={c.logo2}>{"●"}</text>
|
|
113
120
|
<text fg={c.primary}>{"●"}</text>
|
|
121
|
+
<text fg={c.accent}>{"●"}</text>
|
|
114
122
|
<text fg={c.success}>{"●"}</text>
|
|
115
123
|
<text fg={c.error}>{"●"}</text>
|
|
116
124
|
</box>
|
|
117
125
|
)
|
|
118
126
|
})}
|
|
119
127
|
<box height={1} />
|
|
120
|
-
<text fg={theme.colors.textMuted}
|
|
128
|
+
<text fg={theme.colors.textMuted}>{"↑↓ select · Enter confirm · Esc back"}</text>
|
|
121
129
|
</box>
|
|
122
130
|
)}
|
|
123
131
|
|
|
@@ -125,3 +133,123 @@ export function ThemeSetup() {
|
|
|
125
133
|
</box>
|
|
126
134
|
)
|
|
127
135
|
}
|
|
136
|
+
|
|
137
|
+
export function ThemePicker(props: { onDone: () => void }) {
|
|
138
|
+
const theme = useTheme()
|
|
139
|
+
const [idx, setIdx] = createSignal(THEME_NAMES.indexOf(theme.name))
|
|
140
|
+
const [tab, setTab] = createSignal<"theme" | "mode">("theme")
|
|
141
|
+
|
|
142
|
+
useKeyboard((evt) => {
|
|
143
|
+
if (tab() === "theme") {
|
|
144
|
+
if (evt.name === "up" || evt.name === "k") {
|
|
145
|
+
const next = (idx() - 1 + THEME_NAMES.length) % THEME_NAMES.length
|
|
146
|
+
setIdx(next)
|
|
147
|
+
theme.set(THEME_NAMES[next])
|
|
148
|
+
evt.preventDefault()
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
if (evt.name === "down" || evt.name === "j") {
|
|
152
|
+
const next = (idx() + 1) % THEME_NAMES.length
|
|
153
|
+
setIdx(next)
|
|
154
|
+
theme.set(THEME_NAMES[next])
|
|
155
|
+
evt.preventDefault()
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
if (evt.name === "tab") {
|
|
159
|
+
setTab("mode")
|
|
160
|
+
evt.preventDefault()
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
if (evt.name === "return" || evt.name === "escape") {
|
|
164
|
+
props.onDone()
|
|
165
|
+
evt.preventDefault()
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (tab() === "mode") {
|
|
171
|
+
if (evt.name === "up" || evt.name === "down" || evt.name === "k" || evt.name === "j") {
|
|
172
|
+
theme.setMode(theme.mode === "dark" ? "light" : "dark")
|
|
173
|
+
evt.preventDefault()
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
if (evt.name === "tab") {
|
|
177
|
+
setTab("theme")
|
|
178
|
+
evt.preventDefault()
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
if (evt.name === "return" || evt.name === "escape") {
|
|
182
|
+
props.onDone()
|
|
183
|
+
evt.preventDefault()
|
|
184
|
+
return
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<box flexDirection="column" flexGrow={1} paddingLeft={2} paddingRight={2} paddingTop={1}>
|
|
191
|
+
<box flexDirection="row" gap={4} flexShrink={0}>
|
|
192
|
+
<text fg={theme.colors.primary}>
|
|
193
|
+
<span style={{ bold: true }}>Theme Settings</span>
|
|
194
|
+
</text>
|
|
195
|
+
<box flexGrow={1} />
|
|
196
|
+
<text fg={theme.colors.textMuted}>{"Tab: switch section · Enter/Esc: done"}</text>
|
|
197
|
+
</box>
|
|
198
|
+
|
|
199
|
+
<box flexDirection="row" flexGrow={1} paddingTop={1} gap={4}>
|
|
200
|
+
{/* Theme list */}
|
|
201
|
+
<box flexDirection="column" width={40}>
|
|
202
|
+
<text fg={tab() === "theme" ? theme.colors.text : theme.colors.textMuted}>
|
|
203
|
+
<span style={{ bold: true }}>{"Color Theme"}</span>
|
|
204
|
+
</text>
|
|
205
|
+
<box height={1} />
|
|
206
|
+
{THEME_NAMES.map((name, i) => {
|
|
207
|
+
const c = THEMES[name][theme.mode]
|
|
208
|
+
return (
|
|
209
|
+
<box flexDirection="row">
|
|
210
|
+
<text fg={idx() === i ? c.primary : theme.colors.textMuted}>
|
|
211
|
+
{idx() === i && tab() === "theme" ? "❯ " : " "}
|
|
212
|
+
</text>
|
|
213
|
+
<text fg={idx() === i ? c.text : theme.colors.textMuted}>
|
|
214
|
+
<span style={{ bold: idx() === i }}>
|
|
215
|
+
{name.padEnd(14)}
|
|
216
|
+
</span>
|
|
217
|
+
</text>
|
|
218
|
+
<text fg={c.logo1}>{" ●"}</text>
|
|
219
|
+
<text fg={c.logo2}>{"●"}</text>
|
|
220
|
+
<text fg={c.primary}>{"●"}</text>
|
|
221
|
+
<text fg={c.accent}>{"●"}</text>
|
|
222
|
+
<text fg={c.success}>{"●"}</text>
|
|
223
|
+
<text fg={c.error}>{"●"}</text>
|
|
224
|
+
</box>
|
|
225
|
+
)
|
|
226
|
+
})}
|
|
227
|
+
</box>
|
|
228
|
+
|
|
229
|
+
{/* Mode toggle */}
|
|
230
|
+
<box flexDirection="column" width={30}>
|
|
231
|
+
<text fg={tab() === "mode" ? theme.colors.text : theme.colors.textMuted}>
|
|
232
|
+
<span style={{ bold: true }}>{"Background Mode"}</span>
|
|
233
|
+
</text>
|
|
234
|
+
<box height={1} />
|
|
235
|
+
<box flexDirection="row">
|
|
236
|
+
<text fg={theme.mode === "dark" && tab() === "mode" ? theme.colors.primary : theme.colors.textMuted}>
|
|
237
|
+
{theme.mode === "dark" && tab() === "mode" ? "❯ " : " "}
|
|
238
|
+
</text>
|
|
239
|
+
<text fg={theme.mode === "dark" ? theme.colors.text : theme.colors.textMuted}>
|
|
240
|
+
<span style={{ bold: theme.mode === "dark" }}>{"Dark"}</span>
|
|
241
|
+
</text>
|
|
242
|
+
</box>
|
|
243
|
+
<box flexDirection="row">
|
|
244
|
+
<text fg={theme.mode === "light" && tab() === "mode" ? theme.colors.primary : theme.colors.textMuted}>
|
|
245
|
+
{theme.mode === "light" && tab() === "mode" ? "❯ " : " "}
|
|
246
|
+
</text>
|
|
247
|
+
<text fg={theme.mode === "light" ? theme.colors.text : theme.colors.textMuted}>
|
|
248
|
+
<span style={{ bold: theme.mode === "light" }}>{"Light"}</span>
|
|
249
|
+
</text>
|
|
250
|
+
</box>
|
|
251
|
+
</box>
|
|
252
|
+
</box>
|
|
253
|
+
</box>
|
|
254
|
+
)
|
|
255
|
+
}
|