@jonsoc/console-app 1.1.34

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.
Files changed (217) hide show
  1. package/.opencode/agent/css.md +149 -0
  2. package/README.md +32 -0
  3. package/package.json +49 -0
  4. package/public/apple-touch-icon-v3.png +1 -0
  5. package/public/apple-touch-icon.png +1 -0
  6. package/public/email +1 -0
  7. package/public/favicon-96x96-v3.png +1 -0
  8. package/public/favicon-96x96.png +1 -0
  9. package/public/favicon-v3.ico +1 -0
  10. package/public/favicon-v3.svg +1 -0
  11. package/public/favicon.ico +1 -0
  12. package/public/favicon.svg +1 -0
  13. package/public/opencode-brand-assets.zip +0 -0
  14. package/public/robots.txt +6 -0
  15. package/public/site.webmanifest +1 -0
  16. package/public/social-share-black.png +1 -0
  17. package/public/social-share-zen.png +1 -0
  18. package/public/social-share.png +1 -0
  19. package/public/theme.json +182 -0
  20. package/public/web-app-manifest-192x192.png +1 -0
  21. package/public/web-app-manifest-512x512.png +1 -0
  22. package/script/generate-sitemap.ts +103 -0
  23. package/src/app.css +1 -0
  24. package/src/app.tsx +27 -0
  25. package/src/asset/black/hero.png +0 -0
  26. package/src/asset/brand/opencode-brand-assets.zip +0 -0
  27. package/src/asset/brand/opencode-logo-dark.png +0 -0
  28. package/src/asset/brand/opencode-logo-dark.svg +16 -0
  29. package/src/asset/brand/opencode-logo-light.png +0 -0
  30. package/src/asset/brand/opencode-logo-light.svg +16 -0
  31. package/src/asset/brand/opencode-wordmark-dark.png +0 -0
  32. package/src/asset/brand/opencode-wordmark-dark.svg +30 -0
  33. package/src/asset/brand/opencode-wordmark-light.png +0 -0
  34. package/src/asset/brand/opencode-wordmark-light.svg +30 -0
  35. package/src/asset/brand/opencode-wordmark-simple-dark.png +0 -0
  36. package/src/asset/brand/opencode-wordmark-simple-dark.svg +22 -0
  37. package/src/asset/brand/opencode-wordmark-simple-light.png +0 -0
  38. package/src/asset/brand/opencode-wordmark-simple-light.svg +22 -0
  39. package/src/asset/brand/preview-opencode-dark.png +0 -0
  40. package/src/asset/brand/preview-opencode-logo-dark.png +0 -0
  41. package/src/asset/brand/preview-opencode-logo-light.png +0 -0
  42. package/src/asset/brand/preview-opencode-wordmark-dark.png +0 -0
  43. package/src/asset/brand/preview-opencode-wordmark-light.png +0 -0
  44. package/src/asset/brand/preview-opencode-wordmark-simple-dark.png +0 -0
  45. package/src/asset/brand/preview-opencode-wordmark-simple-light.png +0 -0
  46. package/src/asset/lander/avatar-adam.png +0 -0
  47. package/src/asset/lander/avatar-david.png +0 -0
  48. package/src/asset/lander/avatar-dax.png +0 -0
  49. package/src/asset/lander/avatar-frank.png +0 -0
  50. package/src/asset/lander/avatar-jay.png +0 -0
  51. package/src/asset/lander/brand-assets-dark.svg +10 -0
  52. package/src/asset/lander/brand-assets-light.svg +10 -0
  53. package/src/asset/lander/brand.png +0 -0
  54. package/src/asset/lander/check.svg +3 -0
  55. package/src/asset/lander/copy.svg +3 -0
  56. package/src/asset/lander/desktop-app-icon.png +0 -0
  57. package/src/asset/lander/dock.png +0 -0
  58. package/src/asset/lander/logo-dark.svg +11 -0
  59. package/src/asset/lander/logo-light.svg +11 -0
  60. package/src/asset/lander/opencode-comparison-min.mp4 +0 -0
  61. package/src/asset/lander/opencode-comparison-poster.png +0 -0
  62. package/src/asset/lander/opencode-desktop-icon.png +0 -0
  63. package/src/asset/lander/opencode-logo-dark.svg +11 -0
  64. package/src/asset/lander/opencode-logo-light.svg +11 -0
  65. package/src/asset/lander/opencode-min.mp4 +0 -0
  66. package/src/asset/lander/opencode-poster.png +0 -0
  67. package/src/asset/lander/opencode-wordmark-dark.svg +25 -0
  68. package/src/asset/lander/opencode-wordmark-light.svg +25 -0
  69. package/src/asset/lander/screenshot-github.png +0 -0
  70. package/src/asset/lander/screenshot-splash.png +0 -0
  71. package/src/asset/lander/screenshot-vscode.png +0 -0
  72. package/src/asset/lander/screenshot.png +0 -0
  73. package/src/asset/lander/wordmark-dark.svg +3 -0
  74. package/src/asset/lander/wordmark-light.svg +3 -0
  75. package/src/asset/logo-ornate-dark.svg +18 -0
  76. package/src/asset/logo-ornate-light.svg +18 -0
  77. package/src/asset/logo.svg +18 -0
  78. package/src/asset/zen-ornate-dark.svg +8 -0
  79. package/src/asset/zen-ornate-light.svg +8 -0
  80. package/src/component/dropdown.css +80 -0
  81. package/src/component/dropdown.tsx +79 -0
  82. package/src/component/email-signup.tsx +48 -0
  83. package/src/component/faq.tsx +33 -0
  84. package/src/component/footer.tsx +38 -0
  85. package/src/component/header-context-menu.css +63 -0
  86. package/src/component/header.tsx +279 -0
  87. package/src/component/icon.tsx +257 -0
  88. package/src/component/legal.tsx +20 -0
  89. package/src/component/modal.css +66 -0
  90. package/src/component/modal.tsx +24 -0
  91. package/src/component/spotlight.css +15 -0
  92. package/src/component/spotlight.tsx +820 -0
  93. package/src/config.ts +29 -0
  94. package/src/context/auth.session.ts +0 -0
  95. package/src/context/auth.ts +116 -0
  96. package/src/context/auth.withActor.ts +7 -0
  97. package/src/entry-client.tsx +4 -0
  98. package/src/entry-server.tsx +30 -0
  99. package/src/global.d.ts +5 -0
  100. package/src/lib/github.ts +38 -0
  101. package/src/middleware.ts +5 -0
  102. package/src/routes/[...404].css +130 -0
  103. package/src/routes/[...404].tsx +38 -0
  104. package/src/routes/api/enterprise.ts +47 -0
  105. package/src/routes/auth/[...callback].ts +41 -0
  106. package/src/routes/auth/authorize.ts +10 -0
  107. package/src/routes/auth/index.ts +12 -0
  108. package/src/routes/auth/logout.ts +17 -0
  109. package/src/routes/auth/status.ts +7 -0
  110. package/src/routes/bench/[id].tsx +365 -0
  111. package/src/routes/bench/index.tsx +86 -0
  112. package/src/routes/bench/submission.ts +29 -0
  113. package/src/routes/black/common.tsx +62 -0
  114. package/src/routes/black/index.tsx +108 -0
  115. package/src/routes/black/subscribe/[plan].tsx +449 -0
  116. package/src/routes/black/workspace.css +214 -0
  117. package/src/routes/black/workspace.tsx +229 -0
  118. package/src/routes/black.css +828 -0
  119. package/src/routes/black.tsx +285 -0
  120. package/src/routes/brand/index.css +555 -0
  121. package/src/routes/brand/index.tsx +252 -0
  122. package/src/routes/changelog/index.css +477 -0
  123. package/src/routes/changelog/index.tsx +147 -0
  124. package/src/routes/debug/index.ts +13 -0
  125. package/src/routes/desktop-feedback.ts +5 -0
  126. package/src/routes/discord.ts +5 -0
  127. package/src/routes/docs/[...path].ts +20 -0
  128. package/src/routes/docs/index.ts +20 -0
  129. package/src/routes/download/[platform].ts +38 -0
  130. package/src/routes/download/index.css +750 -0
  131. package/src/routes/download/index.tsx +482 -0
  132. package/src/routes/download/types.ts +4 -0
  133. package/src/routes/enterprise/index.css +578 -0
  134. package/src/routes/enterprise/index.tsx +251 -0
  135. package/src/routes/index.css +1251 -0
  136. package/src/routes/index.tsx +840 -0
  137. package/src/routes/legal/privacy-policy/index.css +343 -0
  138. package/src/routes/legal/privacy-policy/index.tsx +1512 -0
  139. package/src/routes/legal/terms-of-service/index.css +254 -0
  140. package/src/routes/legal/terms-of-service/index.tsx +512 -0
  141. package/src/routes/openapi.json.ts +7 -0
  142. package/src/routes/s/[id].ts +20 -0
  143. package/src/routes/stripe/webhook.ts +532 -0
  144. package/src/routes/t/[...path].tsx +20 -0
  145. package/src/routes/temp.tsx +172 -0
  146. package/src/routes/user-menu.css +18 -0
  147. package/src/routes/user-menu.tsx +32 -0
  148. package/src/routes/workspace/[id]/billing/billing-section.module.css +185 -0
  149. package/src/routes/workspace/[id]/billing/billing-section.tsx +240 -0
  150. package/src/routes/workspace/[id]/billing/black-section.module.css +142 -0
  151. package/src/routes/workspace/[id]/billing/black-section.tsx +269 -0
  152. package/src/routes/workspace/[id]/billing/black-waitlist-section.module.css +23 -0
  153. package/src/routes/workspace/[id]/billing/index.tsx +32 -0
  154. package/src/routes/workspace/[id]/billing/monthly-limit-section.module.css +96 -0
  155. package/src/routes/workspace/[id]/billing/monthly-limit-section.tsx +133 -0
  156. package/src/routes/workspace/[id]/billing/payment-section.module.css +93 -0
  157. package/src/routes/workspace/[id]/billing/payment-section.tsx +122 -0
  158. package/src/routes/workspace/[id]/billing/reload-section.module.css +261 -0
  159. package/src/routes/workspace/[id]/billing/reload-section.tsx +213 -0
  160. package/src/routes/workspace/[id]/graph-section.module.css +145 -0
  161. package/src/routes/workspace/[id]/graph-section.tsx +475 -0
  162. package/src/routes/workspace/[id]/index.tsx +81 -0
  163. package/src/routes/workspace/[id]/keys/index.tsx +11 -0
  164. package/src/routes/workspace/[id]/keys/key-section.module.css +197 -0
  165. package/src/routes/workspace/[id]/keys/key-section.tsx +176 -0
  166. package/src/routes/workspace/[id]/members/index.tsx +11 -0
  167. package/src/routes/workspace/[id]/members/member-section.module.css +249 -0
  168. package/src/routes/workspace/[id]/members/member-section.tsx +343 -0
  169. package/src/routes/workspace/[id]/members/role-dropdown.css +72 -0
  170. package/src/routes/workspace/[id]/members/role-dropdown.tsx +43 -0
  171. package/src/routes/workspace/[id]/model-section.module.css +173 -0
  172. package/src/routes/workspace/[id]/model-section.tsx +174 -0
  173. package/src/routes/workspace/[id]/new-user-section.module.css +143 -0
  174. package/src/routes/workspace/[id]/new-user-section.tsx +104 -0
  175. package/src/routes/workspace/[id]/provider-section.module.css +138 -0
  176. package/src/routes/workspace/[id]/provider-section.tsx +188 -0
  177. package/src/routes/workspace/[id]/settings/index.tsx +11 -0
  178. package/src/routes/workspace/[id]/settings/settings-section.module.css +94 -0
  179. package/src/routes/workspace/[id]/settings/settings-section.tsx +122 -0
  180. package/src/routes/workspace/[id]/usage-section.module.css +185 -0
  181. package/src/routes/workspace/[id]/usage-section.tsx +200 -0
  182. package/src/routes/workspace/[id].css +308 -0
  183. package/src/routes/workspace/[id].tsx +62 -0
  184. package/src/routes/workspace/common.tsx +120 -0
  185. package/src/routes/workspace-picker.css +74 -0
  186. package/src/routes/workspace-picker.tsx +122 -0
  187. package/src/routes/workspace.css +107 -0
  188. package/src/routes/workspace.tsx +38 -0
  189. package/src/routes/zen/index.css +866 -0
  190. package/src/routes/zen/index.tsx +343 -0
  191. package/src/routes/zen/util/dataDumper.ts +44 -0
  192. package/src/routes/zen/util/error.ts +13 -0
  193. package/src/routes/zen/util/handler.ts +784 -0
  194. package/src/routes/zen/util/logger.ts +12 -0
  195. package/src/routes/zen/util/provider/anthropic.ts +752 -0
  196. package/src/routes/zen/util/provider/google.ts +75 -0
  197. package/src/routes/zen/util/provider/openai-compatible.ts +546 -0
  198. package/src/routes/zen/util/provider/openai.ts +630 -0
  199. package/src/routes/zen/util/provider/provider.ts +210 -0
  200. package/src/routes/zen/util/rateLimiter.ts +41 -0
  201. package/src/routes/zen/util/stickyProviderTracker.ts +16 -0
  202. package/src/routes/zen/util/trialLimiter.ts +49 -0
  203. package/src/routes/zen/v1/chat/completions.ts +11 -0
  204. package/src/routes/zen/v1/messages.ts +11 -0
  205. package/src/routes/zen/v1/models/[model].ts +13 -0
  206. package/src/routes/zen/v1/models.ts +60 -0
  207. package/src/routes/zen/v1/responses.ts +11 -0
  208. package/src/style/base.css +21 -0
  209. package/src/style/component/button.css +102 -0
  210. package/src/style/index.css +8 -0
  211. package/src/style/reset.css +76 -0
  212. package/src/style/token/color.css +91 -0
  213. package/src/style/token/font.css +21 -0
  214. package/src/style/token/space.css +46 -0
  215. package/sst-env.d.ts +9 -0
  216. package/tsconfig.json +21 -0
  217. package/vite.config.ts +25 -0
@@ -0,0 +1,477 @@
1
+ ::selection {
2
+ background: var(--color-background-interactive);
3
+ color: var(--color-text-strong);
4
+
5
+ @media (prefers-color-scheme: dark) {
6
+ background: var(--color-background-interactive);
7
+ color: var(--color-text-inverted);
8
+ }
9
+ }
10
+
11
+ [data-page="changelog"] {
12
+ --color-background: hsl(0, 20%, 99%);
13
+ --color-background-weak: hsl(0, 8%, 97%);
14
+ --color-background-weak-hover: hsl(0, 8%, 94%);
15
+ --color-background-strong: hsl(0, 5%, 12%);
16
+ --color-background-strong-hover: hsl(0, 5%, 18%);
17
+ --color-background-interactive: hsl(62, 84%, 88%);
18
+ --color-background-interactive-weaker: hsl(64, 74%, 95%);
19
+
20
+ --color-text: hsl(0, 1%, 39%);
21
+ --color-text-weak: hsl(0, 1%, 60%);
22
+ --color-text-weaker: hsl(30, 2%, 81%);
23
+ --color-text-strong: hsl(0, 5%, 12%);
24
+ --color-text-inverted: hsl(0, 20%, 99%);
25
+
26
+ --color-border: hsl(30, 2%, 81%);
27
+ --color-border-weak: hsl(0, 1%, 85%);
28
+
29
+ --color-icon: hsl(0, 1%, 55%);
30
+
31
+ background: var(--color-background);
32
+ font-family: var(--font-mono);
33
+ color: var(--color-text);
34
+ padding-bottom: 5rem;
35
+
36
+ @media (prefers-color-scheme: dark) {
37
+ --color-background: hsl(0, 9%, 7%);
38
+ --color-background-weak: hsl(0, 6%, 10%);
39
+ --color-background-weak-hover: hsl(0, 6%, 15%);
40
+ --color-background-strong: hsl(0, 15%, 94%);
41
+ --color-background-strong-hover: hsl(0, 15%, 97%);
42
+ --color-background-interactive: hsl(62, 100%, 90%);
43
+ --color-background-interactive-weaker: hsl(60, 20%, 8%);
44
+
45
+ --color-text: hsl(0, 4%, 71%);
46
+ --color-text-weak: hsl(0, 2%, 49%);
47
+ --color-text-weaker: hsl(0, 3%, 28%);
48
+ --color-text-strong: hsl(0, 15%, 94%);
49
+ --color-text-inverted: hsl(0, 9%, 7%);
50
+
51
+ --color-border: hsl(0, 3%, 28%);
52
+ --color-border-weak: hsl(0, 4%, 23%);
53
+
54
+ --color-icon: hsl(10, 3%, 43%);
55
+ }
56
+
57
+ /* Header styles - copied from download */
58
+ [data-component="top"] {
59
+ padding: 24px 5rem;
60
+ height: 80px;
61
+ position: sticky;
62
+ top: 0;
63
+ display: flex;
64
+ justify-content: space-between;
65
+ align-items: center;
66
+ background: var(--color-background);
67
+ border-bottom: 1px solid var(--color-border-weak);
68
+ z-index: 10;
69
+
70
+ @media (max-width: 60rem) {
71
+ padding: 24px 1.5rem;
72
+ }
73
+
74
+ img {
75
+ height: 34px;
76
+ width: auto;
77
+ }
78
+
79
+ [data-component="nav-desktop"] {
80
+ ul {
81
+ display: flex;
82
+ justify-content: space-between;
83
+ align-items: center;
84
+ gap: 48px;
85
+
86
+ @media (max-width: 55rem) {
87
+ gap: 32px;
88
+ }
89
+
90
+ @media (max-width: 48rem) {
91
+ gap: 24px;
92
+ }
93
+ li {
94
+ display: inline-block;
95
+ a {
96
+ text-decoration: none;
97
+ span {
98
+ color: var(--color-text-weak);
99
+ }
100
+ }
101
+ a:hover {
102
+ text-decoration: underline;
103
+ text-underline-offset: 2px;
104
+ text-decoration-thickness: 1px;
105
+ }
106
+ [data-slot="cta-button"] {
107
+ background: var(--color-background-strong);
108
+ color: var(--color-text-inverted);
109
+ padding: 8px 16px;
110
+ border-radius: 4px;
111
+ font-weight: 500;
112
+ text-decoration: none;
113
+
114
+ @media (max-width: 55rem) {
115
+ display: none;
116
+ }
117
+ }
118
+ [data-slot="cta-button"]:hover {
119
+ background: var(--color-background-strong-hover);
120
+ text-decoration: none;
121
+ }
122
+ }
123
+ }
124
+
125
+ @media (max-width: 40rem) {
126
+ display: none;
127
+ }
128
+ }
129
+
130
+ [data-component="nav-mobile"] {
131
+ button > svg {
132
+ color: var(--color-icon);
133
+ }
134
+ }
135
+
136
+ [data-component="nav-mobile-toggle"] {
137
+ border: none;
138
+ background: none;
139
+ outline: none;
140
+ height: 40px;
141
+ width: 40px;
142
+ cursor: pointer;
143
+ margin-right: -8px;
144
+ }
145
+
146
+ [data-component="nav-mobile-toggle"]:hover {
147
+ background: var(--color-background-weak);
148
+ }
149
+
150
+ [data-component="nav-mobile"] {
151
+ display: none;
152
+
153
+ @media (max-width: 40rem) {
154
+ display: block;
155
+
156
+ [data-component="nav-mobile-icon"] {
157
+ cursor: pointer;
158
+ height: 40px;
159
+ width: 40px;
160
+ display: flex;
161
+ align-items: center;
162
+ justify-content: center;
163
+ }
164
+
165
+ [data-component="nav-mobile-menu-list"] {
166
+ position: fixed;
167
+ background: var(--color-background);
168
+ top: 80px;
169
+ left: 0;
170
+ right: 0;
171
+ height: 100vh;
172
+
173
+ ul {
174
+ list-style: none;
175
+ padding: 20px 0;
176
+
177
+ li {
178
+ a {
179
+ text-decoration: none;
180
+ padding: 20px;
181
+ display: block;
182
+
183
+ span {
184
+ color: var(--color-text-weak);
185
+ }
186
+ }
187
+
188
+ a:hover {
189
+ background: var(--color-background-weak);
190
+ }
191
+ }
192
+ }
193
+ }
194
+ }
195
+ }
196
+
197
+ [data-slot="logo dark"] {
198
+ display: none;
199
+ }
200
+
201
+ @media (prefers-color-scheme: dark) {
202
+ [data-slot="logo light"] {
203
+ display: none;
204
+ }
205
+ [data-slot="logo dark"] {
206
+ display: block;
207
+ }
208
+ }
209
+ }
210
+
211
+ [data-component="footer"] {
212
+ border-top: 1px solid var(--color-border-weak);
213
+ display: flex;
214
+ flex-direction: row;
215
+ margin-top: 4rem;
216
+
217
+ @media (max-width: 65rem) {
218
+ border-bottom: 1px solid var(--color-border-weak);
219
+ }
220
+
221
+ [data-slot="cell"] {
222
+ flex: 1;
223
+ text-align: center;
224
+
225
+ a {
226
+ text-decoration: none;
227
+ padding: 2rem 0;
228
+ width: 100%;
229
+ display: block;
230
+
231
+ span {
232
+ color: var(--color-text-weak);
233
+
234
+ @media (max-width: 40rem) {
235
+ display: none;
236
+ }
237
+ }
238
+ }
239
+
240
+ a:hover {
241
+ background: var(--color-background-weak);
242
+ text-decoration: underline;
243
+ text-underline-offset: 2px;
244
+ text-decoration-thickness: 1px;
245
+ }
246
+ }
247
+
248
+ [data-slot="cell"] + [data-slot="cell"] {
249
+ border-left: 1px solid var(--color-border-weak);
250
+
251
+ @media (max-width: 40rem) {
252
+ border-left: none;
253
+ }
254
+ }
255
+
256
+ @media (max-width: 25rem) {
257
+ flex-wrap: wrap;
258
+
259
+ [data-slot="cell"] {
260
+ flex: 1 0 100%;
261
+ border-left: none;
262
+ border-top: 1px solid var(--color-border-weak);
263
+ }
264
+
265
+ [data-slot="cell"]:nth-child(1) {
266
+ border-top: none;
267
+ }
268
+ }
269
+ }
270
+
271
+ [data-component="container"] {
272
+ max-width: 67.5rem;
273
+ margin: 0 auto;
274
+ border: 1px solid var(--color-border-weak);
275
+ border-top: none;
276
+
277
+ @media (max-width: 65rem) {
278
+ border: none;
279
+ }
280
+ }
281
+
282
+ [data-component="content"] {
283
+ padding: 6rem 5rem;
284
+
285
+ @media (max-width: 60rem) {
286
+ padding: 4rem 1.5rem;
287
+ }
288
+ }
289
+
290
+ [data-component="legal"] {
291
+ color: var(--color-text-weak);
292
+ text-align: center;
293
+ padding: 2rem 5rem;
294
+ display: flex;
295
+ gap: 32px;
296
+ justify-content: center;
297
+
298
+ @media (max-width: 60rem) {
299
+ padding: 2rem 1.5rem;
300
+ }
301
+
302
+ a {
303
+ color: var(--color-text-weak);
304
+ text-decoration: none;
305
+ }
306
+
307
+ a:hover {
308
+ color: var(--color-text);
309
+ text-decoration: underline;
310
+ }
311
+ }
312
+
313
+ /* Changelog Hero */
314
+ [data-component="changelog-hero"] {
315
+ margin-bottom: 4rem;
316
+ padding-bottom: 2rem;
317
+ border-bottom: 1px solid var(--color-border-weak);
318
+
319
+ @media (max-width: 50rem) {
320
+ margin-bottom: 2rem;
321
+ padding-bottom: 1.5rem;
322
+ }
323
+
324
+ h1 {
325
+ font-size: 1.5rem;
326
+ font-weight: 700;
327
+ color: var(--color-text-strong);
328
+ margin-bottom: 8px;
329
+ }
330
+
331
+ p {
332
+ color: var(--color-text);
333
+ }
334
+ }
335
+
336
+ /* Releases */
337
+ [data-component="releases"] {
338
+ display: flex;
339
+ flex-direction: column;
340
+ gap: 0;
341
+ }
342
+
343
+ [data-component="release"] {
344
+ display: grid;
345
+ grid-template-columns: 180px 1fr;
346
+ gap: 3rem;
347
+ padding: 2rem 0;
348
+ border-bottom: 1px solid var(--color-border-weak);
349
+
350
+ @media (max-width: 50rem) {
351
+ grid-template-columns: 1fr;
352
+ gap: 1rem;
353
+ }
354
+
355
+ &:first-child {
356
+ padding-top: 0;
357
+ }
358
+
359
+ &:last-child {
360
+ border-bottom: none;
361
+ }
362
+
363
+ header {
364
+ display: flex;
365
+ flex-direction: column;
366
+ gap: 4px;
367
+
368
+ @media (max-width: 50rem) {
369
+ flex-direction: row;
370
+ align-items: center;
371
+ gap: 12px;
372
+ }
373
+
374
+ [data-slot="version"] {
375
+ a {
376
+ font-weight: 600;
377
+ color: var(--color-text-strong);
378
+ text-decoration: none;
379
+
380
+ &:hover {
381
+ text-decoration: underline;
382
+ text-underline-offset: 2px;
383
+ text-decoration-thickness: 1px;
384
+ }
385
+ }
386
+ }
387
+
388
+ time {
389
+ color: var(--color-text-weak);
390
+ font-size: 14px;
391
+ }
392
+ }
393
+
394
+ [data-slot="content"] {
395
+ display: flex;
396
+ flex-direction: column;
397
+ gap: 1.5rem;
398
+ }
399
+
400
+ [data-component="section"] {
401
+ h3 {
402
+ font-size: 14px;
403
+ font-weight: 600;
404
+ color: var(--color-text-strong);
405
+ margin-bottom: 8px;
406
+ }
407
+
408
+ ul {
409
+ list-style: none;
410
+ padding: 0;
411
+ margin: 0;
412
+ display: flex;
413
+ flex-direction: column;
414
+ gap: 6px;
415
+
416
+ li {
417
+ color: var(--color-text);
418
+ line-height: 1.5;
419
+ padding-left: 16px;
420
+ position: relative;
421
+
422
+ &::before {
423
+ content: "-";
424
+ position: absolute;
425
+ left: 0;
426
+ color: var(--color-text-weak);
427
+ }
428
+
429
+ [data-slot="author"] {
430
+ color: var(--color-text-weak);
431
+ font-size: 13px;
432
+ margin-left: 4px;
433
+ text-decoration: none;
434
+
435
+ &:hover {
436
+ text-decoration: underline;
437
+ text-underline-offset: 2px;
438
+ text-decoration-thickness: 1px;
439
+ }
440
+ }
441
+ }
442
+ }
443
+ }
444
+
445
+ [data-component="contributors"] {
446
+ font-size: 13px;
447
+ color: var(--color-text-weak);
448
+ padding-top: 0.5rem;
449
+
450
+ span {
451
+ color: var(--color-text-weak);
452
+ }
453
+
454
+ a {
455
+ color: var(--color-text);
456
+ text-decoration: none;
457
+
458
+ &:hover {
459
+ text-decoration: underline;
460
+ text-underline-offset: 2px;
461
+ text-decoration-thickness: 1px;
462
+ }
463
+ }
464
+ }
465
+ }
466
+
467
+ a {
468
+ color: var(--color-text-strong);
469
+ text-decoration: underline;
470
+ text-underline-offset: 2px;
471
+ text-decoration-thickness: 1px;
472
+
473
+ &:hover {
474
+ text-decoration-thickness: 2px;
475
+ }
476
+ }
477
+ }
@@ -0,0 +1,147 @@
1
+ import "./index.css"
2
+ import { Title, Meta, Link } from "@solidjs/meta"
3
+ import { createAsync, query } from "@solidjs/router"
4
+ import { Header } from "~/component/header"
5
+ import { Footer } from "~/component/footer"
6
+ import { Legal } from "~/component/legal"
7
+ import { config } from "~/config"
8
+ import { For, Show } from "solid-js"
9
+
10
+ type Release = {
11
+ tag_name: string
12
+ name: string
13
+ body: string
14
+ published_at: string
15
+ html_url: string
16
+ }
17
+
18
+ const getReleases = query(async () => {
19
+ "use server"
20
+ const response = await fetch("https://api.github.com/repos/anomalyco/jonsoc/releases?per_page=20", {
21
+ headers: {
22
+ Accept: "application/vnd.github.v3+json",
23
+ "User-Agent": "JonsOC-Console",
24
+ },
25
+ cf: {
26
+ cacheTtl: 60 * 5,
27
+ cacheEverything: true,
28
+ },
29
+ } as any)
30
+ if (!response.ok) return []
31
+ return response.json() as Promise<Release[]>
32
+ }, "releases.get")
33
+
34
+ function formatDate(dateString: string) {
35
+ const date = new Date(dateString)
36
+ return date.toLocaleDateString("en-US", {
37
+ year: "numeric",
38
+ month: "short",
39
+ day: "numeric",
40
+ })
41
+ }
42
+
43
+ function parseMarkdown(body: string) {
44
+ const lines = body.split("\n")
45
+ const sections: { title: string; items: string[] }[] = []
46
+ let current: { title: string; items: string[] } | null = null
47
+ let skip = false
48
+
49
+ for (const line of lines) {
50
+ if (line.startsWith("## ")) {
51
+ if (current) sections.push(current)
52
+ const title = line.slice(3).trim()
53
+ current = { title, items: [] }
54
+ skip = false
55
+ } else if (line.startsWith("**Thank you")) {
56
+ skip = true
57
+ } else if (line.startsWith("- ") && !skip) {
58
+ current?.items.push(line.slice(2).trim())
59
+ }
60
+ }
61
+ if (current) sections.push(current)
62
+
63
+ return { sections }
64
+ }
65
+
66
+ function ReleaseItem(props: { item: string }) {
67
+ const parts = () => {
68
+ const match = props.item.match(/^(.+?)(\s*\(@([\w-]+)\))?$/)
69
+ if (match) {
70
+ return {
71
+ text: match[1],
72
+ username: match[3],
73
+ }
74
+ }
75
+ return { text: props.item, username: undefined }
76
+ }
77
+
78
+ return (
79
+ <li>
80
+ <span>{parts().text}</span>
81
+ <Show when={parts().username}>
82
+ <a data-slot="author" href={`https://github.com/${parts().username}`} target="_blank" rel="noopener noreferrer">
83
+ (@{parts().username})
84
+ </a>
85
+ </Show>
86
+ </li>
87
+ )
88
+ }
89
+
90
+ export default function Changelog() {
91
+ const releases = createAsync(() => getReleases())
92
+
93
+ return (
94
+ <main data-page="changelog">
95
+ <Title>JonsOC | Changelog</Title>
96
+ <Link rel="canonical" href={`${config.baseUrl}/changelog`} />
97
+ <Meta name="description" content="JonsOC release notes and changelog" />
98
+
99
+ <div data-component="container">
100
+ <Header hideGetStarted />
101
+
102
+ <div data-component="content">
103
+ <section data-component="changelog-hero">
104
+ <h1>Changelog</h1>
105
+ <p>New updates and improvements to JonsOC</p>
106
+ </section>
107
+
108
+ <section data-component="releases">
109
+ <For each={releases()}>
110
+ {(release) => {
111
+ const parsed = () => parseMarkdown(release.body || "")
112
+ return (
113
+ <article data-component="release">
114
+ <header>
115
+ <div data-slot="version">
116
+ <a href={release.html_url} target="_blank" rel="noopener noreferrer">
117
+ {release.tag_name}
118
+ </a>
119
+ </div>
120
+ <time dateTime={release.published_at}>{formatDate(release.published_at)}</time>
121
+ </header>
122
+ <div data-slot="content">
123
+ <For each={parsed().sections}>
124
+ {(section) => (
125
+ <div data-component="section">
126
+ <h3>{section.title}</h3>
127
+ <ul>
128
+ <For each={section.items}>{(item) => <ReleaseItem item={item} />}</For>
129
+ </ul>
130
+ </div>
131
+ )}
132
+ </For>
133
+ </div>
134
+ </article>
135
+ )
136
+ }}
137
+ </For>
138
+ </section>
139
+
140
+ <Footer />
141
+ </div>
142
+ </div>
143
+
144
+ <Legal />
145
+ </main>
146
+ )
147
+ }
@@ -0,0 +1,13 @@
1
+ import type { APIEvent } from "@solidjs/start/server"
2
+ import { json } from "@solidjs/router"
3
+ import { Database } from "@jonsoc/console-core/drizzle/index.js"
4
+ import { UserTable } from "@jonsoc/console-core/schema/user.sql.js"
5
+
6
+ export async function GET(evt: APIEvent) {
7
+ return json({
8
+ data: await Database.use(async (tx) => {
9
+ const result = await tx.$count(UserTable)
10
+ return result
11
+ }),
12
+ })
13
+ }
@@ -0,0 +1,5 @@
1
+ import { redirect } from "@solidjs/router"
2
+
3
+ export async function GET() {
4
+ return redirect("https://discord.gg/h5TNnkFVNy")
5
+ }
@@ -0,0 +1,5 @@
1
+ import { redirect } from "@solidjs/router"
2
+
3
+ export async function GET() {
4
+ return redirect("https://discord.gg/jonsoc")
5
+ }
@@ -0,0 +1,20 @@
1
+ import type { APIEvent } from "@solidjs/start/server"
2
+
3
+ async function handler(evt: APIEvent) {
4
+ const req = evt.request.clone()
5
+ const url = new URL(req.url)
6
+ const targetUrl = `https://docs.jonsoc.com${url.pathname}${url.search}`
7
+ const response = await fetch(targetUrl, {
8
+ method: req.method,
9
+ headers: req.headers,
10
+ body: req.body,
11
+ })
12
+ return response
13
+ }
14
+
15
+ export const GET = handler
16
+ export const POST = handler
17
+ export const PUT = handler
18
+ export const DELETE = handler
19
+ export const OPTIONS = handler
20
+ export const PATCH = handler
@@ -0,0 +1,20 @@
1
+ import type { APIEvent } from "@solidjs/start/server"
2
+
3
+ async function handler(evt: APIEvent) {
4
+ const req = evt.request.clone()
5
+ const url = new URL(req.url)
6
+ const targetUrl = `https://docs.jonsoc.com${url.pathname}${url.search}`
7
+ const response = await fetch(targetUrl, {
8
+ method: req.method,
9
+ headers: req.headers,
10
+ body: req.body,
11
+ })
12
+ return response
13
+ }
14
+
15
+ export const GET = handler
16
+ export const POST = handler
17
+ export const PUT = handler
18
+ export const DELETE = handler
19
+ export const OPTIONS = handler
20
+ export const PATCH = handler