toiljs 0.0.115 → 0.0.117

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 (326) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +16 -7
  3. package/TYPESCRIPT_LAW.md +1946 -2073
  4. package/build/backend/.tsbuildinfo +1 -1
  5. package/build/cli/.tsbuildinfo +1 -1
  6. package/build/cli/index.js +2375 -258
  7. package/build/client/.tsbuildinfo +1 -1
  8. package/build/client/dev/devtools.js +1 -1
  9. package/build/client/dev/error-overlay.js +1 -1
  10. package/build/client/routing/mount.js +1 -1
  11. package/build/compiler/.tsbuildinfo +1 -1
  12. package/build/compiler/auth-emails.js +7 -3
  13. package/build/compiler/emails.js +1 -1
  14. package/build/compiler/index.d.ts +1 -0
  15. package/build/compiler/index.js +25 -4
  16. package/build/compiler/pages.js +2 -3
  17. package/build/compiler/prerender.d.ts +3 -8
  18. package/build/compiler/prerender.js +88 -127
  19. package/build/compiler/ssg.js +3 -4
  20. package/build/compiler/template-build.js +3 -4
  21. package/build/compiler/toil-docs.generated.js +6 -5
  22. package/build/compiler/vite.js +3 -6
  23. package/build/devserver/.tsbuildinfo +1 -1
  24. package/build/devserver/db/database.js +7 -3
  25. package/build/devserver/db/types.js +1 -1
  26. package/build/devserver/production.js +4 -3
  27. package/build/devserver/runtime/crypto.d.ts +1 -1
  28. package/build/io/.tsbuildinfo +1 -1
  29. package/build/logger/.tsbuildinfo +1 -1
  30. package/build/shared/.tsbuildinfo +1 -1
  31. package/examples/basic/client/routes/analytics.tsx +7 -5
  32. package/examples/basic/server/tsconfig.json +1 -3
  33. package/package.json +45 -44
  34. package/presets/no-uint8array-tostring.js +120 -153
  35. package/presets/oxlint.d.ts +5 -0
  36. package/presets/oxlint.js +224 -0
  37. package/server/runtime/tsconfig.json +1 -3
  38. package/tsconfig.json +2 -11
  39. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -38
  40. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -90
  41. package/.github/ISSUE_TEMPLATE/config.yml +0 -8
  42. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  43. package/.github/PULL_REQUEST_TEMPLATE.md +0 -43
  44. package/.github/changelog-config.json +0 -45
  45. package/.github/dependabot.yml +0 -27
  46. package/.github/workflows/ci.yml +0 -222
  47. package/.vscode/settings.json +0 -10
  48. package/docs/README.md +0 -107
  49. package/docs/auth/README.md +0 -104
  50. package/docs/auth/configuration.md +0 -105
  51. package/docs/auth/emails.md +0 -92
  52. package/docs/auth/extending.md +0 -202
  53. package/docs/auth/how-it-works.md +0 -138
  54. package/docs/auth/usage.md +0 -188
  55. package/docs/backend/README.md +0 -143
  56. package/docs/backend/data.md +0 -351
  57. package/docs/backend/rest.md +0 -402
  58. package/docs/backend/rpc.md +0 -226
  59. package/docs/background/README.md +0 -114
  60. package/docs/background/daemons.md +0 -276
  61. package/docs/background/derive.md +0 -179
  62. package/docs/cli/README.md +0 -379
  63. package/docs/concepts/ai-guide.md +0 -132
  64. package/docs/concepts/config.md +0 -416
  65. package/docs/concepts/decorators.md +0 -127
  66. package/docs/concepts/security.md +0 -108
  67. package/docs/concepts/tiers.md +0 -166
  68. package/docs/concepts/types.md +0 -216
  69. package/docs/database/README.md +0 -143
  70. package/docs/database/capacity.md +0 -350
  71. package/docs/database/counters.md +0 -174
  72. package/docs/database/documents.md +0 -277
  73. package/docs/database/events.md +0 -307
  74. package/docs/database/membership.md +0 -246
  75. package/docs/database/setup.md +0 -155
  76. package/docs/database/unique.md +0 -216
  77. package/docs/database/views.md +0 -246
  78. package/docs/frontend/README.md +0 -104
  79. package/docs/frontend/components.md +0 -286
  80. package/docs/frontend/data-fetching.md +0 -260
  81. package/docs/frontend/images.md +0 -148
  82. package/docs/frontend/metadata.md +0 -344
  83. package/docs/frontend/navigation.md +0 -296
  84. package/docs/frontend/rendering.md +0 -236
  85. package/docs/frontend/routing.md +0 -346
  86. package/docs/frontend/scripts.md +0 -118
  87. package/docs/frontend/search.md +0 -191
  88. package/docs/frontend/styling.md +0 -147
  89. package/docs/frontend/toil-global.md +0 -216
  90. package/docs/getting-started/README.md +0 -81
  91. package/docs/getting-started/create-project.md +0 -131
  92. package/docs/getting-started/deploy.md +0 -101
  93. package/docs/getting-started/first-app.md +0 -215
  94. package/docs/getting-started/installation.md +0 -124
  95. package/docs/getting-started/migrating.md +0 -125
  96. package/docs/getting-started/project-structure.md +0 -163
  97. package/docs/introduction/README.md +0 -66
  98. package/docs/introduction/design-principles.md +0 -81
  99. package/docs/introduction/distributed.md +0 -91
  100. package/docs/introduction/how-it-works.md +0 -121
  101. package/docs/introduction/hyperscale.md +0 -70
  102. package/docs/introduction/modern-stack.md +0 -85
  103. package/docs/introduction/vs-other-frameworks.md +0 -64
  104. package/docs/introduction/why-toil.md +0 -99
  105. package/docs/llms.txt +0 -95
  106. package/docs/realtime/README.md +0 -102
  107. package/docs/realtime/channels.md +0 -211
  108. package/docs/realtime/streams.md +0 -369
  109. package/docs/services/README.md +0 -60
  110. package/docs/services/analytics.md +0 -279
  111. package/docs/services/caching.md +0 -175
  112. package/docs/services/cookies.md +0 -235
  113. package/docs/services/crypto.md +0 -209
  114. package/docs/services/email.md +0 -289
  115. package/docs/services/environment.md +0 -141
  116. package/docs/services/ratelimit.md +0 -174
  117. package/docs/services/time.md +0 -85
  118. package/eslint.config.js +0 -57
  119. package/examples/basic/client/routes/docs/[...slug].tsx +0 -12
  120. package/presets/eslint.js +0 -88
  121. package/src/backend/index.ts +0 -204
  122. package/src/cli/configure.ts +0 -363
  123. package/src/cli/create.ts +0 -924
  124. package/src/cli/db.ts +0 -158
  125. package/src/cli/diagnostics.ts +0 -665
  126. package/src/cli/doctor.ts +0 -982
  127. package/src/cli/features.ts +0 -170
  128. package/src/cli/index.ts +0 -326
  129. package/src/cli/notify.ts +0 -120
  130. package/src/cli/proc.ts +0 -56
  131. package/src/cli/ui.ts +0 -169
  132. package/src/cli/update.ts +0 -248
  133. package/src/cli/updates.ts +0 -92
  134. package/src/cli/validate.ts +0 -31
  135. package/src/cli/version-check.ts +0 -125
  136. package/src/client/auth.ts +0 -869
  137. package/src/client/channel/channel.ts +0 -146
  138. package/src/client/components/Form.tsx +0 -65
  139. package/src/client/components/Image.tsx +0 -160
  140. package/src/client/components/Script.tsx +0 -113
  141. package/src/client/components/Slot.tsx +0 -21
  142. package/src/client/dev/devtools.tsx +0 -1089
  143. package/src/client/dev/error-overlay.tsx +0 -225
  144. package/src/client/errors.ts +0 -11
  145. package/src/client/head/head.ts +0 -182
  146. package/src/client/head/metadata.ts +0 -110
  147. package/src/client/index.ts +0 -130
  148. package/src/client/navigation/Link.tsx +0 -108
  149. package/src/client/navigation/NavLink.tsx +0 -86
  150. package/src/client/navigation/navigation.ts +0 -235
  151. package/src/client/navigation/prefetch.ts +0 -169
  152. package/src/client/navigation/scroll.ts +0 -53
  153. package/src/client/routing/Router.tsx +0 -216
  154. package/src/client/routing/action.ts +0 -122
  155. package/src/client/routing/error-boundary.tsx +0 -43
  156. package/src/client/routing/hooks.ts +0 -140
  157. package/src/client/routing/lazy.ts +0 -77
  158. package/src/client/routing/loader.ts +0 -381
  159. package/src/client/routing/match.ts +0 -47
  160. package/src/client/routing/mount.tsx +0 -90
  161. package/src/client/routing/params-context.ts +0 -10
  162. package/src/client/routing/slot-context.ts +0 -7
  163. package/src/client/rpc.ts +0 -97
  164. package/src/client/search/search.ts +0 -189
  165. package/src/client/search/use-page-search.ts +0 -73
  166. package/src/client/ssr/markers.tsx +0 -172
  167. package/src/client/stream/client.ts +0 -237
  168. package/src/client/types.ts +0 -73
  169. package/src/compiler/auth-emails.ts +0 -312
  170. package/src/compiler/config.ts +0 -369
  171. package/src/compiler/docs.ts +0 -127
  172. package/src/compiler/email-preview.ts +0 -314
  173. package/src/compiler/emails.ts +0 -404
  174. package/src/compiler/fonts.ts +0 -89
  175. package/src/compiler/generate.ts +0 -471
  176. package/src/compiler/image-blur.ts +0 -42
  177. package/src/compiler/image-report.ts +0 -88
  178. package/src/compiler/index.ts +0 -1362
  179. package/src/compiler/pages.ts +0 -49
  180. package/src/compiler/plugin.ts +0 -391
  181. package/src/compiler/prerender.ts +0 -263
  182. package/src/compiler/routes.ts +0 -170
  183. package/src/compiler/seo.ts +0 -415
  184. package/src/compiler/sri.ts +0 -144
  185. package/src/compiler/ssg.ts +0 -177
  186. package/src/compiler/ssr-codegen.ts +0 -85
  187. package/src/compiler/template-build.ts +0 -676
  188. package/src/compiler/template.ts +0 -262
  189. package/src/compiler/toil-docs.generated.ts +0 -77
  190. package/src/compiler/vite.ts +0 -245
  191. package/src/devserver/analytics/index.ts +0 -317
  192. package/src/devserver/config/dotenv.ts +0 -102
  193. package/src/devserver/config/env.ts +0 -30
  194. package/src/devserver/config/ratelimit.ts +0 -123
  195. package/src/devserver/daemon/catalog.ts +0 -120
  196. package/src/devserver/daemon/cron.ts +0 -87
  197. package/src/devserver/daemon/host.ts +0 -134
  198. package/src/devserver/daemon/index.ts +0 -369
  199. package/src/devserver/daemon/runtime.ts +0 -48
  200. package/src/devserver/db/catalog.ts +0 -109
  201. package/src/devserver/db/database.ts +0 -1834
  202. package/src/devserver/db/derives.ts +0 -121
  203. package/src/devserver/db/index.ts +0 -20
  204. package/src/devserver/db/routeKinds.ts +0 -191
  205. package/src/devserver/db/types.ts +0 -168
  206. package/src/devserver/email/caps.ts +0 -0
  207. package/src/devserver/email/config.ts +0 -141
  208. package/src/devserver/email/console.ts +0 -348
  209. package/src/devserver/email/index.ts +0 -155
  210. package/src/devserver/email/providers.ts +0 -131
  211. package/src/devserver/email/status.ts +0 -23
  212. package/src/devserver/email/validate.ts +0 -37
  213. package/src/devserver/email/wire.ts +0 -55
  214. package/src/devserver/http/cache.ts +0 -0
  215. package/src/devserver/http/envelope.ts +0 -157
  216. package/src/devserver/http/proxy.ts +0 -172
  217. package/src/devserver/http/runtime.ts +0 -387
  218. package/src/devserver/index.ts +0 -43
  219. package/src/devserver/production-ipc.ts +0 -63
  220. package/src/devserver/production-worker.ts +0 -83
  221. package/src/devserver/production.ts +0 -718
  222. package/src/devserver/runtime/crypto.ts +0 -587
  223. package/src/devserver/runtime/host.ts +0 -427
  224. package/src/devserver/runtime/module.ts +0 -442
  225. package/src/devserver/server.ts +0 -251
  226. package/src/devserver/ssr.ts +0 -208
  227. package/src/devserver/stream/catalog.ts +0 -106
  228. package/src/devserver/stream/host.ts +0 -42
  229. package/src/devserver/stream/index.ts +0 -301
  230. package/src/devserver/stream/manager.ts +0 -167
  231. package/src/devserver/stream/router.ts +0 -101
  232. package/src/devserver/stream/wire.ts +0 -58
  233. package/src/devserver/stream/ws.ts +0 -78
  234. package/src/devserver/wasm/sections.ts +0 -59
  235. package/src/devserver/wasm/surface.ts +0 -115
  236. package/src/io/FastMap.ts +0 -151
  237. package/src/io/FastSet.ts +0 -113
  238. package/src/io/codec.ts +0 -362
  239. package/src/io/index.ts +0 -10
  240. package/src/io/lengths.ts +0 -14
  241. package/src/io/types.ts +0 -19
  242. package/src/logger/index.ts +0 -22
  243. package/src/shared/index.ts +0 -46
  244. package/test/analytics-dev.test.ts +0 -113
  245. package/test/assembly/aspect-shim.ts +0 -33
  246. package/test/assembly/cookie.spec.ts +0 -303
  247. package/test/assembly/example.spec.ts +0 -22
  248. package/test/assembly/ssr.spec.ts +0 -95
  249. package/test/assembly.test.ts +0 -59
  250. package/test/auth-emails.test.ts +0 -118
  251. package/test/built-ssr.test.ts +0 -148
  252. package/test/channel.test.ts +0 -21
  253. package/test/config-host-port.test.ts +0 -59
  254. package/test/configure.test.ts +0 -94
  255. package/test/daemon-build.test.ts +0 -278
  256. package/test/daemon-catalog.test.ts +0 -349
  257. package/test/daemon-emulation.test.ts +0 -256
  258. package/test/db.test.ts +0 -0
  259. package/test/devserver-database.test.ts +0 -1102
  260. package/test/devserver-email.test.ts +0 -306
  261. package/test/devserver-pqauth.test.ts +0 -93
  262. package/test/devserver-secrets.test.ts +0 -63
  263. package/test/devserver.test.ts +0 -259
  264. package/test/doctor.test.ts +0 -298
  265. package/test/dom/Image.test.tsx +0 -144
  266. package/test/dom/Link.test.tsx +0 -67
  267. package/test/dom/NavLink.test.tsx +0 -37
  268. package/test/dom/Script.test.tsx +0 -48
  269. package/test/dom/action.test.tsx +0 -146
  270. package/test/dom/error-overlay.test.tsx +0 -44
  271. package/test/dom/loader.test.tsx +0 -121
  272. package/test/dom/navigation.test.ts +0 -59
  273. package/test/dom/revalidate.test.tsx +0 -38
  274. package/test/dom/route-head.test.tsx +0 -56
  275. package/test/dom/router-loading.test.tsx +0 -44
  276. package/test/dom/scroll.test.ts +0 -56
  277. package/test/dom/slot.test.tsx +0 -131
  278. package/test/dom/use-metadata.test.tsx +0 -58
  279. package/test/dom/view-transitions.test.tsx +0 -53
  280. package/test/dynamic-routing.test.ts +0 -132
  281. package/test/email-console.test.ts +0 -86
  282. package/test/email-preview.test.ts +0 -73
  283. package/test/emails.test.ts +0 -58
  284. package/test/errors.test.ts +0 -21
  285. package/test/example-guestbook.test.ts +0 -123
  286. package/test/features.test.ts +0 -149
  287. package/test/fixtures/bignum-wire/spec.ts +0 -25
  288. package/test/fixtures/daemon-app.ts +0 -65
  289. package/test/fixtures/stream-echo.ts +0 -26
  290. package/test/fixtures/stream-gate.ts +0 -28
  291. package/test/fixtures/stream-trap.ts +0 -18
  292. package/test/fixtures/stream-typed.ts +0 -41
  293. package/test/fonts.test.ts +0 -28
  294. package/test/global-setup.ts +0 -17
  295. package/test/head.test.ts +0 -42
  296. package/test/image-blur.test.ts +0 -50
  297. package/test/io.test.ts +0 -117
  298. package/test/metadata.test.ts +0 -40
  299. package/test/navlink.test.ts +0 -28
  300. package/test/pages.test.ts +0 -105
  301. package/test/placeholder.test.ts +0 -9
  302. package/test/pqauth-e2e.test.ts +0 -216
  303. package/test/pqauth-email.test.ts +0 -801
  304. package/test/prerender.test.ts +0 -116
  305. package/test/prettier-plugin.test.ts +0 -46
  306. package/test/routes.test.ts +0 -76
  307. package/test/rpc-bignum-wire.test.ts +0 -164
  308. package/test/rpc-dispatch.test.ts +0 -132
  309. package/test/rpc-kinds.test.ts +0 -18
  310. package/test/rpc.test.ts +0 -66
  311. package/test/search.test.ts +0 -114
  312. package/test/seo.test.ts +0 -175
  313. package/test/slot-layouts.test.ts +0 -69
  314. package/test/sri.test.ts +0 -129
  315. package/test/ssg.test.ts +0 -36
  316. package/test/ssr-hydration.test.tsx +0 -122
  317. package/test/ssr-render.test.ts +0 -197
  318. package/test/ssr-template.test.tsx +0 -482
  319. package/test/stream-emulation.test.ts +0 -479
  320. package/test/ui.test.ts +0 -40
  321. package/test/update.test.ts +0 -58
  322. package/test/validate.test.ts +0 -42
  323. package/test/version-check.test.ts +0 -130
  324. package/tests/data-parity/generated-parity.ts +0 -99
  325. package/tests/data-parity/parity.ts +0 -80
  326. package/tests/data-parity/spec.ts +0 -46
@@ -1,314 +0,0 @@
1
- /**
2
- * Dev-only email preview tool. Backs the `/__toil/emails*` endpoints (wired in
3
- * `plugin.ts`): a standalone page that lists `emails/*.tsx`, renders the selected
4
- * one through the live Vite SSR server (so edits and imported `client/*` CSS show
5
- * up), and fills `{{token}}` holes from inputs the same way the edge does at send
6
- * time. Build-path parity comes from sharing `renderEmailFile` with the codegen
7
- * pass (`emails.ts`), so what you preview is what gets baked into `server/_emails.ts`.
8
- */
9
-
10
- import fs from 'node:fs';
11
- import path from 'node:path';
12
-
13
- import type { ViteDevServer } from 'vite';
14
-
15
- import type { ResolvedToilConfig } from './config.js';
16
- import { type RenderedEmail, renderEmailFile, toPascal } from './emails.js';
17
-
18
- /** One discoverable email: its generated `Emails.<name>` and its absolute file. */
19
- export interface EmailListItem {
20
- name: string;
21
- /** Absolute path, used for "open in editor" (`/__toil/open?file=`). */
22
- file: string;
23
- }
24
-
25
- /** The `emails/` dir for a project (sibling of `client/` and `server/`). */
26
- export function emailsDir(cfg: ResolvedToilConfig): string {
27
- return path.join(cfg.root, 'emails');
28
- }
29
-
30
- /**
31
- * A cheap change fingerprint (`<newestMtime>:<fileCount>`) over `emails/*.tsx|jsx`
32
- * and the project's client CSS, polled by the preview page to detect edits (any
33
- * save bumps an mtime to ~now; add/remove changes the count). Stat-only, so it is
34
- * fine to poll ~1/s. Used instead of a long-lived stream, which the buffering wasm
35
- * dev proxy can't forward.
36
- */
37
- export function emailsVersion(cfg: ResolvedToilConfig): string {
38
- let newest = 0;
39
- let count = 0;
40
- const CSS = /\.(css|scss|sass|less|styl|pcss|postcss)$/;
41
- const walk = (dir: string, match: RegExp): void => {
42
- let entries: fs.Dirent[];
43
- try {
44
- entries = fs.readdirSync(dir, { withFileTypes: true });
45
- } catch {
46
- return;
47
- }
48
- for (const e of entries) {
49
- const full = path.join(dir, e.name);
50
- if (e.isDirectory()) {
51
- if (e.name !== 'node_modules') walk(full, match);
52
- } else if (match.test(e.name)) {
53
- try {
54
- const m = fs.statSync(full).mtimeMs;
55
- if (m > newest) newest = m;
56
- count++;
57
- } catch {
58
- // file vanished between readdir and stat; ignore
59
- }
60
- }
61
- }
62
- };
63
- // Email templates and any styles beside them (emails/styles/*), plus client
64
- // CSS in case an email reuses `client/styles/*`.
65
- walk(emailsDir(cfg), /\.(tsx|jsx|css|scss|sass|less|styl|pcss|postcss)$/);
66
- walk(cfg.clientAbsDir, CSS);
67
- return `${String(newest)}:${String(count)}`;
68
- }
69
-
70
- /** List `emails/*.tsx|jsx`, mapped to their generated names. Cheap (no render). */
71
- export function listEmails(cfg: ResolvedToilConfig): EmailListItem[] {
72
- const dir = emailsDir(cfg);
73
- if (!fs.existsSync(dir)) return [];
74
- return fs
75
- .readdirSync(dir)
76
- .filter((f) => /\.(tsx|jsx)$/.test(f))
77
- .sort()
78
- .map((f) => ({
79
- name: toPascal(path.basename(f).replace(/\.(tsx|jsx)$/, '')),
80
- file: path.join(dir, f),
81
- }));
82
- }
83
-
84
- /**
85
- * Render the email whose generated name is `name` through the live SSR server,
86
- * or `null` if there is no such file. Drops the module from the SSR cache first
87
- * so an edit is reflected on every request (the watcher also invalidates on save;
88
- * this makes a manual refresh fresh too).
89
- */
90
- export async function renderEmailByName(
91
- server: ViteDevServer,
92
- cfg: ResolvedToilConfig,
93
- name: string,
94
- ): Promise<RenderedEmail | null> {
95
- const item = listEmails(cfg).find((e) => e.name === name);
96
- if (!item) return null;
97
- const node =
98
- server.moduleGraph.getModuleById(item.file) ??
99
- (await server.moduleGraph.getModuleByUrl(item.file));
100
- if (node) server.moduleGraph.invalidateModule(node);
101
- const { renderToStaticMarkup } = await import('react-dom/server');
102
- return renderEmailFile(
103
- server,
104
- emailsDir(cfg),
105
- path.basename(item.file),
106
- renderToStaticMarkup as (el: unknown) => string,
107
- );
108
- }
109
-
110
- /**
111
- * The self-contained preview page (served at `/__toil/emails`). Plain HTML + a
112
- * tiny inline script -- no client-runtime dependency, so it works in both the
113
- * client-only and wasm-server dev modes. Token substitution happens here in the
114
- * browser (`{{token}}` -> input value), so typing is instant and the iframe shows
115
- * exactly the edge's hole-fill path.
116
- */
117
- export function previewShellHtml(): string {
118
- return `<!doctype html>
119
- <html lang="en">
120
- <head>
121
- <meta charset="utf-8" />
122
- <meta name="viewport" content="width=device-width, initial-scale=1" />
123
- <title>Email preview, toiljs</title>
124
- <style>
125
- /* Matches the toiljs demo brand (examples/basic/client/styles/main.css). */
126
- :root {
127
- color-scheme: dark;
128
- --bg: #080d11; --surface: #0e1520; --surface2: #131d2e; --border: #1b2330;
129
- --text: #f5f6fa; --muted: #8b9ab4; --accent: #2563ff; --accent3: #22e3ab;
130
- }
131
- * { box-sizing: border-box; }
132
- body { margin: 0; height: 100vh; display: flex; font: 14px/1.5 system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; background: var(--bg); color: var(--text); }
133
- #side { width: 248px; flex: 0 0 auto; border-right: 1px solid var(--border); display: flex; flex-direction: column; background: var(--surface); }
134
- .brand { display: flex; align-items: center; gap: 10px; padding: 15px 16px; font-family: 'Montserrat', system-ui, sans-serif; font-weight: 800; font-size: 15px; letter-spacing: -0.01em; border-bottom: 1px solid var(--border); }
135
- .brand .mark { width: 26px; height: 26px; flex: 0 0 auto; border-radius: 7px; display: flex; align-items: center; justify-content: center; color: #fff; font-size: 13px; background: linear-gradient(135deg, var(--accent), #7c3aed 55%, var(--accent3)); }
136
- #list { list-style: none; margin: 0; padding: 8px; overflow: auto; flex: 1; }
137
- #list li { padding: 8px 11px; border-radius: 8px; cursor: pointer; color: var(--muted); transition: background 150ms, color 150ms; }
138
- #list li:hover { background: rgba(255,255,255,0.04); color: var(--text); }
139
- #list li.on { background: rgba(37,99,255,0.14); color: #fff; box-shadow: inset 2px 0 0 var(--accent); }
140
- #list li.muted, #list li.muted:hover { color: #5d6a82; cursor: default; background: none; }
141
- .hint { padding: 12px 16px; font-size: 12px; color: #5d6a82; border-top: 1px solid var(--border); }
142
- .hint code { color: var(--muted); }
143
- #main { flex: 1; display: flex; flex-direction: column; min-width: 0; }
144
- .empty { margin: auto; color: #5d6a82; }
145
- #view { display: flex; flex-direction: column; height: 100%; }
146
- .bar { display: flex; align-items: center; gap: 14px; padding: 14px 18px; border-bottom: 1px solid var(--border); }
147
- .subj { min-width: 0; flex: 1; display: flex; align-items: baseline; gap: 9px; }
148
- .subj .lbl { font-size: 10px; text-transform: uppercase; letter-spacing: .08em; color: #5d6a82; }
149
- #subject { font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
150
- .actions { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }
151
- .seg { display: flex; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
152
- .seg-btn { background: var(--surface); border: 0; color: var(--muted); font: inherit; padding: 6px 14px; cursor: pointer; transition: color 150ms; }
153
- .seg-btn:hover { color: var(--text); }
154
- .seg-btn.on { background: var(--accent); color: #fff; }
155
- .btn { background: var(--surface); border: 1px solid var(--border); color: var(--text); font: inherit; padding: 6px 13px; border-radius: 8px; cursor: pointer; transition: border-color 150ms, background 150ms; }
156
- .btn:hover { border-color: var(--accent); background: var(--surface2); }
157
- .body { display: flex; flex: 1; min-height: 0; }
158
- .tokens { width: 248px; flex: 0 0 auto; border-right: 1px solid var(--border); padding: 14px; overflow: auto; }
159
- .field { display: flex; flex-direction: column; gap: 5px; margin-bottom: 13px; }
160
- .fname { font-size: 12px; color: var(--muted); }
161
- .field input { background: var(--bg); border: 1px solid var(--border); border-radius: 7px; color: var(--text); font: inherit; padding: 7px 9px; }
162
- .field input:focus { outline: none; border-color: var(--accent); }
163
- .muted { color: #5d6a82; font-size: 12px; }
164
- .preview { flex: 1; min-width: 0; display: flex; background: var(--bg); padding: 18px; }
165
- #frame { flex: 1; width: 100%; border: 1px solid var(--border); border-radius: 12px; background: var(--bg); }
166
- #text { flex: 1; width: 100%; margin: 0; padding: 18px; overflow: auto; background: var(--surface); color: var(--muted); white-space: pre-wrap; border: 1px solid var(--border); border-radius: 12px; font: 13px/1.6 'SFMono-Regular', Consolas, monospace; }
167
- </style>
168
- </head>
169
- <body>
170
- <aside id="side">
171
- <div class="brand"><span class="mark">✦</span>Emails</div>
172
- <ul id="list"></ul>
173
- <div class="hint">Author in <code>emails/*.tsx</code>; this updates live on save.</div>
174
- </aside>
175
- <main id="main">
176
- <div id="empty" class="empty">Select an email to preview.</div>
177
- <section id="view" hidden>
178
- <header class="bar">
179
- <div class="subj"><span class="lbl">Subject</span><span id="subject"></span></div>
180
- <div class="actions">
181
- <div class="seg"><button id="tab-html" class="seg-btn on">HTML</button><button id="tab-text" class="seg-btn">Text</button></div>
182
- <button id="open" class="btn">Open in editor</button>
183
- </div>
184
- </header>
185
- <div class="body">
186
- <div class="tokens" id="tokens"></div>
187
- <div class="preview"><iframe id="frame" title="email preview"></iframe><pre id="text" hidden></pre></div>
188
- </div>
189
- </section>
190
- </main>
191
- <script>
192
- (function () {
193
- var BASE = '/__toil/emails';
194
- var listEl = document.getElementById('list');
195
- var subjectEl = document.getElementById('subject');
196
- var frame = document.getElementById('frame');
197
- var textEl = document.getElementById('text');
198
- var tokensEl = document.getElementById('tokens');
199
- var emptyEl = document.getElementById('empty');
200
- var viewEl = document.getElementById('view');
201
- var tabHtml = document.getElementById('tab-html');
202
- var tabText = document.getElementById('tab-text');
203
- var openBtn = document.getElementById('open');
204
- var current = null, rendered = null, format = 'html', values = {};
205
-
206
- function fill(s) {
207
- return String(s).replace(/\\{\\{\\s*([A-Za-z_$][\\w$]*)\\s*\\}\\}/g, function (m, k) {
208
- return Object.prototype.hasOwnProperty.call(values, k) ? values[k] : m;
209
- });
210
- }
211
- function paint() {
212
- if (!rendered) return;
213
- subjectEl.textContent = fill(rendered.subject);
214
- if (format === 'html') {
215
- frame.hidden = false; textEl.hidden = true;
216
- // Wrap the email FRAGMENT in a minimal dark document so the iframe doesn't
217
- // show the browser-default white body/margin around and below the email.
218
- frame.srcdoc = '<!doctype html><meta charset="utf-8"><style>html,body{margin:0;padding:0;background:#080d11}</style>' + fill(rendered.html);
219
- } else {
220
- frame.hidden = true; textEl.hidden = false;
221
- textEl.textContent = fill(rendered.text);
222
- }
223
- }
224
- function paintTokens() {
225
- tokensEl.textContent = '';
226
- if (!rendered.tokens.length) {
227
- var none = document.createElement('div');
228
- none.className = 'muted'; none.textContent = 'No {{tokens}} in this email.';
229
- tokensEl.appendChild(none); return;
230
- }
231
- rendered.tokens.forEach(function (t) {
232
- var row = document.createElement('label'); row.className = 'field';
233
- var span = document.createElement('span'); span.className = 'fname'; span.textContent = t;
234
- var inp = document.createElement('input');
235
- inp.value = values[t] != null ? values[t] : t;
236
- values[t] = inp.value;
237
- inp.addEventListener('input', function () { values[t] = inp.value; paint(); });
238
- row.appendChild(span); row.appendChild(inp); tokensEl.appendChild(row);
239
- });
240
- }
241
- function setFormat(f) {
242
- format = f;
243
- tabHtml.classList.toggle('on', f === 'html');
244
- tabText.classList.toggle('on', f === 'text');
245
- paint();
246
- }
247
- tabHtml.addEventListener('click', function () { setFormat('html'); });
248
- tabText.addEventListener('click', function () { setFormat('text'); });
249
- openBtn.addEventListener('click', function () {
250
- if (current) fetch('/__toil/open?file=' + encodeURIComponent(current.file)).catch(function () {});
251
- });
252
-
253
- function select(item, keep) {
254
- current = item;
255
- Array.prototype.forEach.call(listEl.children, function (li) {
256
- li.classList.toggle('on', li.getAttribute('data-name') === item.name);
257
- });
258
- fetch(BASE + '/render?name=' + encodeURIComponent(item.name)).then(function (r) {
259
- if (!r.ok) throw new Error('render failed');
260
- return r.json();
261
- }).then(function (data) {
262
- rendered = data;
263
- if (!keep) values = {};
264
- emptyEl.hidden = true; viewEl.hidden = false;
265
- paintTokens(); paint();
266
- }).catch(function () {
267
- emptyEl.hidden = false; viewEl.hidden = true;
268
- emptyEl.textContent = 'Could not render ' + item.name + ' (see dev server logs).';
269
- });
270
- }
271
- function buildList(items) {
272
- listEl.textContent = '';
273
- if (!items.length) {
274
- var li = document.createElement('li');
275
- li.className = 'muted'; li.textContent = 'No emails/*.tsx found.';
276
- listEl.appendChild(li); return;
277
- }
278
- items.forEach(function (it) {
279
- var li = document.createElement('li');
280
- li.setAttribute('data-name', it.name);
281
- li.textContent = it.name;
282
- li.classList.toggle('on', !!current && it.name === current.name);
283
- li.addEventListener('click', function () { select(it, false); });
284
- listEl.appendChild(li);
285
- });
286
- }
287
- function refresh() {
288
- fetch(BASE + '/list').then(function (r) { return r.json(); }).then(function (items) {
289
- buildList(items);
290
- if (!items.length) {
291
- current = null; rendered = null;
292
- emptyEl.hidden = false; viewEl.hidden = true;
293
- emptyEl.textContent = 'No emails/*.tsx found.';
294
- return;
295
- }
296
- var match = current && items.filter(function (it) { return it.name === current.name; })[0];
297
- select(match || items[0], !!match);
298
- }).catch(function () {});
299
- }
300
- refresh();
301
- // Live refresh: poll a cheap mtime fingerprint; re-render when it changes.
302
- var version = null;
303
- setInterval(function () {
304
- fetch(BASE + '/version').then(function (r) { return r.text(); }).then(function (v) {
305
- if (version === null) { version = v; return; }
306
- if (v !== version) { version = v; refresh(); }
307
- }).catch(function () {});
308
- }, 1000);
309
- })();
310
- </script>
311
- </body>
312
- </html>
313
- `;
314
- }