jsgui3-server 0.0.151 → 0.0.152
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 +21 -0
- package/admin-ui/v1/controls/admin_shell.js +33 -0
- package/admin-ui/v1/server.js +14 -1
- package/docs/api-reference.md +120 -2
- package/docs/books/website-design/01-introduction.md +73 -0
- package/docs/books/website-design/02-current-state.md +195 -0
- package/docs/books/website-design/03-base-class.md +181 -0
- package/docs/books/website-design/04-webpage.md +307 -0
- package/docs/books/website-design/05-website.md +456 -0
- package/docs/books/website-design/06-pages-storage.md +170 -0
- package/docs/books/website-design/07-api-layer.md +285 -0
- package/docs/books/website-design/08-server-integration.md +271 -0
- package/docs/books/website-design/09-cross-agent-review.md +190 -0
- package/docs/books/website-design/10-open-questions.md +196 -0
- package/docs/books/website-design/11-converged-recommendation.md +205 -0
- package/docs/books/website-design/12-content-model.md +395 -0
- package/docs/books/website-design/13-webpage-module-spec.md +404 -0
- package/docs/books/website-design/14-website-module-spec.md +541 -0
- package/docs/books/website-design/15-multi-repo-plan.md +275 -0
- package/docs/books/website-design/16-minimal-first.md +203 -0
- package/docs/books/website-design/17-implementation-report-codex.md +81 -0
- package/docs/books/website-design/README.md +43 -0
- package/docs/configuration-reference.md +54 -0
- package/docs/proposals/jsgui3-website-and-webpage-design-jsgui3-server-support.md +257 -0
- package/docs/proposals/jsgui3-website-and-webpage-design-review.md +73 -0
- package/docs/proposals/jsgui3-website-and-webpage-design.md +732 -0
- package/docs/swagger.md +316 -0
- package/examples/controls/1) window/server.js +6 -1
- package/examples/controls/21) mvvm and declarative api/check.js +94 -0
- package/examples/controls/21) mvvm and declarative api/check_output.txt +25 -0
- package/examples/controls/21) mvvm and declarative api/check_output_2.txt +27 -0
- package/examples/controls/21) mvvm and declarative api/client.js +241 -0
- declarative api/e2e-screenshot-1-name-change.png +0 -0
- declarative api/e2e-screenshot-2-toggled.png +0 -0
- declarative api/e2e-screenshot-3-final.png +0 -0
- declarative api/e2e-screenshot-final.png +0 -0
- package/examples/controls/21) mvvm and declarative api/e2e-test.js +175 -0
- package/examples/controls/21) mvvm and declarative api/out.html +1 -0
- package/examples/controls/21) mvvm and declarative api/page_out.html +1 -0
- package/examples/controls/21) mvvm and declarative api/server.js +18 -0
- package/examples/data-views/01) query-endpoint/server.js +61 -0
- package/labs/website-design/001-base-class-overhead/check.js +162 -0
- package/labs/website-design/002-pages-storage/check.js +244 -0
- package/labs/website-design/002-pages-storage/results.txt +0 -0
- package/labs/website-design/003-type-detection/check.js +193 -0
- package/labs/website-design/003-type-detection/results.txt +0 -0
- package/labs/website-design/004-two-stage-validation/check.js +314 -0
- package/labs/website-design/004-two-stage-validation/results.txt +0 -0
- package/labs/website-design/005-normalize-input/check.js +303 -0
- package/labs/website-design/006-serve-website-spike/check.js +290 -0
- package/labs/website-design/README.md +34 -0
- package/labs/website-design/manifest.json +68 -0
- package/labs/website-design/run-all.js +60 -0
- package/middleware/json-body.js +126 -0
- package/openapi.js +474 -0
- package/package.json +11 -8
- package/publishers/Publishers.js +6 -5
- package/publishers/http-function-publisher.js +135 -126
- package/publishers/http-webpage-publisher.js +89 -11
- package/publishers/query-publisher.js +116 -0
- package/publishers/swagger-publisher.js +203 -0
- package/publishers/swagger-ui.js +578 -0
- package/resources/adapters/array-adapter.js +143 -0
- package/resources/query-resource.js +131 -0
- package/serve-factory.js +728 -18
- package/server.js +421 -103
- package/tests/README.md +23 -1
- package/tests/admin-ui-jsgui-controls.test.js +16 -1
- package/tests/helpers/playwright-e2e-harness.js +326 -0
- package/tests/openapi.test.js +319 -0
- package/tests/playwright-smoke.test.js +134 -0
- package/tests/publish-enhancements.test.js +673 -0
- package/tests/query-publisher.test.js +430 -0
- package/tests/quick-json-body-test.js +169 -0
- package/tests/serve.test.js +425 -122
- package/tests/swagger-publisher.test.js +1076 -0
- package/tests/test-runner.js +1 -0
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swagger UI HTML Page Generator for jsgui3-server.
|
|
3
|
+
*
|
|
4
|
+
* Produces a self-contained HTML page that loads
|
|
5
|
+
* [Swagger UI](https://github.com/swagger-api/swagger-ui) from the
|
|
6
|
+
* [unpkg CDN](https://unpkg.com/) and points it at the server's own
|
|
7
|
+
* `/api/openapi.json` endpoint.
|
|
8
|
+
*
|
|
9
|
+
* ## Zero Dependencies
|
|
10
|
+
*
|
|
11
|
+
* No npm packages are installed. Both the CSS and JavaScript for
|
|
12
|
+
* Swagger UI are loaded at runtime from the CDN, so no build step
|
|
13
|
+
* or bundling is needed.
|
|
14
|
+
*
|
|
15
|
+
* ## Theming
|
|
16
|
+
*
|
|
17
|
+
* Five built-in colour themes are available, switchable at runtime via a
|
|
18
|
+
* dropdown selector in the Swagger UI topbar. Themes are pure CSS variable
|
|
19
|
+
* swaps using the `[data-theme]` selector on `<html>`.
|
|
20
|
+
*
|
|
21
|
+
* | Theme | Description |
|
|
22
|
+
* |------------------|----------------------------------|
|
|
23
|
+
* | `wlilo` | Deep obsidian/leather/gold dark |
|
|
24
|
+
* | `midnight` | Deep blue-purple dark |
|
|
25
|
+
* | `light` | Clean light mode |
|
|
26
|
+
* | `nord` | Nord-inspired muted palette |
|
|
27
|
+
* | `high-contrast` | WCAG AAA high-contrast dark |
|
|
28
|
+
*
|
|
29
|
+
* Selection is persisted in `localStorage` (key: `swagger-theme`).
|
|
30
|
+
* Custom themes can be passed via `options.themes`.
|
|
31
|
+
*
|
|
32
|
+
* ## CSS Variables
|
|
33
|
+
*
|
|
34
|
+
* All themes set these core variables:
|
|
35
|
+
*
|
|
36
|
+
* | Variable | Purpose |
|
|
37
|
+
* |----------------------|--------------------|
|
|
38
|
+
* | `--swagger-bg` | Page background |
|
|
39
|
+
* | `--swagger-surface` | Card / block bg |
|
|
40
|
+
* | `--swagger-border` | Border colour |
|
|
41
|
+
* | `--swagger-text` | Primary text |
|
|
42
|
+
* | `--swagger-muted` | Secondary text |
|
|
43
|
+
* | `--swagger-accent` | Accent / links |
|
|
44
|
+
* | `--swagger-danger` | Required / errors |
|
|
45
|
+
* | `--swagger-success` | Success indicators |
|
|
46
|
+
* | `--swagger-code-bg` | Code block bg |
|
|
47
|
+
* | `--swagger-code-fg` | Code block text |
|
|
48
|
+
* | `--swagger-input-bg` | Input background |
|
|
49
|
+
*
|
|
50
|
+
* ## CDN Version
|
|
51
|
+
*
|
|
52
|
+
* The major version of Swagger UI loaded from CDN is controlled by the
|
|
53
|
+
* module-level constant `SWAGGER_UI_CDN_VERSION` (currently `'5'`).
|
|
54
|
+
* unpkg resolves this to the latest 5.x release automatically.
|
|
55
|
+
*
|
|
56
|
+
* ## Usage
|
|
57
|
+
*
|
|
58
|
+
* This module is used internally by
|
|
59
|
+
* {@link Swagger_Publisher} and does not normally need to be called directly.
|
|
60
|
+
*
|
|
61
|
+
* ```js
|
|
62
|
+
* const { generate_swagger_html } = require('./publishers/swagger-ui');
|
|
63
|
+
* const html = generate_swagger_html({ title: 'My API' });
|
|
64
|
+
* // → Complete HTML page string
|
|
65
|
+
* ```
|
|
66
|
+
*
|
|
67
|
+
* @module publishers/swagger-ui
|
|
68
|
+
* @see {@link module:openapi} — the spec generator that produces the JSON this page renders.
|
|
69
|
+
* @see {@link module:publishers/swagger-publisher} — the publisher that serves this page.
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
'use strict';
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Major version of Swagger UI to load from unpkg CDN.
|
|
76
|
+
*
|
|
77
|
+
* unpkg resolves `swagger-ui-dist@5` to the latest 5.x.y release.
|
|
78
|
+
* Pin to a specific version (e.g. `'5.17.14'`) if reproducibility
|
|
79
|
+
* is more important than auto-updates.
|
|
80
|
+
*
|
|
81
|
+
* @const {string}
|
|
82
|
+
*/
|
|
83
|
+
const SWAGGER_UI_CDN_VERSION = '5';
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Built-in theme presets.
|
|
87
|
+
*
|
|
88
|
+
* Each theme is a map of CSS custom property values used throughout
|
|
89
|
+
* the Swagger UI page. Themes are activated by setting
|
|
90
|
+
* `document.documentElement.dataset.theme` at runtime.
|
|
91
|
+
*
|
|
92
|
+
* @const {Object<string, {label: string, vars: Object<string, string>}>}
|
|
93
|
+
*/
|
|
94
|
+
const BUILTIN_THEMES = {
|
|
95
|
+
wlilo: {
|
|
96
|
+
label: 'WLILO Dark',
|
|
97
|
+
vars: {
|
|
98
|
+
'--swagger-bg': '#0f1225',
|
|
99
|
+
'--swagger-surface': '#181c35',
|
|
100
|
+
'--swagger-border': '#2a2f52',
|
|
101
|
+
'--swagger-text': '#e0e0f0',
|
|
102
|
+
'--swagger-muted': '#9aa0c8',
|
|
103
|
+
'--swagger-accent': '#c9a84c',
|
|
104
|
+
'--swagger-danger': '#ff6b81',
|
|
105
|
+
'--swagger-success': '#50fa7b',
|
|
106
|
+
'--swagger-code-bg': '#0d0f1e',
|
|
107
|
+
'--swagger-code-fg': '#c9d1d9',
|
|
108
|
+
'--swagger-input-bg': '#0d0f1e',
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
midnight: {
|
|
112
|
+
label: 'Midnight',
|
|
113
|
+
vars: {
|
|
114
|
+
'--swagger-bg': '#0b0e1a',
|
|
115
|
+
'--swagger-surface': '#131729',
|
|
116
|
+
'--swagger-border': '#252a45',
|
|
117
|
+
'--swagger-text': '#d4d8ef',
|
|
118
|
+
'--swagger-muted': '#8b92b8',
|
|
119
|
+
'--swagger-accent': '#4facfe',
|
|
120
|
+
'--swagger-danger': '#ef4444',
|
|
121
|
+
'--swagger-success': '#22c55e',
|
|
122
|
+
'--swagger-code-bg': '#090c16',
|
|
123
|
+
'--swagger-code-fg': '#a5b4d4',
|
|
124
|
+
'--swagger-input-bg': '#090c16',
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
light: {
|
|
128
|
+
label: 'Light',
|
|
129
|
+
vars: {
|
|
130
|
+
'--swagger-bg': '#f8f9fa',
|
|
131
|
+
'--swagger-surface': '#ffffff',
|
|
132
|
+
'--swagger-border': '#dee2e6',
|
|
133
|
+
'--swagger-text': '#212529',
|
|
134
|
+
'--swagger-muted': '#6c757d',
|
|
135
|
+
'--swagger-accent': '#0d6efd',
|
|
136
|
+
'--swagger-danger': '#dc3545',
|
|
137
|
+
'--swagger-success': '#198754',
|
|
138
|
+
'--swagger-code-bg': '#f1f3f5',
|
|
139
|
+
'--swagger-code-fg': '#212529',
|
|
140
|
+
'--swagger-input-bg': '#ffffff',
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
nord: {
|
|
144
|
+
label: 'Nord',
|
|
145
|
+
vars: {
|
|
146
|
+
'--swagger-bg': '#2e3440',
|
|
147
|
+
'--swagger-surface': '#3b4252',
|
|
148
|
+
'--swagger-border': '#4c566a',
|
|
149
|
+
'--swagger-text': '#eceff4',
|
|
150
|
+
'--swagger-muted': '#d8dee9',
|
|
151
|
+
'--swagger-accent': '#88c0d0',
|
|
152
|
+
'--swagger-danger': '#bf616a',
|
|
153
|
+
'--swagger-success': '#a3be8c',
|
|
154
|
+
'--swagger-code-bg': '#2e3440',
|
|
155
|
+
'--swagger-code-fg': '#e5e9f0',
|
|
156
|
+
'--swagger-input-bg': '#2e3440',
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
'high-contrast': {
|
|
160
|
+
label: 'High Contrast',
|
|
161
|
+
vars: {
|
|
162
|
+
'--swagger-bg': '#000000',
|
|
163
|
+
'--swagger-surface': '#1a1a1a',
|
|
164
|
+
'--swagger-border': '#ffffff',
|
|
165
|
+
'--swagger-text': '#ffffff',
|
|
166
|
+
'--swagger-muted': '#e0e0e0',
|
|
167
|
+
'--swagger-accent': '#ffff00',
|
|
168
|
+
'--swagger-danger': '#ff4444',
|
|
169
|
+
'--swagger-success': '#00ff00',
|
|
170
|
+
'--swagger-code-bg': '#0a0a0a',
|
|
171
|
+
'--swagger-code-fg': '#ffffff',
|
|
172
|
+
'--swagger-input-bg': '#1a1a1a',
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Build CSS blocks for all themes.
|
|
179
|
+
*
|
|
180
|
+
* Each theme generates a `[data-theme="name"]` selector block that sets
|
|
181
|
+
* the CSS custom properties for that theme.
|
|
182
|
+
*
|
|
183
|
+
* @param {Object} themes - Theme definitions (same shape as BUILTIN_THEMES).
|
|
184
|
+
* @returns {string} CSS text.
|
|
185
|
+
* @private
|
|
186
|
+
*/
|
|
187
|
+
const build_theme_css = (themes) => {
|
|
188
|
+
const blocks = [];
|
|
189
|
+
for (const [name, theme] of Object.entries(themes)) {
|
|
190
|
+
const vars = Object.entries(theme.vars)
|
|
191
|
+
.map(([k, v]) => ` ${k}: ${v};`)
|
|
192
|
+
.join('\n');
|
|
193
|
+
blocks.push(` [data-theme="${name}"] {\n${vars}\n }`);
|
|
194
|
+
}
|
|
195
|
+
return blocks.join('\n');
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Build the JavaScript for the theme selector widget.
|
|
200
|
+
*
|
|
201
|
+
* Injects a `<select>` into the Swagger UI topbar after it renders.
|
|
202
|
+
* Persists selection to `localStorage`.
|
|
203
|
+
*
|
|
204
|
+
* @param {Object} themes - Theme definitions.
|
|
205
|
+
* @param {string} default_theme - Default theme name.
|
|
206
|
+
* @returns {string} JavaScript source.
|
|
207
|
+
* @private
|
|
208
|
+
*/
|
|
209
|
+
const build_theme_js = (themes, default_theme) => {
|
|
210
|
+
const options_arr = Object.entries(themes).map(([name, t]) =>
|
|
211
|
+
`{ value: ${JSON.stringify(name)}, label: ${JSON.stringify(t.label)} }`
|
|
212
|
+
);
|
|
213
|
+
return `
|
|
214
|
+
// ── Theme Selector ──
|
|
215
|
+
(function() {
|
|
216
|
+
var STORAGE_KEY = 'swagger-theme';
|
|
217
|
+
var DEFAULT_THEME = ${JSON.stringify(default_theme)};
|
|
218
|
+
var themes = [${options_arr.join(', ')}];
|
|
219
|
+
|
|
220
|
+
function applyTheme(name) {
|
|
221
|
+
document.documentElement.setAttribute('data-theme', name);
|
|
222
|
+
try { localStorage.setItem(STORAGE_KEY, name); } catch(e) {}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Apply saved or default theme immediately.
|
|
226
|
+
var saved = null;
|
|
227
|
+
try { saved = localStorage.getItem(STORAGE_KEY); } catch(e) {}
|
|
228
|
+
applyTheme(saved || DEFAULT_THEME);
|
|
229
|
+
|
|
230
|
+
// Wait for Swagger UI to render, then inject the selector.
|
|
231
|
+
var attempts = 0;
|
|
232
|
+
var interval = setInterval(function() {
|
|
233
|
+
attempts++;
|
|
234
|
+
var topbar = document.querySelector('.swagger-ui .topbar-wrapper');
|
|
235
|
+
if (!topbar && attempts < 50) return;
|
|
236
|
+
clearInterval(interval);
|
|
237
|
+
if (!topbar) return;
|
|
238
|
+
|
|
239
|
+
var wrapper = document.createElement('div');
|
|
240
|
+
wrapper.className = 'theme-selector';
|
|
241
|
+
wrapper.style.cssText = 'display:flex;align-items:center;gap:8px;margin-left:auto;padding-right:12px;';
|
|
242
|
+
|
|
243
|
+
var label = document.createElement('span');
|
|
244
|
+
label.textContent = 'Theme';
|
|
245
|
+
label.style.cssText = 'font-size:12px;text-transform:uppercase;letter-spacing:0.08em;opacity:0.7;color:var(--swagger-text);';
|
|
246
|
+
|
|
247
|
+
var select = document.createElement('select');
|
|
248
|
+
select.id = 'theme-select';
|
|
249
|
+
select.style.cssText = 'background:var(--swagger-input-bg);color:var(--swagger-text);border:1px solid var(--swagger-border);border-radius:6px;padding:6px 10px;font-size:13px;cursor:pointer;outline:none;min-width:130px;';
|
|
250
|
+
select.setAttribute('aria-label', 'Select theme');
|
|
251
|
+
|
|
252
|
+
themes.forEach(function(t) {
|
|
253
|
+
var opt = document.createElement('option');
|
|
254
|
+
opt.value = t.value;
|
|
255
|
+
opt.textContent = t.label;
|
|
256
|
+
if (t.value === (saved || DEFAULT_THEME)) opt.selected = true;
|
|
257
|
+
select.appendChild(opt);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
select.addEventListener('change', function() {
|
|
261
|
+
applyTheme(this.value);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
wrapper.appendChild(label);
|
|
265
|
+
wrapper.appendChild(select);
|
|
266
|
+
topbar.style.display = 'flex';
|
|
267
|
+
topbar.style.alignItems = 'center';
|
|
268
|
+
topbar.appendChild(wrapper);
|
|
269
|
+
}, 100);
|
|
270
|
+
})();`;
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Generate a self-contained Swagger UI HTML page with theme support.
|
|
275
|
+
*
|
|
276
|
+
* The returned string is a complete `<!doctype html>` document that
|
|
277
|
+
* can be served directly as `text/html`. It:
|
|
278
|
+
*
|
|
279
|
+
* 1. Loads Swagger UI CSS + JS from unpkg CDN.
|
|
280
|
+
* 2. Initialises `SwaggerUIBundle` pointed at `spec_url`.
|
|
281
|
+
* 3. Provides 5 built-in colour themes (default: WLILO dark).
|
|
282
|
+
* 4. Includes a runtime theme selector in the topbar.
|
|
283
|
+
* 5. Persists theme choice to `localStorage`.
|
|
284
|
+
* 6. Replaces the default Swagger logo with "jsgui3 API Docs".
|
|
285
|
+
*
|
|
286
|
+
* @param {Object} [options] - Configuration options.
|
|
287
|
+
* @param {string} [options.spec_url='/api/openapi.json'] - URL of the OpenAPI spec.
|
|
288
|
+
* @param {string} [options.title='API Documentation'] - HTML page title.
|
|
289
|
+
* @param {string} [options.default_theme='wlilo'] - Default theme name.
|
|
290
|
+
* @param {Object} [options.themes] - Additional custom themes
|
|
291
|
+
* in the same `{ name: { label, vars } }` format as BUILTIN_THEMES.
|
|
292
|
+
* Custom themes are merged with (and can override) built-in themes.
|
|
293
|
+
* @returns {string} Complete HTML page as a string.
|
|
294
|
+
*
|
|
295
|
+
* @example
|
|
296
|
+
* // Default usage (WLILO dark theme, points at /api/openapi.json):
|
|
297
|
+
* const html = generate_swagger_html();
|
|
298
|
+
*
|
|
299
|
+
* @example
|
|
300
|
+
* // Custom title and light theme default:
|
|
301
|
+
* const html = generate_swagger_html({
|
|
302
|
+
* title: 'My Service API',
|
|
303
|
+
* default_theme: 'light'
|
|
304
|
+
* });
|
|
305
|
+
*
|
|
306
|
+
* @example
|
|
307
|
+
* // Add a custom theme:
|
|
308
|
+
* const html = generate_swagger_html({
|
|
309
|
+
* themes: {
|
|
310
|
+
* ocean: {
|
|
311
|
+
* label: 'Ocean Blue',
|
|
312
|
+
* vars: {
|
|
313
|
+
* '--swagger-bg': '#0a192f',
|
|
314
|
+
* '--swagger-surface': '#112240',
|
|
315
|
+
* '--swagger-border': '#233554',
|
|
316
|
+
* '--swagger-text': '#ccd6f6',
|
|
317
|
+
* '--swagger-muted': '#8892b0',
|
|
318
|
+
* '--swagger-accent': '#64ffda',
|
|
319
|
+
* '--swagger-danger': '#ff6b6b',
|
|
320
|
+
* '--swagger-success': '#64ffda',
|
|
321
|
+
* '--swagger-code-bg': '#0a192f',
|
|
322
|
+
* '--swagger-code-fg': '#a8b2d1',
|
|
323
|
+
* '--swagger-input-bg': '#0a192f',
|
|
324
|
+
* }
|
|
325
|
+
* }
|
|
326
|
+
* }
|
|
327
|
+
* });
|
|
328
|
+
*/
|
|
329
|
+
const generate_swagger_html = (options = {}) => {
|
|
330
|
+
const spec_url = options.spec_url || '/api/openapi.json';
|
|
331
|
+
const title = options.title || 'API Documentation';
|
|
332
|
+
const default_theme = options.default_theme || 'wlilo';
|
|
333
|
+
|
|
334
|
+
// Merge built-in themes with optional custom themes.
|
|
335
|
+
const all_themes = { ...BUILTIN_THEMES };
|
|
336
|
+
if (options.themes && typeof options.themes === 'object') {
|
|
337
|
+
Object.assign(all_themes, options.themes);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const theme_css = build_theme_css(all_themes);
|
|
341
|
+
const theme_js = build_theme_js(all_themes, default_theme);
|
|
342
|
+
|
|
343
|
+
return `<!doctype html>
|
|
344
|
+
<html lang="en" data-theme="${esc(default_theme)}">
|
|
345
|
+
<head>
|
|
346
|
+
<meta charset="utf-8" />
|
|
347
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
348
|
+
<title>${esc(title)}</title>
|
|
349
|
+
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@${SWAGGER_UI_CDN_VERSION}/swagger-ui.css" />
|
|
350
|
+
<style>
|
|
351
|
+
/* ── Theme Presets ──────────────────────────────────── */
|
|
352
|
+
${theme_css}
|
|
353
|
+
|
|
354
|
+
/* ── Shared Layout ──────────────────────────────────── */
|
|
355
|
+
html, body {
|
|
356
|
+
margin: 0;
|
|
357
|
+
padding: 0;
|
|
358
|
+
background: var(--swagger-bg);
|
|
359
|
+
color: var(--swagger-text);
|
|
360
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
|
361
|
+
Helvetica, Arial, sans-serif;
|
|
362
|
+
transition: background-color 0.3s ease, color 0.3s ease;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/* ── Topbar ─────────────────────────────────────────── */
|
|
366
|
+
.swagger-ui .topbar {
|
|
367
|
+
background: var(--swagger-surface);
|
|
368
|
+
border-bottom: 1px solid var(--swagger-border);
|
|
369
|
+
padding: 8px 0;
|
|
370
|
+
transition: background-color 0.3s ease, border-color 0.3s ease;
|
|
371
|
+
}
|
|
372
|
+
.swagger-ui .topbar .download-url-wrapper {
|
|
373
|
+
display: flex;
|
|
374
|
+
align-items: center;
|
|
375
|
+
}
|
|
376
|
+
.swagger-ui .topbar .download-url-wrapper .download-url-button {
|
|
377
|
+
background: var(--swagger-accent);
|
|
378
|
+
border: 1px solid var(--swagger-accent);
|
|
379
|
+
color: #fff;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/* ── Branding ───────────────────────────────────────── */
|
|
383
|
+
.swagger-ui .topbar-wrapper img[alt="Swagger UI"] { display: none; }
|
|
384
|
+
.swagger-ui .topbar-wrapper::before {
|
|
385
|
+
content: 'jsgui3 API Docs';
|
|
386
|
+
color: var(--swagger-accent);
|
|
387
|
+
font-size: 1.1rem;
|
|
388
|
+
font-weight: 600;
|
|
389
|
+
letter-spacing: 0.02em;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/* ── Info Section ───────────────────────────────────── */
|
|
393
|
+
.swagger-ui .info .title { color: var(--swagger-text); }
|
|
394
|
+
.swagger-ui .info p,
|
|
395
|
+
.swagger-ui .info li,
|
|
396
|
+
.swagger-ui .info table { color: var(--swagger-muted); }
|
|
397
|
+
|
|
398
|
+
/* ── Scheme Container ───────────────────────────────── */
|
|
399
|
+
.swagger-ui .scheme-container {
|
|
400
|
+
background: var(--swagger-surface);
|
|
401
|
+
box-shadow: none;
|
|
402
|
+
border: 1px solid var(--swagger-border);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/* ── Operations ─────────────────────────────────────── */
|
|
406
|
+
.swagger-ui .opblock-tag {
|
|
407
|
+
color: var(--swagger-text);
|
|
408
|
+
border-bottom-color: var(--swagger-border);
|
|
409
|
+
}
|
|
410
|
+
.swagger-ui .opblock {
|
|
411
|
+
border-color: var(--swagger-border);
|
|
412
|
+
background: var(--swagger-surface);
|
|
413
|
+
transition: background-color 0.2s ease;
|
|
414
|
+
}
|
|
415
|
+
.swagger-ui .opblock .opblock-summary {
|
|
416
|
+
border-color: var(--swagger-border);
|
|
417
|
+
}
|
|
418
|
+
.swagger-ui .opblock .opblock-summary-description {
|
|
419
|
+
color: var(--swagger-muted);
|
|
420
|
+
}
|
|
421
|
+
.swagger-ui .opblock-description-wrapper p,
|
|
422
|
+
.swagger-ui .opblock-external-docs-wrapper p,
|
|
423
|
+
.swagger-ui .opblock-title_normal p {
|
|
424
|
+
color: var(--swagger-muted);
|
|
425
|
+
}
|
|
426
|
+
.swagger-ui .opblock .opblock-section-header {
|
|
427
|
+
background: rgba(79, 172, 254, 0.06);
|
|
428
|
+
box-shadow: none;
|
|
429
|
+
}
|
|
430
|
+
.swagger-ui .opblock .opblock-section-header h4 {
|
|
431
|
+
color: var(--swagger-text);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/* ── Deprecated strike-through + muted ──────────────── */
|
|
435
|
+
.swagger-ui .opblock-deprecated {
|
|
436
|
+
opacity: 0.6;
|
|
437
|
+
border-color: var(--swagger-danger);
|
|
438
|
+
}
|
|
439
|
+
.swagger-ui .opblock-deprecated .opblock-summary-method {
|
|
440
|
+
background: var(--swagger-danger);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/* ── Tables ─────────────────────────────────────────── */
|
|
444
|
+
.swagger-ui table thead tr td,
|
|
445
|
+
.swagger-ui table thead tr th {
|
|
446
|
+
color: var(--swagger-text);
|
|
447
|
+
border-bottom-color: var(--swagger-border);
|
|
448
|
+
}
|
|
449
|
+
.swagger-ui .parameter__name,
|
|
450
|
+
.swagger-ui .parameter__type {
|
|
451
|
+
color: var(--swagger-muted);
|
|
452
|
+
}
|
|
453
|
+
.swagger-ui .parameter__name.required::after {
|
|
454
|
+
color: var(--swagger-danger);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/* ── Responses ──────────────────────────────────────── */
|
|
458
|
+
.swagger-ui .response-col_status {
|
|
459
|
+
color: var(--swagger-text);
|
|
460
|
+
}
|
|
461
|
+
.swagger-ui .response-col_description__inner p {
|
|
462
|
+
color: var(--swagger-muted);
|
|
463
|
+
}
|
|
464
|
+
.swagger-ui .responses-inner h4,
|
|
465
|
+
.swagger-ui .responses-inner h5 {
|
|
466
|
+
color: var(--swagger-text);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/* ── Models ─────────────────────────────────────────── */
|
|
470
|
+
.swagger-ui .model-title { color: var(--swagger-text); }
|
|
471
|
+
.swagger-ui .model { color: var(--swagger-muted); }
|
|
472
|
+
|
|
473
|
+
/* ── Form Elements ──────────────────────────────────── */
|
|
474
|
+
.swagger-ui select {
|
|
475
|
+
background: var(--swagger-input-bg);
|
|
476
|
+
color: var(--swagger-text);
|
|
477
|
+
border-color: var(--swagger-border);
|
|
478
|
+
}
|
|
479
|
+
.swagger-ui input[type=text],
|
|
480
|
+
.swagger-ui textarea {
|
|
481
|
+
background: var(--swagger-input-bg);
|
|
482
|
+
color: var(--swagger-text);
|
|
483
|
+
border-color: var(--swagger-border);
|
|
484
|
+
}
|
|
485
|
+
.swagger-ui .btn {
|
|
486
|
+
color: var(--swagger-text);
|
|
487
|
+
border-color: var(--swagger-border);
|
|
488
|
+
}
|
|
489
|
+
.swagger-ui .btn.execute {
|
|
490
|
+
background: var(--swagger-accent);
|
|
491
|
+
border-color: var(--swagger-accent);
|
|
492
|
+
color: #fff;
|
|
493
|
+
}
|
|
494
|
+
.swagger-ui .btn.cancel {
|
|
495
|
+
color: var(--swagger-danger);
|
|
496
|
+
border-color: var(--swagger-danger);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/* ── Code Blocks ────────────────────────────────────── */
|
|
500
|
+
.swagger-ui .highlight-code .microlight {
|
|
501
|
+
background: var(--swagger-code-bg);
|
|
502
|
+
color: var(--swagger-code-fg);
|
|
503
|
+
border: 1px solid var(--swagger-border);
|
|
504
|
+
border-radius: 6px;
|
|
505
|
+
}
|
|
506
|
+
.swagger-ui .response .renderedMarkdown code {
|
|
507
|
+
background: var(--swagger-code-bg);
|
|
508
|
+
color: var(--swagger-code-fg);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/* ── Layout ─────────────────────────────────────────── */
|
|
512
|
+
.swagger-ui .wrapper {
|
|
513
|
+
max-width: 1200px;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/* ── Smooth transitions for theme switching ──────────── */
|
|
517
|
+
.swagger-ui .opblock,
|
|
518
|
+
.swagger-ui .scheme-container,
|
|
519
|
+
.swagger-ui .topbar,
|
|
520
|
+
.swagger-ui .model-container {
|
|
521
|
+
transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/* ── Theme Selector widget ──────────────────────────── */
|
|
525
|
+
.theme-selector select:hover {
|
|
526
|
+
border-color: var(--swagger-accent);
|
|
527
|
+
}
|
|
528
|
+
.theme-selector select:focus {
|
|
529
|
+
border-color: var(--swagger-accent);
|
|
530
|
+
box-shadow: 0 0 0 2px rgba(79, 172, 254, 0.25);
|
|
531
|
+
}
|
|
532
|
+
</style>
|
|
533
|
+
</head>
|
|
534
|
+
<body>
|
|
535
|
+
<div id="swagger-ui"></div>
|
|
536
|
+
<script src="https://unpkg.com/swagger-ui-dist@${SWAGGER_UI_CDN_VERSION}/swagger-ui-bundle.js" crossorigin></script>
|
|
537
|
+
<script>
|
|
538
|
+
window.onload = function () {
|
|
539
|
+
SwaggerUIBundle({
|
|
540
|
+
url: ${JSON.stringify(spec_url)},
|
|
541
|
+
dom_id: '#swagger-ui',
|
|
542
|
+
deepLinking: true,
|
|
543
|
+
presets: [
|
|
544
|
+
SwaggerUIBundle.presets.apis,
|
|
545
|
+
SwaggerUIBundle.SwaggerUIStandalonePreset
|
|
546
|
+
],
|
|
547
|
+
layout: 'BaseLayout',
|
|
548
|
+
defaultModelsExpandDepth: 1,
|
|
549
|
+
defaultModelExpandDepth: 2,
|
|
550
|
+
docExpansion: 'list',
|
|
551
|
+
filter: true,
|
|
552
|
+
showExtensions: true,
|
|
553
|
+
showCommonExtensions: true
|
|
554
|
+
});
|
|
555
|
+
${theme_js}
|
|
556
|
+
};
|
|
557
|
+
</script>
|
|
558
|
+
</body>
|
|
559
|
+
</html>`;
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Escape a string for safe inclusion in HTML attributes and text content.
|
|
564
|
+
*
|
|
565
|
+
* Handles the four characters that can break HTML context:
|
|
566
|
+
* `&`, `<`, `>`, and `"`.
|
|
567
|
+
*
|
|
568
|
+
* @param {string} str - The string to escape.
|
|
569
|
+
* @returns {string} HTML-safe string.
|
|
570
|
+
* @private
|
|
571
|
+
*/
|
|
572
|
+
const esc = (str) => String(str)
|
|
573
|
+
.replace(/&/g, '&')
|
|
574
|
+
.replace(/</g, '<')
|
|
575
|
+
.replace(/>/g, '>')
|
|
576
|
+
.replace(/"/g, '"');
|
|
577
|
+
|
|
578
|
+
module.exports = { generate_swagger_html, BUILTIN_THEMES };
|