weifuwu 0.19.1 → 0.19.4
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/cli.ts +1 -1
- package/dist/cli.js +1 -1
- package/dist/compile.d.ts +2 -0
- package/dist/index.js +7 -4
- package/package.json +1 -1
- package/cli/template/.weifuwu/ssr/96f5704e.js +0 -481
- package/cli/template/.weifuwu/ssr/fae6ecbe.js +0 -14
package/cli.ts
CHANGED
package/dist/cli.js
CHANGED
package/dist/compile.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export declare function clearCompileCache(): void;
|
|
|
3
3
|
export declare function compileTsx(path: string): Promise<any>;
|
|
4
4
|
/** Dev hot-reload: CJS + in-memory + vm (faster than ESM + disk + import) */
|
|
5
5
|
export declare function compileTsxDev(path: string): Promise<any>;
|
|
6
|
+
/** Auto-select dev (vm) or prod (ESM + import) compilation */
|
|
7
|
+
export declare function compile(path: string): Promise<any>;
|
|
6
8
|
/** Build a single vendor bundle containing all needed vendor modules */
|
|
7
9
|
export declare function compileVendorBundle(): Promise<string>;
|
|
8
10
|
/** Hot-reload: ESM bundle, calls __WFW_REFRESH on import */
|
package/dist/index.js
CHANGED
|
@@ -5248,6 +5248,9 @@ async function compileTsxDev(path2) {
|
|
|
5248
5248
|
cache.set(absPath, mod);
|
|
5249
5249
|
return mod;
|
|
5250
5250
|
}
|
|
5251
|
+
function compile(path2) {
|
|
5252
|
+
return process.env.NODE_ENV !== "production" ? compileTsxDev(path2) : compileTsx(path2);
|
|
5253
|
+
}
|
|
5251
5254
|
var vendorBundle = null;
|
|
5252
5255
|
async function compileVendorBundle() {
|
|
5253
5256
|
if (vendorBundle) return vendorBundle;
|
|
@@ -5524,7 +5527,7 @@ function ssr(path2) {
|
|
|
5524
5527
|
}) : new Response("", { status: 404 });
|
|
5525
5528
|
});
|
|
5526
5529
|
r.get("/", async (req, ctx) => {
|
|
5527
|
-
const pageMod = await
|
|
5530
|
+
const pageMod = await compile(path2);
|
|
5528
5531
|
const Component = pageMod.default;
|
|
5529
5532
|
if (!Component) return new Response("", { status: 500 });
|
|
5530
5533
|
const layouts = ctx.layoutStack || [];
|
|
@@ -5651,7 +5654,7 @@ ${src}`;
|
|
|
5651
5654
|
// layout.ts
|
|
5652
5655
|
function layout(path2) {
|
|
5653
5656
|
return async (req, ctx, next) => {
|
|
5654
|
-
const mod = await
|
|
5657
|
+
const mod = await compile(path2);
|
|
5655
5658
|
const Component = mod.default;
|
|
5656
5659
|
if (!Component) throw new Error(`Layout ${path2} has no default export`);
|
|
5657
5660
|
ctx.layoutStack = [...ctx.layoutStack || [], { path: path2, component: Component }];
|
|
@@ -8420,7 +8423,7 @@ function liveReload(opts) {
|
|
|
8420
8423
|
function notFound(path2) {
|
|
8421
8424
|
return async (req, ctx) => {
|
|
8422
8425
|
if (!path2) return new Response("Not Found", { status: 404 });
|
|
8423
|
-
const mod = await
|
|
8426
|
+
const mod = await compile(path2);
|
|
8424
8427
|
const Component = mod?.default;
|
|
8425
8428
|
const body = Component ? "404 - Not Found" : "404 - Not Found";
|
|
8426
8429
|
return new Response(body, {
|
|
@@ -8438,7 +8441,7 @@ function errorBoundary(errorPath) {
|
|
|
8438
8441
|
try {
|
|
8439
8442
|
return await next(req, ctx);
|
|
8440
8443
|
} catch (err) {
|
|
8441
|
-
const mod = await
|
|
8444
|
+
const mod = await compile(errorPath);
|
|
8442
8445
|
const ErrorComponent = mod.default;
|
|
8443
8446
|
if (!ErrorComponent) throw err;
|
|
8444
8447
|
const layouts = (ctx.layoutStack || []).map((l) => l.component);
|
package/package.json
CHANGED
|
@@ -1,481 +0,0 @@
|
|
|
1
|
-
// ui/page.tsx
|
|
2
|
-
import { useState as useState6 } from "react";
|
|
3
|
-
|
|
4
|
-
// ../../use-websocket.ts
|
|
5
|
-
import { useEffect, useRef, useCallback, useState } from "react";
|
|
6
|
-
var RECONNECT_DELAY = 3e3;
|
|
7
|
-
var MAX_RETRIES = 10;
|
|
8
|
-
function resolveUrl(url) {
|
|
9
|
-
return typeof url === "function" ? url() : url;
|
|
10
|
-
}
|
|
11
|
-
function useWebsocket(url, options) {
|
|
12
|
-
const { onMessage, reconnect: reconnectOpt = true, protocols, enabled = true } = options ?? {};
|
|
13
|
-
const [lastMessage, setLastMessage] = useState(null);
|
|
14
|
-
const [readyState, setReadyState] = useState(WebSocket.CLOSED);
|
|
15
|
-
const wsRef = useRef(null);
|
|
16
|
-
const retryRef = useRef(0);
|
|
17
|
-
const timerRef = useRef(void 0);
|
|
18
|
-
const mountedRef = useRef(true);
|
|
19
|
-
const shouldReconnectRef = useRef(true);
|
|
20
|
-
const urlRef = useRef(url);
|
|
21
|
-
const optsRef = useRef({ onMessage, reconnectOpt, protocols });
|
|
22
|
-
urlRef.current = url;
|
|
23
|
-
optsRef.current = { onMessage, reconnectOpt, protocols };
|
|
24
|
-
const cleanup = useCallback(() => {
|
|
25
|
-
clearTimeout(timerRef.current);
|
|
26
|
-
wsRef.current?.close();
|
|
27
|
-
wsRef.current = null;
|
|
28
|
-
}, []);
|
|
29
|
-
const connect = useCallback(() => {
|
|
30
|
-
if (!mountedRef.current || !enabled) return;
|
|
31
|
-
const resolved = resolveUrl(urlRef.current);
|
|
32
|
-
if (!resolved) return;
|
|
33
|
-
wsRef.current?.close();
|
|
34
|
-
const ws = new WebSocket(resolved, optsRef.current.protocols);
|
|
35
|
-
wsRef.current = ws;
|
|
36
|
-
setReadyState(WebSocket.CONNECTING);
|
|
37
|
-
ws.addEventListener("open", () => {
|
|
38
|
-
if (!mountedRef.current) return;
|
|
39
|
-
retryRef.current = 0;
|
|
40
|
-
setReadyState(WebSocket.OPEN);
|
|
41
|
-
});
|
|
42
|
-
ws.addEventListener("message", (e) => {
|
|
43
|
-
if (!mountedRef.current) return;
|
|
44
|
-
const data = typeof e.data === "string" ? e.data : String(e.data);
|
|
45
|
-
setLastMessage(data);
|
|
46
|
-
optsRef.current.onMessage?.(data);
|
|
47
|
-
});
|
|
48
|
-
ws.addEventListener("close", () => {
|
|
49
|
-
if (!mountedRef.current) return;
|
|
50
|
-
setReadyState(WebSocket.CLOSED);
|
|
51
|
-
const ro = optsRef.current.reconnectOpt;
|
|
52
|
-
if (ro && shouldReconnectRef.current && mountedRef.current) {
|
|
53
|
-
const maxRetries = typeof ro === "object" ? ro.maxRetries ?? MAX_RETRIES : MAX_RETRIES;
|
|
54
|
-
const delay = typeof ro === "object" ? ro.delay ?? RECONNECT_DELAY : RECONNECT_DELAY;
|
|
55
|
-
if (retryRef.current < maxRetries) {
|
|
56
|
-
retryRef.current++;
|
|
57
|
-
timerRef.current = setTimeout(() => connect(), delay);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
}, [enabled]);
|
|
62
|
-
useEffect(() => {
|
|
63
|
-
mountedRef.current = true;
|
|
64
|
-
shouldReconnectRef.current = true;
|
|
65
|
-
if (enabled) connect();
|
|
66
|
-
return () => {
|
|
67
|
-
mountedRef.current = false;
|
|
68
|
-
cleanup();
|
|
69
|
-
};
|
|
70
|
-
}, [enabled, connect, cleanup]);
|
|
71
|
-
const send = useCallback((data) => {
|
|
72
|
-
wsRef.current?.send(data);
|
|
73
|
-
}, []);
|
|
74
|
-
const close = useCallback(() => {
|
|
75
|
-
shouldReconnectRef.current = false;
|
|
76
|
-
cleanup();
|
|
77
|
-
setReadyState(WebSocket.CLOSED);
|
|
78
|
-
}, [cleanup]);
|
|
79
|
-
const reconnectFn = useCallback(() => {
|
|
80
|
-
retryRef.current = 0;
|
|
81
|
-
shouldReconnectRef.current = true;
|
|
82
|
-
cleanup();
|
|
83
|
-
connect();
|
|
84
|
-
}, [cleanup, connect]);
|
|
85
|
-
return { send, close, readyState, lastMessage, reconnect: reconnectFn };
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// ../../use-action.ts
|
|
89
|
-
import { useState as useState2, useCallback as useCallback2, useRef as useRef2 } from "react";
|
|
90
|
-
|
|
91
|
-
// ../../client-router.ts
|
|
92
|
-
import { createElement, useCallback as useCallback3, useState as useState3, useEffect as useEffect2 } from "react";
|
|
93
|
-
|
|
94
|
-
// ../../client-pref.ts
|
|
95
|
-
var interceptors = [];
|
|
96
|
-
function addInterceptor(fn) {
|
|
97
|
-
interceptors.push(fn);
|
|
98
|
-
}
|
|
99
|
-
async function runInterceptors(url) {
|
|
100
|
-
for (const fn of interceptors) {
|
|
101
|
-
if (await fn(url)) return true;
|
|
102
|
-
}
|
|
103
|
-
return false;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// ../../client-router.ts
|
|
107
|
-
var _navigating = false;
|
|
108
|
-
var _listeners = [];
|
|
109
|
-
function setNavigating(v) {
|
|
110
|
-
_navigating = v;
|
|
111
|
-
for (const fn of _listeners) fn(v);
|
|
112
|
-
}
|
|
113
|
-
async function navigate(href) {
|
|
114
|
-
if (typeof document === "undefined") return;
|
|
115
|
-
const url = new URL(href, location.origin);
|
|
116
|
-
if (url.origin !== location.origin) {
|
|
117
|
-
location.href = href;
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
if (await runInterceptors(url)) return;
|
|
121
|
-
const scrollPos = [window.scrollX, window.scrollY];
|
|
122
|
-
setNavigating(true);
|
|
123
|
-
try {
|
|
124
|
-
const html = await fetch(url.pathname + url.search, {
|
|
125
|
-
headers: { accept: "text/html" }
|
|
126
|
-
}).then((r) => r.text());
|
|
127
|
-
const doc = new DOMParser().parseFromString(html, "text/html");
|
|
128
|
-
const rootEl = doc.getElementById("__weifuwu_root");
|
|
129
|
-
if (!rootEl) {
|
|
130
|
-
location.href = href;
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
const newHtml = rootEl.innerHTML;
|
|
134
|
-
const propsMatch = html.match(/window\.__WEIFUWU_PROPS=(.+?)<\/script>/);
|
|
135
|
-
if (!propsMatch) {
|
|
136
|
-
location.href = href;
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
const bundleMatch = html.match(/src="(\/__wfw\/client\/[^"]+\.js)"/);
|
|
140
|
-
const bundleUrl = bundleMatch ? bundleMatch[1] : null;
|
|
141
|
-
applyHead(html);
|
|
142
|
-
const currentRoot = document.getElementById("__weifuwu_root");
|
|
143
|
-
if (!currentRoot) {
|
|
144
|
-
location.href = href;
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
;
|
|
148
|
-
window.__WEIFUWU_PROPS = JSON.parse(propsMatch[1]);
|
|
149
|
-
history.pushState(null, "", url.pathname + url.search);
|
|
150
|
-
currentRoot.innerHTML = newHtml;
|
|
151
|
-
const ctxMatch = html.match(/window\.__WEIFUWU_CTX=(.+?)<\/script>/);
|
|
152
|
-
if (ctxMatch) {
|
|
153
|
-
try {
|
|
154
|
-
window.__WEIFUWU_CTX = JSON.parse(ctxMatch[1]);
|
|
155
|
-
} catch {
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
const localeMatch = html.match(/window\.__LOCALE_DATA__=(.+?)<\/script>/);
|
|
159
|
-
if (localeMatch) {
|
|
160
|
-
try {
|
|
161
|
-
window.__LOCALE_DATA__ = JSON.parse(localeMatch[1]);
|
|
162
|
-
} catch {
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
if (bundleUrl) {
|
|
166
|
-
try {
|
|
167
|
-
await import(
|
|
168
|
-
/* @vite-ignore */
|
|
169
|
-
`${bundleUrl}`
|
|
170
|
-
);
|
|
171
|
-
} catch (e) {
|
|
172
|
-
console.error("[weifuwu/router] hydration failed:", e);
|
|
173
|
-
location.href = href;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
window.scrollTo(scrollPos[0], scrollPos[1]);
|
|
177
|
-
} finally {
|
|
178
|
-
setNavigating(false);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
function applyHead(html) {
|
|
182
|
-
const match = html.match(/<template id="__wfw_head">([\s\S]*?)<\/template>/);
|
|
183
|
-
if (!match) return;
|
|
184
|
-
const headHtml = match[1];
|
|
185
|
-
const titleMatch = headHtml.match(/<title>([^<]*)<\/title>/);
|
|
186
|
-
if (titleMatch) document.title = titleMatch[1];
|
|
187
|
-
const doc = new DOMParser().parseFromString(headHtml, "text/html");
|
|
188
|
-
const newMeta = doc.querySelectorAll("meta");
|
|
189
|
-
const existing = document.querySelectorAll("head meta");
|
|
190
|
-
const newNames = new Set(Array.from(newMeta).map((m) => m.getAttribute("name") || m.getAttribute("property") || ""));
|
|
191
|
-
for (const el of existing) {
|
|
192
|
-
const key = el.getAttribute("name") || el.getAttribute("property") || "";
|
|
193
|
-
if (!newNames.has(key)) el.remove();
|
|
194
|
-
}
|
|
195
|
-
for (const el of newMeta) {
|
|
196
|
-
const key = el.getAttribute("name") || el.getAttribute("property") || "";
|
|
197
|
-
let existingEl = null;
|
|
198
|
-
if (key) {
|
|
199
|
-
for (const m of document.head.querySelectorAll("meta")) {
|
|
200
|
-
if (m.getAttribute("name") === key || m.getAttribute("property") === key) {
|
|
201
|
-
existingEl = m;
|
|
202
|
-
break;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
if (existingEl) {
|
|
207
|
-
for (const attr of el.attributes) existingEl.setAttribute(attr.name, attr.value);
|
|
208
|
-
} else {
|
|
209
|
-
document.head.appendChild(el.cloneNode());
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
const newLink = doc.querySelector('link[rel="canonical"]');
|
|
213
|
-
const existingLink = document.querySelector('link[rel="canonical"]');
|
|
214
|
-
if (newLink) {
|
|
215
|
-
if (existingLink) existingLink.setAttribute("href", newLink.getAttribute("href") || "");
|
|
216
|
-
else document.head.appendChild(newLink.cloneNode());
|
|
217
|
-
} else if (existingLink) {
|
|
218
|
-
existingLink.remove();
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// ../../tsx-context.ts
|
|
223
|
-
import { useSyncExternalStore, createContext } from "react";
|
|
224
|
-
var DEFAULT_CTX = { params: {}, query: {}, parsed: {}, prefs: {}, loaderData: {}, env: {}, user: {} };
|
|
225
|
-
var KEY = "__WEIFUWU_CTX_STORE";
|
|
226
|
-
function getStore() {
|
|
227
|
-
if (typeof globalThis !== "undefined" && globalThis[KEY]) {
|
|
228
|
-
return globalThis[KEY];
|
|
229
|
-
}
|
|
230
|
-
const s = {
|
|
231
|
-
_ctx: DEFAULT_CTX,
|
|
232
|
-
_snapshot: { params: DEFAULT_CTX.params, query: DEFAULT_CTX.query, user: DEFAULT_CTX.user, parsed: DEFAULT_CTX.parsed, prefs: DEFAULT_CTX.prefs, env: DEFAULT_CTX.env },
|
|
233
|
-
_listeners: /* @__PURE__ */ new Set(),
|
|
234
|
-
_alsGetStore: null
|
|
235
|
-
};
|
|
236
|
-
if (typeof globalThis !== "undefined") {
|
|
237
|
-
globalThis[KEY] = s;
|
|
238
|
-
}
|
|
239
|
-
return s;
|
|
240
|
-
}
|
|
241
|
-
var store = getStore();
|
|
242
|
-
var subscribe = (cb) => {
|
|
243
|
-
store._listeners.add(cb);
|
|
244
|
-
return () => {
|
|
245
|
-
store._listeners.delete(cb);
|
|
246
|
-
};
|
|
247
|
-
};
|
|
248
|
-
var getSnapshot = () => store._snapshot;
|
|
249
|
-
function setCtx(value) {
|
|
250
|
-
store._ctx = { ...store._ctx, ...value };
|
|
251
|
-
store._snapshot = { params: store._ctx.params, query: store._ctx.query, user: store._ctx.user, parsed: store._ctx.parsed, prefs: store._ctx.prefs, env: store._ctx.env };
|
|
252
|
-
store._listeners.forEach((fn) => fn());
|
|
253
|
-
}
|
|
254
|
-
function useCtx() {
|
|
255
|
-
if (typeof window !== "undefined") {
|
|
256
|
-
const snapshot = useSyncExternalStore(subscribe, getSnapshot);
|
|
257
|
-
return { ...snapshot, ...window.__WEIFUWU_CTX };
|
|
258
|
-
}
|
|
259
|
-
const alsStore = store._alsGetStore?.();
|
|
260
|
-
return alsStore ?? store._ctx;
|
|
261
|
-
}
|
|
262
|
-
function useLoaderData() {
|
|
263
|
-
const ctx = useCtx();
|
|
264
|
-
return ctx.loaderData;
|
|
265
|
-
}
|
|
266
|
-
var TsxContext = createContext(DEFAULT_CTX);
|
|
267
|
-
|
|
268
|
-
// ../../head.tsx
|
|
269
|
-
import { createElement as createElement2 } from "react";
|
|
270
|
-
|
|
271
|
-
// ../../client-state.ts
|
|
272
|
-
import { useSyncExternalStore as useSyncExternalStore2, useCallback as useCallback4, useEffect as useEffect3, useRef as useRef3, useState as useState4 } from "react";
|
|
273
|
-
|
|
274
|
-
// ../../client-locale.ts
|
|
275
|
-
function buildT() {
|
|
276
|
-
const messages = globalThis.__LOCALE_DATA__ || (typeof window !== "undefined" ? window.__LOCALE_DATA__ : null);
|
|
277
|
-
if (!messages) return (key, _p, fb) => fb ?? key;
|
|
278
|
-
return (key, params, fallback) => {
|
|
279
|
-
const msg = key.split(".").reduce((o, k) => o?.[k], messages);
|
|
280
|
-
if (msg === void 0 || msg === null) return fallback ?? key;
|
|
281
|
-
if (!params) return String(msg);
|
|
282
|
-
let result = String(msg);
|
|
283
|
-
for (const [k, v] of Object.entries(params)) result = result.replace(`{${k}}`, v);
|
|
284
|
-
return result;
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
addInterceptor(async (url) => {
|
|
288
|
-
const m = url.pathname.match(/^\/__lang\/([\w-]+)$/);
|
|
289
|
-
if (!m) return false;
|
|
290
|
-
try {
|
|
291
|
-
const res = await fetch(url.pathname, {
|
|
292
|
-
headers: { accept: "application/json" }
|
|
293
|
-
});
|
|
294
|
-
const data = await res.json();
|
|
295
|
-
const ctx = { ...window.__WEIFUWU_CTX || {}, params: {}, query: {} };
|
|
296
|
-
ctx.prefs = { ...ctx.prefs, locale: data.locale };
|
|
297
|
-
if (data.messages) window.__LOCALE_DATA__ = data.messages;
|
|
298
|
-
window.__WEIFUWU_CTX = ctx;
|
|
299
|
-
setCtx(ctx);
|
|
300
|
-
} catch {
|
|
301
|
-
location.href = url.href;
|
|
302
|
-
}
|
|
303
|
-
return true;
|
|
304
|
-
});
|
|
305
|
-
function useLocale() {
|
|
306
|
-
const ctx = useCtx();
|
|
307
|
-
return {
|
|
308
|
-
locale: ctx.prefs.locale,
|
|
309
|
-
setLocale: (locale) => navigate("/__lang/" + locale),
|
|
310
|
-
t: buildT()
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// ../../client-theme.ts
|
|
315
|
-
import { useEffect as useEffect4 } from "react";
|
|
316
|
-
function resolveTheme(theme) {
|
|
317
|
-
if (theme === "system") {
|
|
318
|
-
if (typeof window === "undefined") return "light";
|
|
319
|
-
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
320
|
-
}
|
|
321
|
-
return theme;
|
|
322
|
-
}
|
|
323
|
-
var _mqListener = null;
|
|
324
|
-
function applyTheme(theme) {
|
|
325
|
-
if (typeof document === "undefined") return;
|
|
326
|
-
const resolved = resolveTheme(theme);
|
|
327
|
-
document.documentElement.dataset.theme = resolved;
|
|
328
|
-
if (theme === "system") {
|
|
329
|
-
if (!_mqListener) {
|
|
330
|
-
const mq = window.matchMedia("(prefers-color-scheme: dark)");
|
|
331
|
-
mq.addEventListener("change", (e) => {
|
|
332
|
-
if (window.__WEIFUWU_CTX?.prefs?.theme === "system") {
|
|
333
|
-
document.documentElement.dataset.theme = e.matches ? "dark" : "light";
|
|
334
|
-
}
|
|
335
|
-
});
|
|
336
|
-
_mqListener = mq;
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
addInterceptor(async (url) => {
|
|
341
|
-
const m = url.pathname.match(/^\/__theme\/([\w-]+)$/);
|
|
342
|
-
if (!m) return false;
|
|
343
|
-
try {
|
|
344
|
-
const res = await fetch(url.pathname, {
|
|
345
|
-
headers: { accept: "application/json" }
|
|
346
|
-
});
|
|
347
|
-
const data = await res.json();
|
|
348
|
-
const ctx = { ...window.__WEIFUWU_CTX || {}, params: {}, query: {} };
|
|
349
|
-
ctx.prefs = { ...ctx.prefs, theme: data.theme };
|
|
350
|
-
window.__WEIFUWU_CTX = ctx;
|
|
351
|
-
applyTheme(data.theme);
|
|
352
|
-
setCtx(ctx);
|
|
353
|
-
} catch {
|
|
354
|
-
location.href = url.href;
|
|
355
|
-
}
|
|
356
|
-
return true;
|
|
357
|
-
});
|
|
358
|
-
function useTheme() {
|
|
359
|
-
const ctx = useCtx();
|
|
360
|
-
const theme = ctx.prefs.theme ?? "system";
|
|
361
|
-
useEffect4(() => {
|
|
362
|
-
applyTheme(theme);
|
|
363
|
-
}, [theme]);
|
|
364
|
-
return {
|
|
365
|
-
theme,
|
|
366
|
-
resolvedTheme: resolveTheme(theme),
|
|
367
|
-
setTheme: (t) => navigate("/__theme/" + t)
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// ../../use-flash-message.ts
|
|
372
|
-
import { useState as useState5 } from "react";
|
|
373
|
-
|
|
374
|
-
// ui/components/Greeting.tsx
|
|
375
|
-
import { jsxs } from "react/jsx-runtime";
|
|
376
|
-
function Greeting({ name }) {
|
|
377
|
-
return /* @__PURE__ */ jsxs("span", { className: "text-red-500 font-bold", children: [
|
|
378
|
-
name,
|
|
379
|
-
"!"
|
|
380
|
-
] });
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// ui/page.tsx
|
|
384
|
-
import { jsx, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
385
|
-
function Home() {
|
|
386
|
-
const [input, setInput] = useState6("");
|
|
387
|
-
const { send, lastMessage, readyState } = useWebsocket("/ws/echo");
|
|
388
|
-
const { locale, t, setLocale } = useLocale();
|
|
389
|
-
const { theme, resolvedTheme, setTheme } = useTheme();
|
|
390
|
-
const ld = useLoaderData();
|
|
391
|
-
return /* @__PURE__ */ jsxs2("div", { className: "min-h-screen bg-white dark:bg-gray-950 text-gray-900 dark:text-gray-100", children: [
|
|
392
|
-
/* @__PURE__ */ jsx("header", { className: "border-b dark:border-gray-800", children: /* @__PURE__ */ jsxs2("div", { className: "max-w-5xl mx-auto flex items-center justify-between h-14 px-4", children: [
|
|
393
|
-
/* @__PURE__ */ jsx("span", { className: "font-bold text-lg", children: "weifuwu" }),
|
|
394
|
-
/* @__PURE__ */ jsxs2("nav", { className: "hidden sm:flex gap-6 text-sm", children: [
|
|
395
|
-
/* @__PURE__ */ jsx("span", { className: "hover:text-blue-600 transition cursor-pointer", children: t("nav.home") }),
|
|
396
|
-
/* @__PURE__ */ jsx("span", { className: "hover:text-blue-600 transition cursor-pointer", children: t("nav.docs") }),
|
|
397
|
-
/* @__PURE__ */ jsx("span", { className: "hover:text-blue-600 transition cursor-pointer", children: t("nav.api") })
|
|
398
|
-
] }),
|
|
399
|
-
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 text-sm", children: [
|
|
400
|
-
/* @__PURE__ */ jsx(
|
|
401
|
-
"button",
|
|
402
|
-
{
|
|
403
|
-
onClick: () => setLocale(locale === "en" ? "zh-CN" : "en"),
|
|
404
|
-
className: "px-2 py-1 rounded border dark:border-gray-700 hover:bg-gray-100 dark:hover:bg-gray-800 transition",
|
|
405
|
-
children: locale === "en" ? "\u4E2D\u6587" : "EN"
|
|
406
|
-
}
|
|
407
|
-
),
|
|
408
|
-
/* @__PURE__ */ jsx(
|
|
409
|
-
"button",
|
|
410
|
-
{
|
|
411
|
-
onClick: () => setTheme(resolvedTheme === "light" ? "dark" : "light"),
|
|
412
|
-
className: "px-2 py-1 rounded border dark:border-gray-700 hover:bg-gray-100 dark:hover:bg-gray-800 transition",
|
|
413
|
-
children: resolvedTheme === "light" ? "\u{1F319}" : "\u2600\uFE0F"
|
|
414
|
-
}
|
|
415
|
-
)
|
|
416
|
-
] })
|
|
417
|
-
] }) }),
|
|
418
|
-
/* @__PURE__ */ jsxs2("section", { className: "text-center py-20 px-4", children: [
|
|
419
|
-
/* @__PURE__ */ jsx("h1", { className: "text-5xl font-bold mb-4", children: t("hero.title") }),
|
|
420
|
-
/* @__PURE__ */ jsx("p", { className: "text-xl text-gray-500 dark:text-gray-400 mb-8", children: t("hero.subtitle") }),
|
|
421
|
-
/* @__PURE__ */ jsx(Greeting, { name: "Weifuwu" }),
|
|
422
|
-
/* @__PURE__ */ jsxs2("div", { className: "flex justify-center gap-4 mt-8", children: [
|
|
423
|
-
/* @__PURE__ */ jsx("a", { href: "#", className: "px-6 py-2.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition font-medium", children: t("cta.start") }),
|
|
424
|
-
/* @__PURE__ */ jsx("a", { href: "#", className: "px-6 py-2.5 border dark:border-gray-700 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition font-medium", children: t("cta.learn") })
|
|
425
|
-
] })
|
|
426
|
-
] }),
|
|
427
|
-
ld.features && /* @__PURE__ */ jsx("section", { className: "max-w-4xl mx-auto grid grid-cols-1 md:grid-cols-3 gap-6 px-4 pb-20", children: ld.features.map((f, i) => /* @__PURE__ */ jsxs2("div", { className: "p-6 rounded-xl border dark:border-gray-800 bg-gray-50 dark:bg-gray-900", children: [
|
|
428
|
-
/* @__PURE__ */ jsx("h3", { className: "font-semibold mb-2", children: f.title }),
|
|
429
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: f.desc })
|
|
430
|
-
] }, i)) }),
|
|
431
|
-
/* @__PURE__ */ jsx("section", { className: "max-w-xl mx-auto px-4 pb-20", children: /* @__PURE__ */ jsxs2("div", { className: "border dark:border-gray-800 rounded-xl p-6 bg-gray-50 dark:bg-gray-900 space-y-4", children: [
|
|
432
|
-
/* @__PURE__ */ jsx("h2", { className: "font-semibold", children: t("demo.title") }),
|
|
433
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: readyState === 1 ? t("ws.connected") : readyState === 0 ? t("ws.connecting") : t("ws.disconnected") }),
|
|
434
|
-
/* @__PURE__ */ jsxs2("div", { className: "flex gap-2", children: [
|
|
435
|
-
/* @__PURE__ */ jsx(
|
|
436
|
-
"input",
|
|
437
|
-
{
|
|
438
|
-
value: input,
|
|
439
|
-
onChange: (e) => setInput(e.target.value),
|
|
440
|
-
onKeyDown: (e) => {
|
|
441
|
-
if (e.key === "Enter") {
|
|
442
|
-
send(input);
|
|
443
|
-
setInput("");
|
|
444
|
-
}
|
|
445
|
-
},
|
|
446
|
-
placeholder: t("demo.placeholder"),
|
|
447
|
-
className: "flex-1 border dark:border-gray-700 rounded px-3 py-2 text-sm bg-white dark:bg-gray-950 outline-none focus:border-blue-500 transition"
|
|
448
|
-
}
|
|
449
|
-
),
|
|
450
|
-
/* @__PURE__ */ jsx(
|
|
451
|
-
"button",
|
|
452
|
-
{
|
|
453
|
-
onClick: () => {
|
|
454
|
-
send(input);
|
|
455
|
-
setInput("");
|
|
456
|
-
},
|
|
457
|
-
className: "px-4 py-2 bg-blue-600 text-white rounded text-sm hover:bg-blue-700 transition font-medium",
|
|
458
|
-
children: t("demo.send")
|
|
459
|
-
}
|
|
460
|
-
)
|
|
461
|
-
] }),
|
|
462
|
-
lastMessage && /* @__PURE__ */ jsxs2("p", { className: "text-sm text-gray-600 dark:text-gray-400", children: [
|
|
463
|
-
/* @__PURE__ */ jsxs2("span", { className: "font-medium", children: [
|
|
464
|
-
t("demo.echo"),
|
|
465
|
-
":"
|
|
466
|
-
] }),
|
|
467
|
-
" ",
|
|
468
|
-
lastMessage
|
|
469
|
-
] })
|
|
470
|
-
] }) }),
|
|
471
|
-
/* @__PURE__ */ jsxs2("footer", { className: "border-t dark:border-gray-800 py-8 text-center text-sm text-gray-500", children: [
|
|
472
|
-
"\xA9 2026 MyApp \xB7 ",
|
|
473
|
-
t("footer.privacy"),
|
|
474
|
-
" \xB7 ",
|
|
475
|
-
t("footer.terms")
|
|
476
|
-
] })
|
|
477
|
-
] });
|
|
478
|
-
}
|
|
479
|
-
export {
|
|
480
|
-
Home as default
|
|
481
|
-
};
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
// ui/layout.tsx
|
|
2
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
function RootLayout({ children }) {
|
|
4
|
-
return /* @__PURE__ */ jsxs("html", { lang: "en", children: [
|
|
5
|
-
/* @__PURE__ */ jsxs("head", { children: [
|
|
6
|
-
/* @__PURE__ */ jsx("meta", { charSet: "utf-8" }),
|
|
7
|
-
/* @__PURE__ */ jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1" })
|
|
8
|
-
] }),
|
|
9
|
-
/* @__PURE__ */ jsx("body", { children: /* @__PURE__ */ jsx("main", { children }) })
|
|
10
|
-
] });
|
|
11
|
-
}
|
|
12
|
-
export {
|
|
13
|
-
RootLayout as default
|
|
14
|
-
};
|