zero-http 0.2.0 → 0.2.2
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 +314 -115
- package/documentation/full-server.js +102 -5
- package/documentation/public/data/api.json +154 -33
- package/documentation/public/data/examples.json +35 -11
- package/documentation/public/data/options.json +14 -8
- package/documentation/public/index.html +138 -53
- package/documentation/public/scripts/data-sections.js +23 -4
- package/documentation/public/scripts/helpers.js +4 -4
- package/documentation/public/scripts/playground.js +204 -0
- package/documentation/public/scripts/ui.js +140 -8
- package/documentation/public/scripts/uploads.js +35 -20
- package/documentation/public/styles.css +46 -23
- package/documentation/public/vendor/icons/compress.svg +27 -0
- package/documentation/public/vendor/icons/https.svg +18 -0
- package/documentation/public/vendor/icons/logo.svg +24 -0
- package/documentation/public/vendor/icons/router.svg +27 -0
- package/documentation/public/vendor/icons/sse.svg +22 -0
- package/documentation/public/vendor/icons/websocket.svg +21 -0
- package/index.js +21 -4
- package/lib/app.js +156 -15
- package/lib/body/json.js +3 -0
- package/lib/body/multipart.js +2 -0
- package/lib/body/raw.js +3 -0
- package/lib/body/text.js +3 -0
- package/lib/body/urlencoded.js +3 -0
- package/lib/{fetch.js → fetch/index.js} +30 -1
- package/lib/http/index.js +9 -0
- package/lib/{request.js → http/request.js} +7 -1
- package/lib/{response.js → http/response.js} +70 -1
- package/lib/middleware/compress.js +194 -0
- package/lib/middleware/index.js +12 -0
- package/lib/router/index.js +278 -0
- package/lib/sse/index.js +8 -0
- package/lib/sse/stream.js +322 -0
- package/lib/ws/connection.js +440 -0
- package/lib/ws/handshake.js +122 -0
- package/lib/ws/index.js +12 -0
- package/package.json +1 -1
- package/lib/router.js +0 -87
- /package/lib/{cors.js → middleware/cors.js} +0 -0
- /package/lib/{logger.js → middleware/logger.js} +0 -0
- /package/lib/{rateLimit.js → middleware/rateLimit.js} +0 -0
- /package/lib/{static.js → middleware/static.js} +0 -0
|
@@ -12,9 +12,11 @@ document.addEventListener('DOMContentLoaded', () =>
|
|
|
12
12
|
initTocSidebar();
|
|
13
13
|
initTocNavigation();
|
|
14
14
|
initTocToolbar();
|
|
15
|
+
initTocCollapsible();
|
|
16
|
+
initTocSearch();
|
|
15
17
|
});
|
|
16
18
|
|
|
17
|
-
/*
|
|
19
|
+
/* -- Feature Tabs ------------------------------------------------------------ */
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
22
|
* Wire the feature / server-model tab buttons so clicking one activates
|
|
@@ -47,7 +49,7 @@ function initFeatureTabs()
|
|
|
47
49
|
});
|
|
48
50
|
}
|
|
49
51
|
|
|
50
|
-
/*
|
|
52
|
+
/* -- TOC Sidebar Toggle ------------------------------------------------------ */
|
|
51
53
|
|
|
52
54
|
/**
|
|
53
55
|
* Wire the hamburger button to toggle the sidebar on both desktop (persistent)
|
|
@@ -106,7 +108,7 @@ function initTocSidebar()
|
|
|
106
108
|
window.addEventListener('resize', syncAria);
|
|
107
109
|
}
|
|
108
110
|
|
|
109
|
-
/*
|
|
111
|
+
/* -- TOC Smooth-Scroll Navigation -------------------------------------------- */
|
|
110
112
|
|
|
111
113
|
/**
|
|
112
114
|
* When clicking a TOC link that points to a `#hash`, auto-open any ancestor
|
|
@@ -166,7 +168,7 @@ function initTocNavigation()
|
|
|
166
168
|
});
|
|
167
169
|
}
|
|
168
170
|
|
|
169
|
-
/*
|
|
171
|
+
/* -- TOC Toolbar (scroll-to-top & expand/collapse all) ----------------------- */
|
|
170
172
|
|
|
171
173
|
/**
|
|
172
174
|
* Wire the icon-bar buttons at the top of the sidebar:
|
|
@@ -179,7 +181,7 @@ function initTocToolbar()
|
|
|
179
181
|
const toggleBtn = document.getElementById('toc-toggle-acc');
|
|
180
182
|
if (!topBtn && !toggleBtn) return;
|
|
181
183
|
|
|
182
|
-
/*
|
|
184
|
+
/* -- Scroll to top -------------------------------------------------- */
|
|
183
185
|
const brandBtn = document.getElementById('brand-top');
|
|
184
186
|
|
|
185
187
|
[topBtn, brandBtn].forEach(el =>
|
|
@@ -192,15 +194,21 @@ function initTocToolbar()
|
|
|
192
194
|
});
|
|
193
195
|
});
|
|
194
196
|
|
|
195
|
-
/*
|
|
197
|
+
/* -- Expand / Collapse sidebar categories only ---------------------- */
|
|
196
198
|
if (toggleBtn)
|
|
197
199
|
{
|
|
198
|
-
let expanded =
|
|
200
|
+
let expanded = true; /* Start expanded */
|
|
201
|
+
toggleBtn.classList.add('acc-expanded');
|
|
199
202
|
|
|
200
203
|
toggleBtn.addEventListener('click', () =>
|
|
201
204
|
{
|
|
202
205
|
expanded = !expanded;
|
|
203
|
-
|
|
206
|
+
|
|
207
|
+
/* Toggle only collapsible TOC categories in the sidebar */
|
|
208
|
+
document.querySelectorAll('.toc-collapsible').forEach(li =>
|
|
209
|
+
{
|
|
210
|
+
li.classList.toggle('toc-collapsed', !expanded);
|
|
211
|
+
});
|
|
204
212
|
|
|
205
213
|
toggleBtn.classList.toggle('acc-expanded', expanded);
|
|
206
214
|
toggleBtn.title = expanded ? 'Collapse all' : 'Expand all';
|
|
@@ -208,3 +216,127 @@ function initTocToolbar()
|
|
|
208
216
|
});
|
|
209
217
|
}
|
|
210
218
|
}
|
|
219
|
+
|
|
220
|
+
/* -- TOC Collapsible Categories ---------------------------------------------- */
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Make sidebar categories that have (or will have) nested sub-items
|
|
224
|
+
* collapsible via a toggle chevron. Clicking the chevron expands/collapses
|
|
225
|
+
* the sub-list. Clicking the link itself still navigates.
|
|
226
|
+
*/
|
|
227
|
+
function initTocCollapsible()
|
|
228
|
+
{
|
|
229
|
+
const items = document.querySelectorAll('.toc-collapsible');
|
|
230
|
+
items.forEach(li =>
|
|
231
|
+
{
|
|
232
|
+
/* Start expanded */
|
|
233
|
+
|
|
234
|
+
/* Create toggle button */
|
|
235
|
+
const toggle = document.createElement('button');
|
|
236
|
+
toggle.className = 'toc-collapse-btn';
|
|
237
|
+
toggle.setAttribute('aria-label', 'Toggle section');
|
|
238
|
+
toggle.innerHTML = '<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
|
239
|
+
|
|
240
|
+
toggle.addEventListener('click', (e) =>
|
|
241
|
+
{
|
|
242
|
+
e.preventDefault();
|
|
243
|
+
e.stopPropagation();
|
|
244
|
+
li.classList.toggle('toc-collapsed');
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
li.insertBefore(toggle, li.firstChild);
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/* -- TOC Search Filter ------------------------------------------------------- */
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Wire the sidebar search input to filter TOC items by matching against
|
|
255
|
+
* the display name (link text) of each item in the sidebar.
|
|
256
|
+
*/
|
|
257
|
+
function initTocSearch()
|
|
258
|
+
{
|
|
259
|
+
const input = document.getElementById('toc-search');
|
|
260
|
+
if (!input) return;
|
|
261
|
+
|
|
262
|
+
const nav = document.querySelector('.toc-sidebar nav ul');
|
|
263
|
+
if (!nav) return;
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Get the visible display name for a TOC list item.
|
|
267
|
+
*/
|
|
268
|
+
function getDisplayName(li)
|
|
269
|
+
{
|
|
270
|
+
const a = li.querySelector(':scope > a');
|
|
271
|
+
return a ? a.textContent.trim().toLowerCase() : '';
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Check whether any sub-items match the query by display name.
|
|
276
|
+
*/
|
|
277
|
+
function hasSubMatch(li, q)
|
|
278
|
+
{
|
|
279
|
+
const subItems = li.querySelectorAll('.toc-sub-item');
|
|
280
|
+
for (const sub of subItems)
|
|
281
|
+
{
|
|
282
|
+
if (getDisplayName(sub).includes(q)) return true;
|
|
283
|
+
}
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
let debounceTimer = null;
|
|
288
|
+
|
|
289
|
+
input.addEventListener('input', () =>
|
|
290
|
+
{
|
|
291
|
+
clearTimeout(debounceTimer);
|
|
292
|
+
debounceTimer = setTimeout(() =>
|
|
293
|
+
{
|
|
294
|
+
const q = input.value.trim().toLowerCase();
|
|
295
|
+
|
|
296
|
+
const topItems = nav.querySelectorAll(':scope > li');
|
|
297
|
+
|
|
298
|
+
if (!q)
|
|
299
|
+
{
|
|
300
|
+
/* Reset: show everything, restore collapsed state */
|
|
301
|
+
topItems.forEach(li =>
|
|
302
|
+
{
|
|
303
|
+
li.style.display = '';
|
|
304
|
+
const subItems = li.querySelectorAll('.toc-sub-item');
|
|
305
|
+
subItems.forEach(s => s.style.display = '');
|
|
306
|
+
});
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
topItems.forEach(li =>
|
|
311
|
+
{
|
|
312
|
+
const titleMatch = getDisplayName(li).includes(q);
|
|
313
|
+
const subMatch = hasSubMatch(li, q);
|
|
314
|
+
|
|
315
|
+
if (titleMatch || subMatch)
|
|
316
|
+
{
|
|
317
|
+
li.style.display = '';
|
|
318
|
+
/* Auto-expand when searching */
|
|
319
|
+
if (li.classList.contains('toc-collapsible'))
|
|
320
|
+
{
|
|
321
|
+
li.classList.remove('toc-collapsed');
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/* Filter sub-items if only some match */
|
|
325
|
+
const subItems = li.querySelectorAll('.toc-sub-item');
|
|
326
|
+
if (subItems.length)
|
|
327
|
+
{
|
|
328
|
+
subItems.forEach(sub =>
|
|
329
|
+
{
|
|
330
|
+
/* If parent title matched, show all children; otherwise filter by sub-item name */
|
|
331
|
+
sub.style.display = (titleMatch || getDisplayName(sub).includes(q)) ? '' : 'none';
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
else
|
|
336
|
+
{
|
|
337
|
+
li.style.display = 'none';
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
}, 120);
|
|
341
|
+
});
|
|
342
|
+
}
|
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
* showJsonResult, highlightAllPre)
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
/*
|
|
10
|
+
/* -- Pagination State -------------------------------------------------------- */
|
|
11
11
|
|
|
12
12
|
let currentPage = 1;
|
|
13
13
|
let currentSort = 'mtime';
|
|
14
14
|
let currentOrder = 'desc';
|
|
15
15
|
const pageSize = 4;
|
|
16
16
|
|
|
17
|
-
/*
|
|
17
|
+
/* -- Combined Uploads + Trash JSON ------------------------------------------- */
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Fetch the combined uploads + trash listing and render it as JSON into the
|
|
@@ -30,7 +30,7 @@ async function loadUploadsCombined()
|
|
|
30
30
|
} catch (e) { }
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
/*
|
|
33
|
+
/* -- Convenience: DELETE + Show Result --------------------------------------- */
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
36
|
* Issue a DELETE request and display the JSON response.
|
|
@@ -46,7 +46,7 @@ async function deleteAndShow(path, container)
|
|
|
46
46
|
try { await loadUploadsCombined(); } catch (e) { showJsonResult(container, j); }
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
/*
|
|
49
|
+
/* -- Trash Row Factory ------------------------------------------------------- */
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* Build a trash-row DOM node with Restore and Delete Permanently buttons.
|
|
@@ -90,7 +90,7 @@ function createTrashRow(name)
|
|
|
90
90
|
return row;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
/*
|
|
93
|
+
/* -- Trash List -------------------------------------------------------------- */
|
|
94
94
|
|
|
95
95
|
/**
|
|
96
96
|
* Fetch the trash listing from the server and render rows into `#trashList`.
|
|
@@ -125,7 +125,7 @@ function addTrashRow(name)
|
|
|
125
125
|
else trashList.appendChild(row);
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
/*
|
|
128
|
+
/* -- Undo Toast -------------------------------------------------------------- */
|
|
129
129
|
|
|
130
130
|
/**
|
|
131
131
|
* Display a transient undo panel when a file is moved to trash.
|
|
@@ -133,35 +133,50 @@ function addTrashRow(name)
|
|
|
133
133
|
*/
|
|
134
134
|
function showUndo(name)
|
|
135
135
|
{
|
|
136
|
+
/* Remove any existing undo toast first */
|
|
137
|
+
const prev = document.querySelector('.undo-toast');
|
|
138
|
+
if (prev) prev.remove();
|
|
139
|
+
|
|
136
140
|
const box = document.createElement('div');
|
|
137
|
-
box.className = '
|
|
138
|
-
box.textContent = `Trashed ${name}
|
|
141
|
+
box.className = 'undo-toast';
|
|
142
|
+
box.textContent = `Trashed ${name} \u2014 `;
|
|
139
143
|
|
|
140
144
|
const btn = document.createElement('button');
|
|
141
145
|
btn.textContent = 'Undo';
|
|
142
146
|
btn.className = 'btn';
|
|
143
147
|
box.appendChild(btn);
|
|
144
148
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
149
|
+
const close = document.createElement('button');
|
|
150
|
+
close.className = 'undo-toast-close';
|
|
151
|
+
close.innerHTML = '×';
|
|
152
|
+
close.setAttribute('aria-label', 'Dismiss');
|
|
153
|
+
box.appendChild(close);
|
|
154
|
+
|
|
155
|
+
document.body.appendChild(box);
|
|
156
|
+
|
|
157
|
+
const dismiss = () => { clearTimeout(tid); box.remove(); document.removeEventListener('click', outsideClick); };
|
|
158
|
+
|
|
159
|
+
const tid = setTimeout(dismiss, 8000);
|
|
160
|
+
|
|
161
|
+
close.addEventListener('click', (e) => { e.stopPropagation(); dismiss(); });
|
|
150
162
|
|
|
151
|
-
|
|
163
|
+
/* Click outside to dismiss */
|
|
164
|
+
function outsideClick(e) { if (!box.contains(e.target)) dismiss(); }
|
|
165
|
+
/* Delay listener so the current click doesn't immediately close it */
|
|
166
|
+
setTimeout(() => document.addEventListener('click', outsideClick), 0);
|
|
152
167
|
|
|
153
|
-
btn.addEventListener('click', async () =>
|
|
168
|
+
btn.addEventListener('click', async (e) =>
|
|
154
169
|
{
|
|
155
|
-
|
|
170
|
+
e.stopPropagation();
|
|
171
|
+
dismiss();
|
|
156
172
|
await fetch('/uploads/' + encodeURIComponent(name) + '/restore', { method: 'POST' });
|
|
157
|
-
box.remove();
|
|
158
173
|
try { await loadUploadsCombined(); } catch (e) { }
|
|
159
174
|
loadUploadsList();
|
|
160
175
|
loadTrashList();
|
|
161
176
|
});
|
|
162
177
|
}
|
|
163
178
|
|
|
164
|
-
/*
|
|
179
|
+
/* -- Upload Card Factory ----------------------------------------------------- */
|
|
165
180
|
|
|
166
181
|
/** Inline SVG placeholder for non-image files. */
|
|
167
182
|
const PLACEHOLDER_SVG = 'data:image/svg+xml;utf8,' + encodeURIComponent(
|
|
@@ -241,7 +256,7 @@ function createUploadCard(f, uploadResult)
|
|
|
241
256
|
return card;
|
|
242
257
|
}
|
|
243
258
|
|
|
244
|
-
/*
|
|
259
|
+
/* -- Paginated Upload List --------------------------------------------------- */
|
|
245
260
|
|
|
246
261
|
/**
|
|
247
262
|
* Fetch the paginated upload list and render file cards into `#uploadsList`.
|
|
@@ -310,7 +325,7 @@ async function loadUploadsList()
|
|
|
310
325
|
}
|
|
311
326
|
}
|
|
312
327
|
|
|
313
|
-
/*
|
|
328
|
+
/* -- Wire Upload Form & Bulk Actions ----------------------------------------- */
|
|
314
329
|
|
|
315
330
|
/**
|
|
316
331
|
* Initialise the upload form (XHR with progress), pagination controls, and
|
|
@@ -188,24 +188,29 @@ details.acc[open] > summary:before{transform:rotate(90deg)}
|
|
|
188
188
|
.tabs-content{display:block}
|
|
189
189
|
.tab-panel{display:none;padding:6px 2px;transition:opacity .18s ease}
|
|
190
190
|
.tab-panel.active{display:block}
|
|
191
|
-
.feature-grid{display:grid;grid-template-columns:repeat(
|
|
192
|
-
.feature-card{background:linear-gradient(180deg,rgba(255,255,255,0.02),transparent);padding:
|
|
193
|
-
.feature-card:hover{transform:translateY(-
|
|
194
|
-
.feature-icon{width:
|
|
195
|
-
.feature-icon svg{width:
|
|
196
|
-
.feature-icon img.icon-svg{width:
|
|
197
|
-
|
|
198
|
-
.feature-
|
|
199
|
-
|
|
200
|
-
.feature-
|
|
201
|
-
.
|
|
202
|
-
.
|
|
203
|
-
.
|
|
204
|
-
.
|
|
205
|
-
.
|
|
206
|
-
.
|
|
207
|
-
|
|
208
|
-
|
|
191
|
+
.feature-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:8px}
|
|
192
|
+
.feature-card{background:linear-gradient(180deg,rgba(255,255,255,0.02),transparent);padding:10px 12px;border-radius:10px;border:1px solid rgba(255,255,255,0.03);display:flex;flex-direction:row;align-items:center;gap:10px;transition:transform .12s ease,box-shadow .12s ease}
|
|
193
|
+
.feature-card:hover{transform:translateY(-3px);box-shadow:0 8px 24px rgba(0,0,0,0.5)}
|
|
194
|
+
.feature-icon{width:40px;height:40px;min-width:40px;border-radius:10px;background:linear-gradient(180deg, rgba(88,101,242,0.12), rgba(31,142,253,0.06));display:grid;place-items:center;font-size:16px;color:var(--accent);}
|
|
195
|
+
.feature-icon svg{width:26px;height:26px;display:block;fill:var(--accent);}
|
|
196
|
+
.feature-icon img.icon-svg{width:30px;height:30px;display:block;object-fit:contain;margin:0 auto}
|
|
197
|
+
.feature-icon img.icon-svg[src$="/static.svg"], .feature-icon img.icon-svg[src$="static.svg"]{transform:translateY(1px);display:block}
|
|
198
|
+
.feature-card h5{margin:0;font-size:13px;line-height:1.2}
|
|
199
|
+
.feature-card p{margin:0;color:var(--muted);font-size:12px;line-height:1.3}
|
|
200
|
+
.feature-text{display:flex;flex-direction:column;gap:2px;min-width:0}
|
|
201
|
+
.pipeline{display:grid;grid-template-columns:1fr auto 1fr auto 1fr auto 1fr;align-items:start;gap:6px}
|
|
202
|
+
.pipe-stage{text-align:center;padding:16px 10px;border-radius:12px;background:linear-gradient(180deg,rgba(255,255,255,0.025),transparent);border:1px solid rgba(255,255,255,0.04);transition:transform .12s ease,box-shadow .12s ease}
|
|
203
|
+
.pipe-stage:hover{transform:translateY(-3px);box-shadow:0 12px 28px rgba(0,0,0,0.5)}
|
|
204
|
+
.pipe-icon{width:46px;height:46px;border-radius:13px;background:linear-gradient(135deg,var(--accent),var(--accent-2));display:flex;align-items:center;justify-content:center;margin:0 auto 10px;box-shadow:0 6px 16px rgba(88,101,242,0.15)}
|
|
205
|
+
.pipe-icon svg{width:22px;height:22px;fill:none;stroke:#fff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
|
|
206
|
+
.pipe-stage h5{margin:0 0 5px;font-size:13px}
|
|
207
|
+
.pipe-stage p{margin:0;color:var(--muted);font-size:12px;line-height:1.45}
|
|
208
|
+
.pipe-arrow{display:flex;align-items:center;padding-top:18px}
|
|
209
|
+
@keyframes flowPulse{0%,100%{opacity:0.25}50%{opacity:0.55}}
|
|
210
|
+
.pipe-arrow svg{width:22px;height:22px;fill:none;stroke:var(--accent);stroke-width:2;stroke-linecap:round;stroke-linejoin:round;animation:flowPulse 2.5s ease-in-out infinite;animation-delay:calc(var(--i,0) * 0.5s)}
|
|
211
|
+
|
|
212
|
+
@media(max-width:900px){.feature-grid{grid-template-columns:repeat(auto-fill,minmax(180px,1fr))}.tabs-nav{flex-wrap:wrap}.pipeline{grid-template-columns:1fr;justify-items:center;gap:2px}.pipe-stage{max-width:380px;width:100%}.pipe-arrow{padding:2px 0}.pipe-arrow svg{transform:rotate(90deg)}}
|
|
213
|
+
@media(max-width:500px){.feature-grid{grid-template-columns:1fr}}
|
|
209
214
|
|
|
210
215
|
@media(max-width:900px){.playgrid{grid-template-columns:1fr}.ui-shell{padding:16px}}
|
|
211
216
|
|
|
@@ -270,8 +275,8 @@ details.acc[open] > summary:before{transform:rotate(90deg)}
|
|
|
270
275
|
body.toc-open .toc-sidebar{opacity:1;transform:translateX(0);pointer-events:auto}
|
|
271
276
|
|
|
272
277
|
/* TOC icon toolbar */
|
|
273
|
-
.toc-toolbar{display:flex;gap:6px;margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid rgba(255,255,255,0.04)}
|
|
274
|
-
.toc-tool-btn{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:8px;border:1px solid rgba(255,255,255,0.04);background:transparent;color:var(--muted);cursor:pointer;transition:color .12s ease,background .12s ease,transform .12s ease}
|
|
278
|
+
.toc-toolbar{display:flex;align-items:center;gap:6px;margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid rgba(255,255,255,0.04)}
|
|
279
|
+
.toc-tool-btn{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;flex-shrink:0;border-radius:8px;border:1px solid rgba(255,255,255,0.04);background:transparent;color:var(--muted);cursor:pointer;transition:color .12s ease,background .12s ease,transform .12s ease}
|
|
275
280
|
.toc-tool-btn:hover{color:var(--accent);background:rgba(88,101,242,0.08)}
|
|
276
281
|
.toc-tool-btn:active{transform:scale(0.92)}
|
|
277
282
|
.toc-tool-btn.acc-expanded svg{transform:rotate(180deg);transition:transform .18s ease}
|
|
@@ -284,6 +289,21 @@ body.toc-open .toc-sidebar{opacity:1;transform:translateX(0);pointer-events:auto
|
|
|
284
289
|
.toc-sidebar nav li.toc-sub-item a:hover{color:var(--text);background:linear-gradient(180deg,rgba(255,255,255,0.01),transparent)}
|
|
285
290
|
.toc-sidebar nav li.toc-sub-item a:visited{color:rgba(255,255,255,0.6)}
|
|
286
291
|
|
|
292
|
+
/* TOC search filter (inline in toolbar) */
|
|
293
|
+
.toc-search-wrap{position:relative;flex:1;min-width:0}
|
|
294
|
+
.toc-search-icon{position:absolute;left:8px;top:50%;transform:translateY(-50%);color:var(--muted);pointer-events:none}
|
|
295
|
+
.toc-search{width:100%;box-sizing:border-box;padding:6px 8px 6px 26px;border-radius:8px;border:1px solid rgba(255,255,255,0.04);background:rgba(255,255,255,0.02);color:var(--text);font-size:12px;outline:none;transition:border-color .15s ease,box-shadow .15s ease;height:32px}
|
|
296
|
+
.toc-search::placeholder{color:rgba(255,255,255,0.3)}
|
|
297
|
+
.toc-search:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(88,101,242,0.12)}
|
|
298
|
+
|
|
299
|
+
/* Collapsible TOC categories */
|
|
300
|
+
.toc-collapsible{position:relative;padding-left:20px !important}
|
|
301
|
+
.toc-collapse-btn{position:absolute;left:2px;top:12px;width:18px;height:18px;display:inline-flex;align-items:center;justify-content:center;background:transparent;border:none;color:var(--muted);cursor:pointer;padding:0;border-radius:4px;transition:color .12s ease,background .12s ease}
|
|
302
|
+
.toc-collapse-btn:hover{color:var(--accent);background:rgba(88,101,242,0.08)}
|
|
303
|
+
.toc-collapse-btn svg{transition:transform .18s ease}
|
|
304
|
+
.toc-collapsible:not(.toc-collapsed) > .toc-collapse-btn svg{transform:rotate(90deg)}
|
|
305
|
+
.toc-collapsible.toc-collapsed > .toc-sub{display:none !important}
|
|
306
|
+
|
|
287
307
|
|
|
288
308
|
@media(min-width:900px){
|
|
289
309
|
.ui-shell{display:flex;align-items:flex-start}
|
|
@@ -297,7 +317,6 @@ body.toc-open .toc-sidebar{opacity:1;transform:translateX(0);pointer-events:auto
|
|
|
297
317
|
.ui-main{margin-left:0}
|
|
298
318
|
}
|
|
299
319
|
|
|
300
|
-
/* Keep the hamburger always accessible on small screens */
|
|
301
320
|
@media(max-width:640px){
|
|
302
321
|
:root{--header-height:56px}
|
|
303
322
|
.ui-header{width:calc(100% - 16px);padding:8px 12px;border-radius:0}
|
|
@@ -305,6 +324,10 @@ body.toc-open .toc-sidebar{opacity:1;transform:translateX(0);pointer-events:auto
|
|
|
305
324
|
.brand .logo{font-size:18px}
|
|
306
325
|
}
|
|
307
326
|
|
|
308
|
-
/* Ensure page content is not hidden behind the fixed header */
|
|
309
327
|
body{padding-top:calc(var(--header-height) + 12px)}
|
|
310
|
-
.ui-shell{margin-top:0}
|
|
328
|
+
.ui-shell{margin-top:0}
|
|
329
|
+
|
|
330
|
+
.undo-toast{position:fixed;top:calc(var(--header-height) + 12px);left:50%;transform:translateX(-50%);z-index:200;background:var(--surface, #161a22);border:1px solid var(--surface-border, rgba(255,255,255,0.06));color:var(--text, #e2e8f0);padding:10px 18px;border-radius:10px;font-size:14px;box-shadow:0 6px 20px rgba(0,0,0,0.5);display:flex;align-items:center;gap:10px;animation:toast-in .25s ease}
|
|
331
|
+
@keyframes toast-in{from{opacity:0;transform:translateX(-50%) translateY(-10px)}to{opacity:1;transform:translateX(-50%) translateY(0)}}
|
|
332
|
+
.undo-toast-close{background:none;border:none;color:var(--text, #e2e8f0);font-size:18px;line-height:1;cursor:pointer;padding:0 0 0 4px;opacity:.5;transition:opacity .15s}
|
|
333
|
+
.undo-toast-close:hover{opacity:1}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" aria-hidden="true">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="g-comp" x1="0" x2="1">
|
|
4
|
+
<stop offset="0" stop-color="#7b61ff"/>
|
|
5
|
+
<stop offset="1" stop-color="#3ec6ff"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect width="64" height="64" rx="8" fill="transparent"/>
|
|
9
|
+
<!-- Outer large container (uncompressed) -->
|
|
10
|
+
<rect x="6" y="8" width="24" height="48" rx="5" fill="url(#g-comp)" opacity="0.3"/>
|
|
11
|
+
<!-- Inward arrows implying compression -->
|
|
12
|
+
<path d="M6 20h8" stroke="url(#g-comp)" stroke-width="2" stroke-linecap="round" opacity="0.6"/>
|
|
13
|
+
<path d="M30 20h-8" stroke="url(#g-comp)" stroke-width="2" stroke-linecap="round" opacity="0.6"/>
|
|
14
|
+
<path d="M6 32h8" stroke="url(#g-comp)" stroke-width="2" stroke-linecap="round" opacity="0.6"/>
|
|
15
|
+
<path d="M30 32h-8" stroke="url(#g-comp)" stroke-width="2" stroke-linecap="round" opacity="0.6"/>
|
|
16
|
+
<path d="M6 44h8" stroke="url(#g-comp)" stroke-width="2" stroke-linecap="round" opacity="0.6"/>
|
|
17
|
+
<path d="M30 44h-8" stroke="url(#g-comp)" stroke-width="2" stroke-linecap="round" opacity="0.6"/>
|
|
18
|
+
<!-- Arrow pointing to compressed result -->
|
|
19
|
+
<path d="M34 32h6" stroke="url(#g-comp)" stroke-width="2.5" stroke-linecap="round"/>
|
|
20
|
+
<path d="M38 28l4 4-4 4" fill="none" stroke="url(#g-comp)" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
21
|
+
<!-- Compact compressed block -->
|
|
22
|
+
<rect x="44" y="20" width="14" height="24" rx="4" fill="url(#g-comp)"/>
|
|
23
|
+
<!-- White highlight lines on compressed block -->
|
|
24
|
+
<line x1="48" y1="27" x2="54" y2="27" stroke="rgba(255,255,255,0.8)" stroke-width="1.5" stroke-linecap="round"/>
|
|
25
|
+
<line x1="48" y1="32" x2="54" y2="32" stroke="rgba(255,255,255,0.55)" stroke-width="1.5" stroke-linecap="round"/>
|
|
26
|
+
<line x1="48" y1="37" x2="52" y2="37" stroke="rgba(255,255,255,0.35)" stroke-width="1.5" stroke-linecap="round"/>
|
|
27
|
+
</svg>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" aria-hidden="true">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="g-tls" x1="0" x2="1">
|
|
4
|
+
<stop offset="0" stop-color="#7b61ff"/>
|
|
5
|
+
<stop offset="1" stop-color="#3ec6ff"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect width="64" height="64" rx="8" fill="transparent"/>
|
|
9
|
+
<!-- Shield shape -->
|
|
10
|
+
<path d="M32 6 L52 16 L52 34 C52 48 32 58 32 58 C32 58 12 48 12 34 L12 16 Z" fill="url(#g-tls)"/>
|
|
11
|
+
<!-- Lock body -->
|
|
12
|
+
<rect x="24" y="30" width="16" height="14" rx="3" fill="rgba(255,255,255,0.9)"/>
|
|
13
|
+
<!-- Lock shackle -->
|
|
14
|
+
<path d="M27 30 V24 C27 19 37 19 37 24 V30" fill="none" stroke="rgba(255,255,255,0.9)" stroke-width="3" stroke-linecap="round"/>
|
|
15
|
+
<!-- Keyhole -->
|
|
16
|
+
<circle cx="32" cy="36" r="2.5" fill="url(#g-tls)"/>
|
|
17
|
+
<rect x="31" y="36" width="2" height="4" rx="1" fill="url(#g-tls)"/>
|
|
18
|
+
</svg>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<svg width="512" height="512" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
4
|
+
<stop offset="0%" stop-color="#00c6ff"></stop>
|
|
5
|
+
<stop offset="100%" stop-color="#0072ff"></stop>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
|
|
9
|
+
<rect width="512" height="512" rx="90" fill="#0f172a"></rect>
|
|
10
|
+
|
|
11
|
+
<circle cx="256" cy="230" r="110" fill="none" stroke="url(#grad)" stroke-width="28"></circle>
|
|
12
|
+
|
|
13
|
+
<line x1="256" y1="120" x2="256" y2="340" stroke="#0f172a" stroke-width="28"></line>
|
|
14
|
+
|
|
15
|
+
<rect x="180" y="190" width="152" height="80" rx="16" fill="#0f172a"></rect>
|
|
16
|
+
|
|
17
|
+
<text x="256" y="245" text-anchor="middle" font-family="Segoe UI, Arial, sans-serif" font-size="42" font-weight="700" fill="url(#grad)">
|
|
18
|
+
HTTP
|
|
19
|
+
</text>
|
|
20
|
+
|
|
21
|
+
<text x="256" y="430" text-anchor="middle" font-family="Segoe UI, Arial, sans-serif" font-size="44" font-weight="600" fill="white" letter-spacing="4">
|
|
22
|
+
ZERO
|
|
23
|
+
</text>
|
|
24
|
+
</svg>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" aria-hidden="true">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="g-rtr" x1="0" x2="1">
|
|
4
|
+
<stop offset="0" stop-color="#7b61ff"/>
|
|
5
|
+
<stop offset="1" stop-color="#3ec6ff"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect width="64" height="64" rx="8" fill="transparent"/>
|
|
9
|
+
<!-- Central hub node -->
|
|
10
|
+
<circle cx="32" cy="32" r="10" fill="url(#g-rtr)"/>
|
|
11
|
+
<circle cx="32" cy="32" r="4" fill="rgba(255,255,255,0.85)"/>
|
|
12
|
+
<!-- Branch: top-left -->
|
|
13
|
+
<line x1="24" y1="24" x2="14" y2="14" stroke="url(#g-rtr)" stroke-width="2.5" stroke-linecap="round"/>
|
|
14
|
+
<rect x="6" y="6" width="14" height="12" rx="3.5" fill="url(#g-rtr)" opacity="0.7"/>
|
|
15
|
+
<line x1="10" y1="11" x2="16" y2="11" stroke="rgba(255,255,255,0.7)" stroke-width="1.5" stroke-linecap="round"/>
|
|
16
|
+
<line x1="10" y1="14" x2="14" y2="14" stroke="rgba(255,255,255,0.4)" stroke-width="1.5" stroke-linecap="round"/>
|
|
17
|
+
<!-- Branch: top-right -->
|
|
18
|
+
<line x1="40" y1="24" x2="50" y2="14" stroke="url(#g-rtr)" stroke-width="2.5" stroke-linecap="round"/>
|
|
19
|
+
<rect x="44" y="6" width="14" height="12" rx="3.5" fill="url(#g-rtr)" opacity="0.7"/>
|
|
20
|
+
<line x1="48" y1="11" x2="54" y2="11" stroke="rgba(255,255,255,0.7)" stroke-width="1.5" stroke-linecap="round"/>
|
|
21
|
+
<line x1="48" y1="14" x2="52" y2="14" stroke="rgba(255,255,255,0.4)" stroke-width="1.5" stroke-linecap="round"/>
|
|
22
|
+
<!-- Branch: bottom-center -->
|
|
23
|
+
<line x1="32" y1="42" x2="32" y2="50" stroke="url(#g-rtr)" stroke-width="2.5" stroke-linecap="round"/>
|
|
24
|
+
<rect x="22" y="50" width="20" height="10" rx="3.5" fill="url(#g-rtr)" opacity="0.7"/>
|
|
25
|
+
<line x1="26" y1="55" x2="38" y2="55" stroke="rgba(255,255,255,0.7)" stroke-width="1.5" stroke-linecap="round"/>
|
|
26
|
+
<line x1="26" y1="58" x2="34" y2="58" stroke="rgba(255,255,255,0.4)" stroke-width="1.5" stroke-linecap="round"/>
|
|
27
|
+
</svg>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" aria-hidden="true">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="g-sse" x1="0" x2="1">
|
|
4
|
+
<stop offset="0" stop-color="#7b61ff"/>
|
|
5
|
+
<stop offset="1" stop-color="#3ec6ff"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect width="64" height="64" rx="8" fill="transparent"/>
|
|
9
|
+
<!-- Server box -->
|
|
10
|
+
<rect x="8" y="16" width="20" height="32" rx="5" fill="url(#g-sse)"/>
|
|
11
|
+
<!-- White indicator dots on server -->
|
|
12
|
+
<circle cx="18" cy="25" r="2.5" fill="rgba(255,255,255,0.9)"/>
|
|
13
|
+
<circle cx="18" cy="32" r="2.5" fill="rgba(255,255,255,0.55)"/>
|
|
14
|
+
<circle cx="18" cy="39" r="2.5" fill="rgba(255,255,255,0.35)"/>
|
|
15
|
+
<!-- Three streaming arrows flowing right -->
|
|
16
|
+
<line x1="32" y1="24" x2="50" y2="24" stroke="url(#g-sse)" stroke-width="3" stroke-linecap="round"/>
|
|
17
|
+
<path d="M47 20l5 4-5 4" fill="none" stroke="url(#g-sse)" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
18
|
+
<line x1="34" y1="32" x2="52" y2="32" stroke="url(#g-sse)" stroke-width="3" stroke-linecap="round" opacity="0.7"/>
|
|
19
|
+
<path d="M49 28l5 4-5 4" fill="none" stroke="url(#g-sse)" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" opacity="0.7"/>
|
|
20
|
+
<line x1="36" y1="40" x2="54" y2="40" stroke="url(#g-sse)" stroke-width="3" stroke-linecap="round" opacity="0.45"/>
|
|
21
|
+
<path d="M51 36l5 4-5 4" fill="none" stroke="url(#g-sse)" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" opacity="0.45"/>
|
|
22
|
+
</svg>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" aria-hidden="true">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="g-ws" x1="0" x2="1">
|
|
4
|
+
<stop offset="0" stop-color="#7b61ff"/>
|
|
5
|
+
<stop offset="1" stop-color="#3ec6ff"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect width="64" height="64" rx="8" fill="transparent"/>
|
|
9
|
+
<!-- Two speech bubbles overlapping to represent bidirectional real-time messaging -->
|
|
10
|
+
<rect x="6" y="10" width="32" height="22" rx="6" fill="url(#g-ws)"/>
|
|
11
|
+
<polygon points="14,32 20,40 24,32" fill="url(#g-ws)"/>
|
|
12
|
+
<!-- White dots in left bubble -->
|
|
13
|
+
<circle cx="15" cy="21" r="2.5" fill="rgba(255,255,255,0.85)"/>
|
|
14
|
+
<circle cx="22" cy="21" r="2.5" fill="rgba(255,255,255,0.85)"/>
|
|
15
|
+
<circle cx="29" cy="21" r="2.5" fill="rgba(255,255,255,0.85)"/>
|
|
16
|
+
<!-- Second bubble (offset) -->
|
|
17
|
+
<rect x="26" y="28" width="32" height="20" rx="6" fill="url(#g-ws)" opacity="0.75"/>
|
|
18
|
+
<polygon points="44,48 48,55 52,48" fill="url(#g-ws)" opacity="0.75"/>
|
|
19
|
+
<!-- Arrow inside second bubble implying response -->
|
|
20
|
+
<path d="M34 38h16M44 34l6 4-6 4" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
|
21
|
+
</svg>
|
package/index.js
CHANGED
|
@@ -4,12 +4,16 @@
|
|
|
4
4
|
* Re-exports every middleware, the app factory, and the fetch helper.
|
|
5
5
|
*/
|
|
6
6
|
const App = require('./lib/app');
|
|
7
|
-
const
|
|
7
|
+
const Router = require('./lib/router');
|
|
8
|
+
const cors = require('./lib/middleware/cors');
|
|
8
9
|
const fetch = require('./lib/fetch');
|
|
9
10
|
const body = require('./lib/body');
|
|
10
|
-
const serveStatic = require('./lib/static');
|
|
11
|
-
const rateLimit = require('./lib/rateLimit');
|
|
12
|
-
const logger = require('./lib/logger');
|
|
11
|
+
const serveStatic = require('./lib/middleware/static');
|
|
12
|
+
const rateLimit = require('./lib/middleware/rateLimit');
|
|
13
|
+
const logger = require('./lib/middleware/logger');
|
|
14
|
+
const compress = require('./lib/middleware/compress');
|
|
15
|
+
const { WebSocketConnection } = require('./lib/ws');
|
|
16
|
+
const { SSEStream } = require('./lib/sse');
|
|
13
17
|
|
|
14
18
|
module.exports = {
|
|
15
19
|
/**
|
|
@@ -17,6 +21,12 @@ module.exports = {
|
|
|
17
21
|
* @returns {import('./lib/app')} Fresh App with an empty middleware stack.
|
|
18
22
|
*/
|
|
19
23
|
createApp: () => new App(),
|
|
24
|
+
/**
|
|
25
|
+
* Create a standalone Router for modular route grouping.
|
|
26
|
+
* Mount on an App with `app.use('/prefix', router)`.
|
|
27
|
+
* @returns {import('./lib/router')} Fresh Router instance.
|
|
28
|
+
*/
|
|
29
|
+
Router: () => new Router(),
|
|
20
30
|
/** @see module:cors */
|
|
21
31
|
cors,
|
|
22
32
|
/** @see module:fetch */
|
|
@@ -40,4 +50,11 @@ module.exports = {
|
|
|
40
50
|
rateLimit,
|
|
41
51
|
/** @see module:logger */
|
|
42
52
|
logger,
|
|
53
|
+
/** @see module:compress */
|
|
54
|
+
compress,
|
|
55
|
+
// classes (for advanced / direct usage)
|
|
56
|
+
/** @see module:ws/connection */
|
|
57
|
+
WebSocketConnection,
|
|
58
|
+
/** @see module:sse/stream */
|
|
59
|
+
SSEStream,
|
|
43
60
|
};
|