@webstir-io/webstir 0.1.0
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/README.md +69 -0
- package/assets/features/client_nav/client_nav.ts +469 -0
- package/assets/features/content_nav/content_nav.css +170 -0
- package/assets/features/content_nav/content_nav.ts +358 -0
- package/assets/features/router/router-types.ts +6 -0
- package/assets/features/router/router.ts +118 -0
- package/assets/features/search/search.css +204 -0
- package/assets/features/search/search.ts +627 -0
- package/assets/templates/api/src/backend/index.ts +13 -0
- package/assets/templates/api/src/backend/tsconfig.json +15 -0
- package/assets/templates/api/src/shared/router-types.ts +23 -0
- package/assets/templates/api/src/shared/tsconfig.json +10 -0
- package/assets/templates/api/src/shared/types/index.ts +4 -0
- package/assets/templates/full/src/backend/index.ts +13 -0
- package/assets/templates/full/src/backend/tsconfig.json +15 -0
- package/assets/templates/full/src/frontend/app/app.css +65 -0
- package/assets/templates/full/src/frontend/app/app.html +13 -0
- package/assets/templates/full/src/frontend/app/app.ts +188 -0
- package/assets/templates/full/src/frontend/app/error.ts +127 -0
- package/assets/templates/full/src/frontend/app/hmr.js +355 -0
- package/assets/templates/full/src/frontend/app/navigation.ts +8 -0
- package/assets/templates/full/src/frontend/app/refresh.js +114 -0
- package/assets/templates/full/src/frontend/app/router.ts +126 -0
- package/assets/templates/full/src/frontend/app/styles/base.css +2 -0
- package/assets/templates/full/src/frontend/app/styles/reset.css +48 -0
- package/assets/templates/full/src/frontend/pages/home/index.css +21 -0
- package/assets/templates/full/src/frontend/pages/home/index.html +10 -0
- package/assets/templates/full/src/frontend/pages/home/index.ts +18 -0
- package/assets/templates/full/src/frontend/pages/home/tests/home.test.ts +21 -0
- package/assets/templates/full/src/frontend/tsconfig.json +20 -0
- package/assets/templates/full/src/shared/router-types.ts +23 -0
- package/assets/templates/full/src/shared/tsconfig.json +10 -0
- package/assets/templates/full/src/shared/types/index.ts +4 -0
- package/assets/templates/shared/Errors.404.html +23 -0
- package/assets/templates/shared/Errors.500.html +23 -0
- package/assets/templates/shared/Errors.default.html +23 -0
- package/assets/templates/shared/types/global.d.ts +32 -0
- package/assets/templates/shared/types.global.d.ts +32 -0
- package/assets/templates/spa/src/frontend/app/app.css +65 -0
- package/assets/templates/spa/src/frontend/app/app.html +13 -0
- package/assets/templates/spa/src/frontend/app/app.ts +188 -0
- package/assets/templates/spa/src/frontend/app/error.ts +127 -0
- package/assets/templates/spa/src/frontend/app/hmr.js +355 -0
- package/assets/templates/spa/src/frontend/app/navigation.ts +8 -0
- package/assets/templates/spa/src/frontend/app/refresh.js +114 -0
- package/assets/templates/spa/src/frontend/app/router.ts +126 -0
- package/assets/templates/spa/src/frontend/app/styles/base.css +2 -0
- package/assets/templates/spa/src/frontend/app/styles/reset.css +48 -0
- package/assets/templates/spa/src/frontend/pages/home/index.css +21 -0
- package/assets/templates/spa/src/frontend/pages/home/index.html +10 -0
- package/assets/templates/spa/src/frontend/pages/home/index.ts +18 -0
- package/assets/templates/spa/src/frontend/pages/home/tests/home.test.ts +21 -0
- package/assets/templates/spa/src/frontend/tsconfig.json +20 -0
- package/assets/templates/spa/src/shared/router-types.ts +23 -0
- package/assets/templates/spa/src/shared/tsconfig.json +10 -0
- package/assets/templates/spa/src/shared/types/index.ts +4 -0
- package/assets/templates/ssg/src/frontend/app/app.css +12 -0
- package/assets/templates/ssg/src/frontend/app/app.html +43 -0
- package/assets/templates/ssg/src/frontend/app/app.ts +190 -0
- package/assets/templates/ssg/src/frontend/app/error.ts +127 -0
- package/assets/templates/ssg/src/frontend/app/hmr.js +370 -0
- package/assets/templates/ssg/src/frontend/app/refresh.js +163 -0
- package/assets/templates/ssg/src/frontend/app/scripts/components/drawer.ts +183 -0
- package/assets/templates/ssg/src/frontend/app/scripts/components/menu.ts +75 -0
- package/assets/templates/ssg/src/frontend/app/styles/base.css +77 -0
- package/assets/templates/ssg/src/frontend/app/styles/components/buttons.css +108 -0
- package/assets/templates/ssg/src/frontend/app/styles/components/drawer.css +12 -0
- package/assets/templates/ssg/src/frontend/app/styles/components/header.css +164 -0
- package/assets/templates/ssg/src/frontend/app/styles/components/markdown.css +25 -0
- package/assets/templates/ssg/src/frontend/app/styles/layout.css +41 -0
- package/assets/templates/ssg/src/frontend/app/styles/reset.css +56 -0
- package/assets/templates/ssg/src/frontend/app/styles/tokens.css +72 -0
- package/assets/templates/ssg/src/frontend/app/styles/utilities.css +14 -0
- package/assets/templates/ssg/src/frontend/content/_sidebar.json +14 -0
- package/assets/templates/ssg/src/frontend/content/content-pipeline.md +82 -0
- package/assets/templates/ssg/src/frontend/content/css-playbook.md +68 -0
- package/assets/templates/ssg/src/frontend/content/hosting.md +48 -0
- package/assets/templates/ssg/src/frontend/pages/about/index.css +33 -0
- package/assets/templates/ssg/src/frontend/pages/about/index.html +60 -0
- package/assets/templates/ssg/src/frontend/pages/docs/index.css +505 -0
- package/assets/templates/ssg/src/frontend/pages/docs/index.html +52 -0
- package/assets/templates/ssg/src/frontend/pages/docs/index.ts +495 -0
- package/assets/templates/ssg/src/frontend/pages/home/index.css +91 -0
- package/assets/templates/ssg/src/frontend/pages/home/index.html +38 -0
- package/assets/templates/ssg/src/frontend/pages/home/tests/home.test.ts +24 -0
- package/assets/templates/ssg/src/frontend/tsconfig.json +13 -0
- package/package.json +41 -0
- package/scripts/pack-standalone.mjs +127 -0
- package/scripts/sync-assets.mjs +87 -0
- package/src/add-backend.ts +164 -0
- package/src/add.ts +112 -0
- package/src/api-watch.ts +84 -0
- package/src/backend-inspect.ts +45 -0
- package/src/backend-runtime.ts +286 -0
- package/src/build-plan.ts +12 -0
- package/src/build.ts +10 -0
- package/src/cli.ts +569 -0
- package/src/compile-tests.ts +61 -0
- package/src/dev-server.ts +393 -0
- package/src/enable-assets.ts +196 -0
- package/src/enable.ts +477 -0
- package/src/execute.ts +85 -0
- package/src/format.ts +254 -0
- package/src/frontend-watch.ts +145 -0
- package/src/full-watch.ts +80 -0
- package/src/index.ts +20 -0
- package/src/init-assets.ts +96 -0
- package/src/init.ts +339 -0
- package/src/paths.ts +26 -0
- package/src/providers.ts +88 -0
- package/src/publish.ts +8 -0
- package/src/refresh.ts +56 -0
- package/src/repair.ts +414 -0
- package/src/runtime.ts +48 -0
- package/src/smoke.ts +161 -0
- package/src/stop-signal.ts +26 -0
- package/src/test.ts +215 -0
- package/src/types.ts +29 -0
- package/src/watch-daemon-client.ts +171 -0
- package/src/watch-events.ts +195 -0
- package/src/watch.ts +66 -0
- package/src/workspace-watcher.ts +251 -0
- package/src/workspace.ts +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# webstir
|
|
2
|
+
|
|
3
|
+
Bun-first CLI for Webstir.
|
|
4
|
+
|
|
5
|
+
Primary command name: `webstir`
|
|
6
|
+
|
|
7
|
+
Current command surface:
|
|
8
|
+
|
|
9
|
+
- `init`
|
|
10
|
+
- `refresh`
|
|
11
|
+
- `repair`
|
|
12
|
+
- `enable`
|
|
13
|
+
- `add-page`
|
|
14
|
+
- `add-test`
|
|
15
|
+
- `add-route`
|
|
16
|
+
- `add-job`
|
|
17
|
+
- `backend-inspect`
|
|
18
|
+
- `build`
|
|
19
|
+
- `publish`
|
|
20
|
+
- `watch`
|
|
21
|
+
- `test`
|
|
22
|
+
- `smoke`
|
|
23
|
+
|
|
24
|
+
Repo-local use:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
bun run webstir -- --help
|
|
28
|
+
bun run webstir -- build --workspace /absolute/path/to/workspace
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Local machine install for external workspaces:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
cd /Users/iamce/dev/webstir-io/webstir/orchestrators/bun
|
|
35
|
+
bun link
|
|
36
|
+
|
|
37
|
+
mkdir -p ~/tmp/webstir-check
|
|
38
|
+
cd ~/tmp/webstir-check
|
|
39
|
+
bun link @webstir-io/webstir
|
|
40
|
+
|
|
41
|
+
./node_modules/.bin/webstir init ssg site
|
|
42
|
+
cd site
|
|
43
|
+
bun install
|
|
44
|
+
../node_modules/.bin/webstir build --workspace "$PWD"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
For a tarball install, pack the CLI locally with:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
cd /Users/iamce/dev/webstir-io/webstir/orchestrators/bun
|
|
51
|
+
bun run pack:local
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
For a machine-local standalone tarball that bundles the current Webstir packages:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
cd /Users/iamce/dev/webstir-io/webstir/orchestrators/bun
|
|
58
|
+
bun run pack:standalone
|
|
59
|
+
|
|
60
|
+
mkdir -p ~/tmp/webstir-standalone
|
|
61
|
+
cd ~/tmp/webstir-standalone
|
|
62
|
+
printf '{"name":"webstir-local","private":true}\n' > package.json
|
|
63
|
+
bun add /Users/iamce/dev/webstir-io/webstir/orchestrators/bun/artifacts/webstir-io-webstir-0.1.0-standalone.tgz
|
|
64
|
+
|
|
65
|
+
./node_modules/.bin/webstir init ssg site
|
|
66
|
+
cd site
|
|
67
|
+
bun install
|
|
68
|
+
../node_modules/.bin/webstir build --workspace "$PWD"
|
|
69
|
+
```
|
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
export {};
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Minimal PJAX-style navigation: swaps the <main> content, updates title/URL,
|
|
5
|
+
* and restores scroll/focus.
|
|
6
|
+
*
|
|
7
|
+
* Opt out per-link with:
|
|
8
|
+
* - data-no-client-nav
|
|
9
|
+
* - data-client-nav="off"
|
|
10
|
+
*/
|
|
11
|
+
export function enableClientNav(): void {
|
|
12
|
+
document.addEventListener('click', async (event) => {
|
|
13
|
+
const target = event.target;
|
|
14
|
+
if (!(target instanceof Element)) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (event.defaultPrevented || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const link = target.closest('a');
|
|
22
|
+
if (!link || !(link instanceof HTMLAnchorElement)) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const setting = link.getAttribute('data-client-nav');
|
|
27
|
+
const optOut = link.hasAttribute('data-no-client-nav')
|
|
28
|
+
|| setting === 'off'
|
|
29
|
+
|| setting === 'false';
|
|
30
|
+
if (optOut) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const isExternal = link.origin !== window.location.origin;
|
|
35
|
+
const opensInNewTab = link.getAttribute('target') === '_blank';
|
|
36
|
+
const isDownload = link.hasAttribute('download');
|
|
37
|
+
if (isExternal || opensInNewTab || isDownload) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const isSameDocumentAnchor = link.hash
|
|
42
|
+
&& link.pathname === window.location.pathname
|
|
43
|
+
&& link.search === window.location.search;
|
|
44
|
+
if (isSameDocumentAnchor) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
event.preventDefault();
|
|
49
|
+
await renderUrl(link.href, { pushHistory: true });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
window.addEventListener('popstate', async () => {
|
|
53
|
+
await renderUrl(window.location.href, { pushHistory: false });
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let activeRequestId = 0;
|
|
58
|
+
let activeController: AbortController | null = null;
|
|
59
|
+
const DYNAMIC_ATTR = 'data-webstir-dynamic';
|
|
60
|
+
const DYNAMIC_VALUE = 'client-nav';
|
|
61
|
+
const BASE_PATH = resolveBasePath();
|
|
62
|
+
|
|
63
|
+
function resolveBasePath(): string {
|
|
64
|
+
const raw = document.documentElement?.getAttribute('data-webstir-base') ?? '';
|
|
65
|
+
return normalizeBasePath(raw);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function normalizeBasePath(value: string): string {
|
|
69
|
+
const trimmed = value.trim();
|
|
70
|
+
if (!trimmed || trimmed === '/') {
|
|
71
|
+
return '';
|
|
72
|
+
}
|
|
73
|
+
if (!trimmed.startsWith('/')) {
|
|
74
|
+
return `/${trimmed}`;
|
|
75
|
+
}
|
|
76
|
+
return trimmed.endsWith('/') ? trimmed.slice(0, -1) : trimmed;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function withBasePath(value: string): string {
|
|
80
|
+
if (!BASE_PATH) {
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
if (!value.startsWith('/') || value.startsWith('//')) {
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
if (value === BASE_PATH || value.startsWith(`${BASE_PATH}/`) || value.startsWith(`${BASE_PATH}?`) || value.startsWith(`${BASE_PATH}#`)) {
|
|
87
|
+
return value;
|
|
88
|
+
}
|
|
89
|
+
return `${BASE_PATH}${value}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function stripBasePath(value: string): string {
|
|
93
|
+
if (!BASE_PATH || !value.startsWith('/')) {
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
if (value === BASE_PATH) {
|
|
97
|
+
return '/';
|
|
98
|
+
}
|
|
99
|
+
if (value.startsWith(`${BASE_PATH}/`) || value.startsWith(`${BASE_PATH}?`) || value.startsWith(`${BASE_PATH}#`)) {
|
|
100
|
+
return value.slice(BASE_PATH.length);
|
|
101
|
+
}
|
|
102
|
+
return value;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function renderUrl(url: string, { pushHistory }: { pushHistory: boolean }): Promise<void> {
|
|
106
|
+
activeRequestId += 1;
|
|
107
|
+
const requestId = activeRequestId;
|
|
108
|
+
|
|
109
|
+
if (activeController) {
|
|
110
|
+
activeController.abort();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const controller = new AbortController();
|
|
114
|
+
activeController = controller;
|
|
115
|
+
|
|
116
|
+
let response: Response;
|
|
117
|
+
try {
|
|
118
|
+
response = await fetch(url, {
|
|
119
|
+
headers: { 'X-Webstir-Client-Nav': '1' },
|
|
120
|
+
signal: controller.signal
|
|
121
|
+
});
|
|
122
|
+
} catch {
|
|
123
|
+
if (controller.signal.aborted) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
window.location.href = url;
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!response.ok) {
|
|
132
|
+
window.location.href = url;
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const html = await response.text();
|
|
137
|
+
if (requestId !== activeRequestId) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const parser = new DOMParser();
|
|
142
|
+
const doc = parser.parseFromString(html, 'text/html');
|
|
143
|
+
|
|
144
|
+
await syncHead(doc, url);
|
|
145
|
+
|
|
146
|
+
const newMain = doc.querySelector('main');
|
|
147
|
+
const currentMain = document.querySelector('main');
|
|
148
|
+
if (newMain && currentMain) {
|
|
149
|
+
currentMain.replaceWith(newMain);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const newTitle = doc.querySelector('title');
|
|
153
|
+
if (newTitle && newTitle.textContent) {
|
|
154
|
+
document.title = newTitle.textContent;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (pushHistory) {
|
|
158
|
+
window.history.pushState({}, '', url);
|
|
159
|
+
}
|
|
160
|
+
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
161
|
+
const focusTarget = document.querySelector('[autofocus]');
|
|
162
|
+
if (focusTarget instanceof HTMLElement) {
|
|
163
|
+
focusTarget.focus();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
executeScripts(document.querySelector('main'));
|
|
167
|
+
window.dispatchEvent(new CustomEvent('webstir:client-nav', { detail: { url } }));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
enableClientNav();
|
|
171
|
+
|
|
172
|
+
async function syncHead(doc: Document, url: string): Promise<void> {
|
|
173
|
+
const head = document.head;
|
|
174
|
+
const newHead = doc.head;
|
|
175
|
+
if (!head || !newHead) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const preservedClientNav = head.querySelector('script[data-webstir="client-nav"]');
|
|
180
|
+
const preservedAppCss = Array.from(head.querySelectorAll<HTMLLinkElement>('link[rel="stylesheet"]'))
|
|
181
|
+
.find((link) => isAppStylesheetHref(link.getAttribute('href'))) ?? null;
|
|
182
|
+
|
|
183
|
+
for (const element of Array.from(head.querySelectorAll(`script[${DYNAMIC_ATTR}="${DYNAMIC_VALUE}"]`))) {
|
|
184
|
+
element.remove();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
for (const script of Array.from(head.querySelectorAll('script[src]'))) {
|
|
188
|
+
const src = script.getAttribute('src') ?? '';
|
|
189
|
+
const normalizedSrc = stripBasePath(src);
|
|
190
|
+
if (script === preservedClientNav) {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (normalizedSrc === '/hmr.js' || normalizedSrc === '/refresh.js') {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (normalizedSrc.startsWith('/pages/')) {
|
|
197
|
+
script.remove();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const desiredStyles = new Map<string, string>();
|
|
202
|
+
for (const link of Array.from(newHead.querySelectorAll<HTMLLinkElement>('link[rel="stylesheet"]'))) {
|
|
203
|
+
const href = link.getAttribute('href');
|
|
204
|
+
if (!href) {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
const resolved = resolveUrl(href, url);
|
|
208
|
+
if (!resolved) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const key = stripBasePath(stripQueryAndHash(resolved));
|
|
212
|
+
const finalHref = key === '/app/app.css' && preservedAppCss
|
|
213
|
+
? (preservedAppCss.getAttribute('href') ?? resolved)
|
|
214
|
+
: withBasePath(resolved);
|
|
215
|
+
desiredStyles.set(key, finalHref);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (preservedAppCss) {
|
|
219
|
+
const appHref = preservedAppCss.getAttribute('href') ?? withBasePath('/app/app.css');
|
|
220
|
+
desiredStyles.set('/app/app.css', appHref);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const existingStyles = new Map<string, HTMLLinkElement>();
|
|
224
|
+
const staleStyles: HTMLLinkElement[] = [];
|
|
225
|
+
for (const link of Array.from(head.querySelectorAll<HTMLLinkElement>('link[rel="stylesheet"]'))) {
|
|
226
|
+
const key = normalizeStylesheetKey(link.getAttribute('href'), window.location.href);
|
|
227
|
+
if (!key) {
|
|
228
|
+
link.remove();
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (desiredStyles.has(key)) {
|
|
232
|
+
if (!existingStyles.has(key)) {
|
|
233
|
+
existingStyles.set(key, link);
|
|
234
|
+
}
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
staleStyles.push(link);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const pendingStyles: HTMLLinkElement[] = [];
|
|
241
|
+
for (const [key, href] of desiredStyles.entries()) {
|
|
242
|
+
if (existingStyles.has(key)) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
const next = document.createElement('link');
|
|
246
|
+
next.rel = 'stylesheet';
|
|
247
|
+
next.href = href;
|
|
248
|
+
head.appendChild(next);
|
|
249
|
+
existingStyles.set(key, next);
|
|
250
|
+
pendingStyles.push(next);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const stylesReady = pendingStyles.length > 0
|
|
254
|
+
? waitForStylesheets(pendingStyles)
|
|
255
|
+
: Promise.resolve();
|
|
256
|
+
if (staleStyles.length > 0) {
|
|
257
|
+
void stylesReady.then(() => {
|
|
258
|
+
requestAnimationFrame(() => {
|
|
259
|
+
for (const link of staleStyles) {
|
|
260
|
+
link.remove();
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
syncCriticalStyles(head, newHead);
|
|
267
|
+
|
|
268
|
+
for (const script of Array.from(newHead.querySelectorAll('script[src]'))) {
|
|
269
|
+
const src = script.getAttribute('src');
|
|
270
|
+
if (!src) {
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
if (src === '/clientNav.js' || src.endsWith('/clientNav.js')) {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
if (src === '/hmr.js' || src === '/refresh.js') {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const resolved = resolveUrl(src, url);
|
|
281
|
+
if (!resolved) {
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const next = document.createElement('script');
|
|
286
|
+
const type = script.getAttribute('type');
|
|
287
|
+
if (type) {
|
|
288
|
+
next.type = type;
|
|
289
|
+
}
|
|
290
|
+
next.src = resolved;
|
|
291
|
+
next.setAttribute(DYNAMIC_ATTR, DYNAMIC_VALUE);
|
|
292
|
+
head.appendChild(next);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (preservedClientNav && !head.contains(preservedClientNav)) {
|
|
296
|
+
head.appendChild(preservedClientNav);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
await stylesReady;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function executeScripts(container: Element | null): void {
|
|
303
|
+
if (!container) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const scripts = Array.from(container.querySelectorAll('script'));
|
|
308
|
+
for (const script of scripts) {
|
|
309
|
+
const src = script.getAttribute('src');
|
|
310
|
+
const type = script.getAttribute('type');
|
|
311
|
+
|
|
312
|
+
const normalizedSrc = src ? stripBasePath(src) : '';
|
|
313
|
+
if (normalizedSrc && (normalizedSrc === '/clientNav.js' || normalizedSrc.endsWith('/clientNav.js'))) {
|
|
314
|
+
script.remove();
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
if (normalizedSrc === '/hmr.js' || normalizedSrc === '/refresh.js') {
|
|
318
|
+
script.remove();
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const next = document.createElement('script');
|
|
323
|
+
if (type) {
|
|
324
|
+
next.type = type;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (src) {
|
|
328
|
+
const resolved = resolveUrl(src, window.location.href);
|
|
329
|
+
if (resolved) {
|
|
330
|
+
next.src = resolved;
|
|
331
|
+
}
|
|
332
|
+
} else if (script.textContent) {
|
|
333
|
+
next.textContent = script.textContent;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
script.replaceWith(next);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function resolveUrl(value: string, baseUrl: string): string | null {
|
|
341
|
+
try {
|
|
342
|
+
const trimmed = String(value ?? '').trim();
|
|
343
|
+
const [path, suffix] = splitPathSuffix(trimmed);
|
|
344
|
+
if (path && !path.startsWith('/') && !path.startsWith('http:') && !path.startsWith('https:')) {
|
|
345
|
+
if (path === 'index.js' || path === 'index.css') {
|
|
346
|
+
const pageName = getPageNameFromUrl(baseUrl);
|
|
347
|
+
return withBasePath(`/pages/${pageName}/${path}${suffix}`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const resolved = new URL(value, baseUrl);
|
|
352
|
+
return withBasePath(resolved.pathname + resolved.search + resolved.hash);
|
|
353
|
+
} catch {
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function normalizeStylesheetKey(href: string | null, baseUrl: string): string | null {
|
|
359
|
+
const resolved = resolveUrl(href ?? '', baseUrl);
|
|
360
|
+
if (!resolved) {
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
return stripBasePath(stripQueryAndHash(resolved));
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function stripQueryAndHash(value: string): string {
|
|
367
|
+
return value.split(/[?#]/)[0] ?? value;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function waitForStylesheets(links: HTMLLinkElement[], timeoutMs = 2000): Promise<void> {
|
|
371
|
+
if (links.length === 0) {
|
|
372
|
+
return Promise.resolve();
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return new Promise((resolve) => {
|
|
376
|
+
let remaining = links.length;
|
|
377
|
+
let done = false;
|
|
378
|
+
const finish = () => {
|
|
379
|
+
if (done) {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
done = true;
|
|
383
|
+
resolve();
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
const timer = window.setTimeout(finish, timeoutMs);
|
|
387
|
+
const handle = () => {
|
|
388
|
+
if (done) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
remaining -= 1;
|
|
392
|
+
if (remaining <= 0) {
|
|
393
|
+
window.clearTimeout(timer);
|
|
394
|
+
finish();
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
for (const link of links) {
|
|
399
|
+
if (link.sheet) {
|
|
400
|
+
handle();
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
link.addEventListener('load', handle, { once: true });
|
|
404
|
+
link.addEventListener('error', handle, { once: true });
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function syncCriticalStyles(head: HTMLHeadElement, newHead: HTMLHeadElement): void {
|
|
410
|
+
for (const style of Array.from(head.querySelectorAll<HTMLStyleElement>('style[data-critical]'))) {
|
|
411
|
+
style.remove();
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
for (const style of Array.from(newHead.querySelectorAll<HTMLStyleElement>('style[data-critical]'))) {
|
|
415
|
+
const next = document.createElement('style');
|
|
416
|
+
for (const attribute of Array.from(style.attributes)) {
|
|
417
|
+
next.setAttribute(attribute.name, attribute.value);
|
|
418
|
+
}
|
|
419
|
+
if (style.textContent) {
|
|
420
|
+
next.textContent = style.textContent;
|
|
421
|
+
}
|
|
422
|
+
head.appendChild(next);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function splitPathSuffix(value: string): [string, string] {
|
|
427
|
+
const [path, suffix = ''] = value.split(/(?=[?#])/);
|
|
428
|
+
return [path ?? '', suffix ?? ''];
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function isAppStylesheetHref(href: string | null): boolean {
|
|
432
|
+
if (!href) {
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
try {
|
|
437
|
+
const normalized = stripBasePath(new URL(href, window.location.origin).pathname);
|
|
438
|
+
return normalized === '/app/app.css';
|
|
439
|
+
} catch {
|
|
440
|
+
const trimmed = href.trim();
|
|
441
|
+
if (!trimmed) {
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
const [path] = trimmed.split(/[?#]/);
|
|
445
|
+
return stripBasePath(path) === '/app/app.css';
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function getPageNameFromUrl(url: string): string {
|
|
450
|
+
try {
|
|
451
|
+
const pathname = stripBasePath(new URL(url, window.location.href).pathname);
|
|
452
|
+
const trimmed = pathname.replace(/^\/+|\/+$/g, '');
|
|
453
|
+
if (!trimmed) {
|
|
454
|
+
return 'home';
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const firstSegment = trimmed.split('/')[0];
|
|
458
|
+
return firstSegment || 'home';
|
|
459
|
+
} catch {
|
|
460
|
+
return 'home';
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function cssEscape(value: string): string {
|
|
465
|
+
if (typeof CSS !== 'undefined' && typeof (CSS as { escape?: (value: string) => string }).escape === 'function') {
|
|
466
|
+
return (CSS as { escape: (value: string) => string }).escape(value);
|
|
467
|
+
}
|
|
468
|
+
return value.replace(/[\"\\\\]/g, '\\\\$&');
|
|
469
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
@layer features {
|
|
2
|
+
.docs-layout[data-content-nav="true"] .docs-sidebar {
|
|
3
|
+
position: fixed;
|
|
4
|
+
top: var(--ws-header-sticky-offset, 0px);
|
|
5
|
+
bottom: 0;
|
|
6
|
+
left: 0;
|
|
7
|
+
width: var(--ws-docs-sidebar-width, 16rem);
|
|
8
|
+
border: 0;
|
|
9
|
+
border-right: 1px solid var(--ws-border);
|
|
10
|
+
border-radius: 0;
|
|
11
|
+
background: var(--ws-surface-1);
|
|
12
|
+
padding: var(--ws-space-5) var(--ws-space-6);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.docs-panel__header {
|
|
16
|
+
display: flex;
|
|
17
|
+
align-items: center;
|
|
18
|
+
justify-content: space-between;
|
|
19
|
+
gap: var(--ws-space-3);
|
|
20
|
+
margin-bottom: var(--ws-space-3);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.docs-panel__title {
|
|
24
|
+
font-size: 0.75rem;
|
|
25
|
+
text-transform: uppercase;
|
|
26
|
+
letter-spacing: 0.08em;
|
|
27
|
+
font-weight: 700;
|
|
28
|
+
color: var(--ws-muted);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.docs-panel__link {
|
|
32
|
+
font-size: 1rem;
|
|
33
|
+
letter-spacing: 0;
|
|
34
|
+
font-weight: 600;
|
|
35
|
+
color: var(--ws-fg);
|
|
36
|
+
text-decoration: none;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.docs-panel__link:hover {
|
|
40
|
+
color: var(--ws-accent);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.docs-panel__link:focus-visible {
|
|
44
|
+
outline: 0.1875rem solid var(--ws-focus);
|
|
45
|
+
outline-offset: 0.125rem;
|
|
46
|
+
border-radius: var(--ws-radius-2);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.docs-toolbar {
|
|
50
|
+
display: flex;
|
|
51
|
+
align-items: center;
|
|
52
|
+
gap: var(--ws-space-3);
|
|
53
|
+
flex-wrap: wrap;
|
|
54
|
+
margin-bottom: var(--ws-space-4);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.docs-breadcrumb {
|
|
58
|
+
flex: 1 1 auto;
|
|
59
|
+
min-width: 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.docs-breadcrumb__list {
|
|
63
|
+
list-style: none;
|
|
64
|
+
margin: 0;
|
|
65
|
+
padding: 0;
|
|
66
|
+
display: flex;
|
|
67
|
+
flex-wrap: wrap;
|
|
68
|
+
align-items: center;
|
|
69
|
+
gap: var(--ws-space-2);
|
|
70
|
+
color: var(--ws-muted);
|
|
71
|
+
font-size: 0.9rem;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.docs-breadcrumb__item {
|
|
75
|
+
display: inline-flex;
|
|
76
|
+
align-items: center;
|
|
77
|
+
gap: var(--ws-space-2);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.docs-breadcrumb__item:not(:first-child)::before {
|
|
81
|
+
content: "/";
|
|
82
|
+
color: var(--ws-muted);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.docs-breadcrumb__link {
|
|
86
|
+
color: inherit;
|
|
87
|
+
text-decoration: none;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.docs-breadcrumb__link:hover {
|
|
91
|
+
color: var(--ws-accent);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.docs-nav__list--nested {
|
|
95
|
+
margin-top: var(--ws-space-2);
|
|
96
|
+
padding-left: var(--ws-space-4);
|
|
97
|
+
border-left: 1px solid var(--ws-border);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.docs-nav__link,
|
|
101
|
+
.docs-nav__label {
|
|
102
|
+
color: inherit;
|
|
103
|
+
text-decoration: none;
|
|
104
|
+
font-weight: 600;
|
|
105
|
+
line-height: 1.4;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.docs-nav__label {
|
|
109
|
+
display: inline-flex;
|
|
110
|
+
color: var(--ws-muted);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.docs-nav__link {
|
|
114
|
+
color: var(--ws-fg);
|
|
115
|
+
font-size: 0.95rem;
|
|
116
|
+
padding-left: var(--ws-space-3);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.docs-nav__link:hover {
|
|
120
|
+
color: var(--ws-accent);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.docs-nav__item[data-active="true"] > .docs-nav__link,
|
|
124
|
+
.docs-nav__item[data-active-branch="true"] > .docs-nav__label {
|
|
125
|
+
color: var(--ws-accent);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.app-nav__docs {
|
|
129
|
+
display: none;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.app-nav__docs > .docs-nav__list {
|
|
133
|
+
padding-left: var(--ws-space-3);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.app-nav__docs .docs-nav__link {
|
|
137
|
+
color: var(--ws-fg);
|
|
138
|
+
font-weight: 400;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.app-nav__docs .docs-nav__link:focus-visible {
|
|
142
|
+
outline: 0.1875rem solid var(--ws-focus);
|
|
143
|
+
outline-offset: 0.125rem;
|
|
144
|
+
border-radius: var(--ws-radius-1);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.app-nav__docs .docs-nav__list--nested {
|
|
148
|
+
padding-left: var(--ws-space-2);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@media (--ws-sm) {
|
|
152
|
+
.docs-layout[data-content-nav="true"] .docs-sidebar {
|
|
153
|
+
display: none;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.docs-toolbar {
|
|
157
|
+
margin-bottom: var(--ws-space-2);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.app-nav__docs {
|
|
161
|
+
display: grid;
|
|
162
|
+
gap: var(--ws-space-1);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.app-menu.is-open .app-nav__docs .docs-nav__link {
|
|
166
|
+
width: 100%;
|
|
167
|
+
justify-content: space-between;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|