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.
- package/CHANGELOG.md +10 -0
- package/README.md +16 -7
- package/TYPESCRIPT_LAW.md +1946 -2073
- package/build/backend/.tsbuildinfo +1 -1
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/index.js +2375 -258
- package/build/client/.tsbuildinfo +1 -1
- package/build/client/dev/devtools.js +1 -1
- package/build/client/dev/error-overlay.js +1 -1
- package/build/client/routing/mount.js +1 -1
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/auth-emails.js +7 -3
- package/build/compiler/emails.js +1 -1
- package/build/compiler/index.d.ts +1 -0
- package/build/compiler/index.js +25 -4
- package/build/compiler/pages.js +2 -3
- package/build/compiler/prerender.d.ts +3 -8
- package/build/compiler/prerender.js +88 -127
- package/build/compiler/ssg.js +3 -4
- package/build/compiler/template-build.js +3 -4
- package/build/compiler/toil-docs.generated.js +6 -5
- package/build/compiler/vite.js +3 -6
- package/build/devserver/.tsbuildinfo +1 -1
- package/build/devserver/db/database.js +7 -3
- package/build/devserver/db/types.js +1 -1
- package/build/devserver/production.js +4 -3
- package/build/devserver/runtime/crypto.d.ts +1 -1
- package/build/io/.tsbuildinfo +1 -1
- package/build/logger/.tsbuildinfo +1 -1
- package/build/shared/.tsbuildinfo +1 -1
- package/examples/basic/client/routes/analytics.tsx +7 -5
- package/examples/basic/server/tsconfig.json +1 -3
- package/package.json +45 -44
- package/presets/no-uint8array-tostring.js +120 -153
- package/presets/oxlint.d.ts +5 -0
- package/presets/oxlint.js +224 -0
- package/server/runtime/tsconfig.json +1 -3
- package/tsconfig.json +2 -11
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -38
- package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -90
- package/.github/ISSUE_TEMPLATE/config.yml +0 -8
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -43
- package/.github/changelog-config.json +0 -45
- package/.github/dependabot.yml +0 -27
- package/.github/workflows/ci.yml +0 -222
- package/.vscode/settings.json +0 -10
- package/docs/README.md +0 -107
- package/docs/auth/README.md +0 -104
- package/docs/auth/configuration.md +0 -105
- package/docs/auth/emails.md +0 -92
- package/docs/auth/extending.md +0 -202
- package/docs/auth/how-it-works.md +0 -138
- package/docs/auth/usage.md +0 -188
- package/docs/backend/README.md +0 -143
- package/docs/backend/data.md +0 -351
- package/docs/backend/rest.md +0 -402
- package/docs/backend/rpc.md +0 -226
- package/docs/background/README.md +0 -114
- package/docs/background/daemons.md +0 -276
- package/docs/background/derive.md +0 -179
- package/docs/cli/README.md +0 -379
- package/docs/concepts/ai-guide.md +0 -132
- package/docs/concepts/config.md +0 -416
- package/docs/concepts/decorators.md +0 -127
- package/docs/concepts/security.md +0 -108
- package/docs/concepts/tiers.md +0 -166
- package/docs/concepts/types.md +0 -216
- package/docs/database/README.md +0 -143
- package/docs/database/capacity.md +0 -350
- package/docs/database/counters.md +0 -174
- package/docs/database/documents.md +0 -277
- package/docs/database/events.md +0 -307
- package/docs/database/membership.md +0 -246
- package/docs/database/setup.md +0 -155
- package/docs/database/unique.md +0 -216
- package/docs/database/views.md +0 -246
- package/docs/frontend/README.md +0 -104
- package/docs/frontend/components.md +0 -286
- package/docs/frontend/data-fetching.md +0 -260
- package/docs/frontend/images.md +0 -148
- package/docs/frontend/metadata.md +0 -344
- package/docs/frontend/navigation.md +0 -296
- package/docs/frontend/rendering.md +0 -236
- package/docs/frontend/routing.md +0 -346
- package/docs/frontend/scripts.md +0 -118
- package/docs/frontend/search.md +0 -191
- package/docs/frontend/styling.md +0 -147
- package/docs/frontend/toil-global.md +0 -216
- package/docs/getting-started/README.md +0 -81
- package/docs/getting-started/create-project.md +0 -131
- package/docs/getting-started/deploy.md +0 -101
- package/docs/getting-started/first-app.md +0 -215
- package/docs/getting-started/installation.md +0 -124
- package/docs/getting-started/migrating.md +0 -125
- package/docs/getting-started/project-structure.md +0 -163
- package/docs/introduction/README.md +0 -66
- package/docs/introduction/design-principles.md +0 -81
- package/docs/introduction/distributed.md +0 -91
- package/docs/introduction/how-it-works.md +0 -121
- package/docs/introduction/hyperscale.md +0 -70
- package/docs/introduction/modern-stack.md +0 -85
- package/docs/introduction/vs-other-frameworks.md +0 -64
- package/docs/introduction/why-toil.md +0 -99
- package/docs/llms.txt +0 -95
- package/docs/realtime/README.md +0 -102
- package/docs/realtime/channels.md +0 -211
- package/docs/realtime/streams.md +0 -369
- package/docs/services/README.md +0 -60
- package/docs/services/analytics.md +0 -279
- package/docs/services/caching.md +0 -175
- package/docs/services/cookies.md +0 -235
- package/docs/services/crypto.md +0 -209
- package/docs/services/email.md +0 -289
- package/docs/services/environment.md +0 -141
- package/docs/services/ratelimit.md +0 -174
- package/docs/services/time.md +0 -85
- package/eslint.config.js +0 -57
- package/examples/basic/client/routes/docs/[...slug].tsx +0 -12
- package/presets/eslint.js +0 -88
- package/src/backend/index.ts +0 -204
- package/src/cli/configure.ts +0 -363
- package/src/cli/create.ts +0 -924
- package/src/cli/db.ts +0 -158
- package/src/cli/diagnostics.ts +0 -665
- package/src/cli/doctor.ts +0 -982
- package/src/cli/features.ts +0 -170
- package/src/cli/index.ts +0 -326
- package/src/cli/notify.ts +0 -120
- package/src/cli/proc.ts +0 -56
- package/src/cli/ui.ts +0 -169
- package/src/cli/update.ts +0 -248
- package/src/cli/updates.ts +0 -92
- package/src/cli/validate.ts +0 -31
- package/src/cli/version-check.ts +0 -125
- package/src/client/auth.ts +0 -869
- package/src/client/channel/channel.ts +0 -146
- package/src/client/components/Form.tsx +0 -65
- package/src/client/components/Image.tsx +0 -160
- package/src/client/components/Script.tsx +0 -113
- package/src/client/components/Slot.tsx +0 -21
- package/src/client/dev/devtools.tsx +0 -1089
- package/src/client/dev/error-overlay.tsx +0 -225
- package/src/client/errors.ts +0 -11
- package/src/client/head/head.ts +0 -182
- package/src/client/head/metadata.ts +0 -110
- package/src/client/index.ts +0 -130
- package/src/client/navigation/Link.tsx +0 -108
- package/src/client/navigation/NavLink.tsx +0 -86
- package/src/client/navigation/navigation.ts +0 -235
- package/src/client/navigation/prefetch.ts +0 -169
- package/src/client/navigation/scroll.ts +0 -53
- package/src/client/routing/Router.tsx +0 -216
- package/src/client/routing/action.ts +0 -122
- package/src/client/routing/error-boundary.tsx +0 -43
- package/src/client/routing/hooks.ts +0 -140
- package/src/client/routing/lazy.ts +0 -77
- package/src/client/routing/loader.ts +0 -381
- package/src/client/routing/match.ts +0 -47
- package/src/client/routing/mount.tsx +0 -90
- package/src/client/routing/params-context.ts +0 -10
- package/src/client/routing/slot-context.ts +0 -7
- package/src/client/rpc.ts +0 -97
- package/src/client/search/search.ts +0 -189
- package/src/client/search/use-page-search.ts +0 -73
- package/src/client/ssr/markers.tsx +0 -172
- package/src/client/stream/client.ts +0 -237
- package/src/client/types.ts +0 -73
- package/src/compiler/auth-emails.ts +0 -312
- package/src/compiler/config.ts +0 -369
- package/src/compiler/docs.ts +0 -127
- package/src/compiler/email-preview.ts +0 -314
- package/src/compiler/emails.ts +0 -404
- package/src/compiler/fonts.ts +0 -89
- package/src/compiler/generate.ts +0 -471
- package/src/compiler/image-blur.ts +0 -42
- package/src/compiler/image-report.ts +0 -88
- package/src/compiler/index.ts +0 -1362
- package/src/compiler/pages.ts +0 -49
- package/src/compiler/plugin.ts +0 -391
- package/src/compiler/prerender.ts +0 -263
- package/src/compiler/routes.ts +0 -170
- package/src/compiler/seo.ts +0 -415
- package/src/compiler/sri.ts +0 -144
- package/src/compiler/ssg.ts +0 -177
- package/src/compiler/ssr-codegen.ts +0 -85
- package/src/compiler/template-build.ts +0 -676
- package/src/compiler/template.ts +0 -262
- package/src/compiler/toil-docs.generated.ts +0 -77
- package/src/compiler/vite.ts +0 -245
- package/src/devserver/analytics/index.ts +0 -317
- package/src/devserver/config/dotenv.ts +0 -102
- package/src/devserver/config/env.ts +0 -30
- package/src/devserver/config/ratelimit.ts +0 -123
- package/src/devserver/daemon/catalog.ts +0 -120
- package/src/devserver/daemon/cron.ts +0 -87
- package/src/devserver/daemon/host.ts +0 -134
- package/src/devserver/daemon/index.ts +0 -369
- package/src/devserver/daemon/runtime.ts +0 -48
- package/src/devserver/db/catalog.ts +0 -109
- package/src/devserver/db/database.ts +0 -1834
- package/src/devserver/db/derives.ts +0 -121
- package/src/devserver/db/index.ts +0 -20
- package/src/devserver/db/routeKinds.ts +0 -191
- package/src/devserver/db/types.ts +0 -168
- package/src/devserver/email/caps.ts +0 -0
- package/src/devserver/email/config.ts +0 -141
- package/src/devserver/email/console.ts +0 -348
- package/src/devserver/email/index.ts +0 -155
- package/src/devserver/email/providers.ts +0 -131
- package/src/devserver/email/status.ts +0 -23
- package/src/devserver/email/validate.ts +0 -37
- package/src/devserver/email/wire.ts +0 -55
- package/src/devserver/http/cache.ts +0 -0
- package/src/devserver/http/envelope.ts +0 -157
- package/src/devserver/http/proxy.ts +0 -172
- package/src/devserver/http/runtime.ts +0 -387
- package/src/devserver/index.ts +0 -43
- package/src/devserver/production-ipc.ts +0 -63
- package/src/devserver/production-worker.ts +0 -83
- package/src/devserver/production.ts +0 -718
- package/src/devserver/runtime/crypto.ts +0 -587
- package/src/devserver/runtime/host.ts +0 -427
- package/src/devserver/runtime/module.ts +0 -442
- package/src/devserver/server.ts +0 -251
- package/src/devserver/ssr.ts +0 -208
- package/src/devserver/stream/catalog.ts +0 -106
- package/src/devserver/stream/host.ts +0 -42
- package/src/devserver/stream/index.ts +0 -301
- package/src/devserver/stream/manager.ts +0 -167
- package/src/devserver/stream/router.ts +0 -101
- package/src/devserver/stream/wire.ts +0 -58
- package/src/devserver/stream/ws.ts +0 -78
- package/src/devserver/wasm/sections.ts +0 -59
- package/src/devserver/wasm/surface.ts +0 -115
- package/src/io/FastMap.ts +0 -151
- package/src/io/FastSet.ts +0 -113
- package/src/io/codec.ts +0 -362
- package/src/io/index.ts +0 -10
- package/src/io/lengths.ts +0 -14
- package/src/io/types.ts +0 -19
- package/src/logger/index.ts +0 -22
- package/src/shared/index.ts +0 -46
- package/test/analytics-dev.test.ts +0 -113
- package/test/assembly/aspect-shim.ts +0 -33
- package/test/assembly/cookie.spec.ts +0 -303
- package/test/assembly/example.spec.ts +0 -22
- package/test/assembly/ssr.spec.ts +0 -95
- package/test/assembly.test.ts +0 -59
- package/test/auth-emails.test.ts +0 -118
- package/test/built-ssr.test.ts +0 -148
- package/test/channel.test.ts +0 -21
- package/test/config-host-port.test.ts +0 -59
- package/test/configure.test.ts +0 -94
- package/test/daemon-build.test.ts +0 -278
- package/test/daemon-catalog.test.ts +0 -349
- package/test/daemon-emulation.test.ts +0 -256
- package/test/db.test.ts +0 -0
- package/test/devserver-database.test.ts +0 -1102
- package/test/devserver-email.test.ts +0 -306
- package/test/devserver-pqauth.test.ts +0 -93
- package/test/devserver-secrets.test.ts +0 -63
- package/test/devserver.test.ts +0 -259
- package/test/doctor.test.ts +0 -298
- package/test/dom/Image.test.tsx +0 -144
- package/test/dom/Link.test.tsx +0 -67
- package/test/dom/NavLink.test.tsx +0 -37
- package/test/dom/Script.test.tsx +0 -48
- package/test/dom/action.test.tsx +0 -146
- package/test/dom/error-overlay.test.tsx +0 -44
- package/test/dom/loader.test.tsx +0 -121
- package/test/dom/navigation.test.ts +0 -59
- package/test/dom/revalidate.test.tsx +0 -38
- package/test/dom/route-head.test.tsx +0 -56
- package/test/dom/router-loading.test.tsx +0 -44
- package/test/dom/scroll.test.ts +0 -56
- package/test/dom/slot.test.tsx +0 -131
- package/test/dom/use-metadata.test.tsx +0 -58
- package/test/dom/view-transitions.test.tsx +0 -53
- package/test/dynamic-routing.test.ts +0 -132
- package/test/email-console.test.ts +0 -86
- package/test/email-preview.test.ts +0 -73
- package/test/emails.test.ts +0 -58
- package/test/errors.test.ts +0 -21
- package/test/example-guestbook.test.ts +0 -123
- package/test/features.test.ts +0 -149
- package/test/fixtures/bignum-wire/spec.ts +0 -25
- package/test/fixtures/daemon-app.ts +0 -65
- package/test/fixtures/stream-echo.ts +0 -26
- package/test/fixtures/stream-gate.ts +0 -28
- package/test/fixtures/stream-trap.ts +0 -18
- package/test/fixtures/stream-typed.ts +0 -41
- package/test/fonts.test.ts +0 -28
- package/test/global-setup.ts +0 -17
- package/test/head.test.ts +0 -42
- package/test/image-blur.test.ts +0 -50
- package/test/io.test.ts +0 -117
- package/test/metadata.test.ts +0 -40
- package/test/navlink.test.ts +0 -28
- package/test/pages.test.ts +0 -105
- package/test/placeholder.test.ts +0 -9
- package/test/pqauth-e2e.test.ts +0 -216
- package/test/pqauth-email.test.ts +0 -801
- package/test/prerender.test.ts +0 -116
- package/test/prettier-plugin.test.ts +0 -46
- package/test/routes.test.ts +0 -76
- package/test/rpc-bignum-wire.test.ts +0 -164
- package/test/rpc-dispatch.test.ts +0 -132
- package/test/rpc-kinds.test.ts +0 -18
- package/test/rpc.test.ts +0 -66
- package/test/search.test.ts +0 -114
- package/test/seo.test.ts +0 -175
- package/test/slot-layouts.test.ts +0 -69
- package/test/sri.test.ts +0 -129
- package/test/ssg.test.ts +0 -36
- package/test/ssr-hydration.test.tsx +0 -122
- package/test/ssr-render.test.ts +0 -197
- package/test/ssr-template.test.tsx +0 -482
- package/test/stream-emulation.test.ts +0 -479
- package/test/ui.test.ts +0 -40
- package/test/update.test.ts +0 -58
- package/test/validate.test.ts +0 -42
- package/test/version-check.test.ts +0 -130
- package/tests/data-parity/generated-parity.ts +0 -99
- package/tests/data-parity/parity.ts +0 -80
- 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
|
-
}
|