@prauga/flexdoc-backend 2.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 +227 -0
- package/dist/flexdoc.module.d.ts +14 -0
- package/dist/flexdoc.module.js +77 -0
- package/dist/flexdoc.module.js.map +1 -0
- package/dist/flexdoc.module.test.d.ts +1 -0
- package/dist/flexdoc.module.test.js +72 -0
- package/dist/flexdoc.module.test.js.map +1 -0
- package/dist/flexdoc.service.d.ts +5 -0
- package/dist/flexdoc.service.js +24 -0
- package/dist/flexdoc.service.js.map +1 -0
- package/dist/framework-adapters.d.ts +25 -0
- package/dist/framework-adapters.js +163 -0
- package/dist/framework-adapters.js.map +1 -0
- package/dist/framework-adapters.test.d.ts +1 -0
- package/dist/framework-adapters.test.js +37 -0
- package/dist/framework-adapters.test.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces.d.ts +169 -0
- package/dist/interfaces.js +3 -0
- package/dist/interfaces.js.map +1 -0
- package/dist/renderer/flexdoc.standalone.css +1 -0
- package/dist/renderer/flexdoc.standalone.js +200 -0
- package/dist/renderer-assets.d.ts +5 -0
- package/dist/renderer-assets.js +60 -0
- package/dist/renderer-assets.js.map +1 -0
- package/dist/scripts/generate-auth.d.ts +2 -0
- package/dist/scripts/generate-auth.js +107 -0
- package/dist/scripts/generate-auth.js.map +1 -0
- package/dist/setup.d.ts +6 -0
- package/dist/setup.js +220 -0
- package/dist/setup.js.map +1 -0
- package/dist/setup.test.d.ts +1 -0
- package/dist/setup.test.js +89 -0
- package/dist/setup.test.js.map +1 -0
- package/dist/template.d.ts +15 -0
- package/dist/template.js +64 -0
- package/dist/template.js.map +1 -0
- package/dist/template.test.d.ts +1 -0
- package/dist/template.test.js +38 -0
- package/dist/template.test.js.map +1 -0
- package/dist/templates/code-examples.njk +106 -0
- package/dist/templates/content.njk +272 -0
- package/dist/templates/footer.njk +38 -0
- package/dist/templates/header.njk +57 -0
- package/dist/templates/layout.njk +647 -0
- package/dist/templates/parameters.njk +64 -0
- package/dist/templates/request-body.njk +63 -0
- package/dist/templates/responses.njk +78 -0
- package/dist/templates/sidebar.njk +117 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +34 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const template_1 = require("./template");
|
|
4
|
+
describe('generateFlexDocHTML', () => {
|
|
5
|
+
it('should generate HTML with embedded spec when provided', () => {
|
|
6
|
+
const spec = { openapi: '3.1.0', info: { title: 'Test API', version: '1.0.0' }, paths: {} };
|
|
7
|
+
const html = (0, template_1.generateFlexDocHTML)(spec);
|
|
8
|
+
expect(html).toContain('<!DOCTYPE html>');
|
|
9
|
+
expect(html).toContain('<title>Test API</title>');
|
|
10
|
+
expect(html).toContain('window.__FLEXDOC_SPEC__');
|
|
11
|
+
expect(html).toContain(JSON.stringify(spec));
|
|
12
|
+
expect(html).toContain('contractVersion');
|
|
13
|
+
});
|
|
14
|
+
it('should include specUrl when provided', () => {
|
|
15
|
+
const specUrl = 'https://example.com/openapi.json';
|
|
16
|
+
const html = (0, template_1.generateFlexDocHTML)(null, { specUrl });
|
|
17
|
+
expect(html).toContain('window.__FLEXDOC_SPEC_URL__');
|
|
18
|
+
expect(html).toContain(specUrl);
|
|
19
|
+
});
|
|
20
|
+
it('should include renderer theme and product options', () => {
|
|
21
|
+
const options = { theme: 'dark', tryIt: { enabled: true }, codeSamples: { languages: ['curl', 'go'] } };
|
|
22
|
+
const html = (0, template_1.generateFlexDocHTML)(null, options);
|
|
23
|
+
expect(html).toContain('window.__FLEXDOC_OPTIONS__');
|
|
24
|
+
expect(html).toContain('"theme":"dark"');
|
|
25
|
+
expect(html).toContain('"tryIt":{"enabled":true}');
|
|
26
|
+
});
|
|
27
|
+
it('never exposes route authentication secrets to the browser', () => {
|
|
28
|
+
const html = (0, template_1.generateFlexDocHTML)(null, { auth: { type: 'basic', secretKey: 'super-secret-do-not-ship' } });
|
|
29
|
+
expect(html).not.toContain('super-secret-do-not-ship');
|
|
30
|
+
expect(html).not.toContain('secretKey');
|
|
31
|
+
});
|
|
32
|
+
it('should handle null spec gracefully', () => {
|
|
33
|
+
const html = (0, template_1.generateFlexDocHTML)(null);
|
|
34
|
+
expect(html).toContain('<!DOCTYPE html>');
|
|
35
|
+
expect(html).toContain('window.__FLEXDOC_SPEC__ = null');
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
//# sourceMappingURL=template.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template.test.js","sourceRoot":"","sources":["../src/template.test.ts"],"names":[],"mappings":";;AACA,yCAAiD;AAEjD,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,MAAM,IAAI,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;QAC5F,MAAM,IAAI,GAAG,IAAA,8BAAmB,EAAC,IAAI,CAAC,CAAC;QACvC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;QAC1C,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,yBAAyB,CAAC,CAAC;QAClD,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,yBAAyB,CAAC,CAAC;QAClD,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;QAC7C,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,OAAO,GAAG,kCAAkC,CAAC;QACnD,MAAM,IAAI,GAAG,IAAA,8BAAmB,EAAC,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;QACpD,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,6BAA6B,CAAC,CAAC;QACtD,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,OAAO,GAAmB,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,EAAE,SAAS,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC;QACxH,MAAM,IAAI,GAAG,IAAA,8BAAmB,EAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAChD,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,4BAA4B,CAAC,CAAC;QACrD,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;QACzC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,0BAA0B,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACnE,MAAM,IAAI,GAAG,IAAA,8BAAmB,EAAC,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,0BAA0B,EAAE,EAAE,CAAC,CAAC;QAC3G,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,0BAA0B,CAAC,CAAC;QACvD,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,IAAI,GAAG,IAAA,8BAAmB,EAAC,IAAI,CAAC,CAAC;QACvC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;QAC1C,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,gCAAgC,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
<div class="mb-8">
|
|
2
|
+
<button class="flex items-center gap-2 text-lg font-semibold text-gray-900 dark:text-white mb-4 hover:text-primary-600 dark:hover:text-primary-400 transition-colors"
|
|
3
|
+
data-toggle="code-examples-{{ getOperationId(path, method, operation) }}">
|
|
4
|
+
<i data-lucide="chevron-down" class="w-5 h-5"></i>
|
|
5
|
+
Code Examples
|
|
6
|
+
</button>
|
|
7
|
+
|
|
8
|
+
<div id="code-examples-{{ getOperationId(path, method, operation) }}" class="collapsible-content">
|
|
9
|
+
<div class="bg-gray-50 dark:bg-gray-700/50 rounded-xl p-6 border border-gray-200 dark:border-gray-600">
|
|
10
|
+
<div class="code-tabs">
|
|
11
|
+
<!-- Tab Navigation -->
|
|
12
|
+
<div class="border-b border-gray-200 dark:border-gray-600 mb-4">
|
|
13
|
+
<nav class="flex space-x-8">
|
|
14
|
+
<button class="code-tab border-b-2 border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 py-2 px-1 text-sm font-medium"
|
|
15
|
+
data-target="curl-{{ getOperationId(path, method, operation) }}">
|
|
16
|
+
cURL
|
|
17
|
+
</button>
|
|
18
|
+
<button class="code-tab border-b-2 border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 py-2 px-1 text-sm font-medium"
|
|
19
|
+
data-target="python-{{ getOperationId(path, method, operation) }}">
|
|
20
|
+
Python
|
|
21
|
+
</button>
|
|
22
|
+
<button class="code-tab border-b-2 border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 py-2 px-1 text-sm font-medium"
|
|
23
|
+
data-target="javascript-{{ getOperationId(path, method, operation) }}">
|
|
24
|
+
JavaScript
|
|
25
|
+
</button>
|
|
26
|
+
<button class="code-tab border-b-2 border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 py-2 px-1 text-sm font-medium"
|
|
27
|
+
data-target="go-{{ getOperationId(path, method, operation) }}">
|
|
28
|
+
Go
|
|
29
|
+
</button>
|
|
30
|
+
</nav>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<!-- Tab Panels -->
|
|
34
|
+
<div>
|
|
35
|
+
<!-- cURL Panel -->
|
|
36
|
+
<div id="curl-{{ getOperationId(path, method, operation) }}" class="code-panel">
|
|
37
|
+
<div class="bg-gray-100 dark:bg-gray-800 rounded-lg overflow-hidden border border-gray-200 dark:border-transparent">
|
|
38
|
+
<div class="flex items-center justify-between px-4 py-2 bg-gray-200 dark:bg-gray-700 border-b border-gray-300 dark:border-gray-700">
|
|
39
|
+
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">cURL</span>
|
|
40
|
+
<button class="text-xs text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition-colors"
|
|
41
|
+
data-copy="{{ renderCurlExample(path, method, operation, baseUrl) }}">
|
|
42
|
+
<i data-lucide="copy" class="w-3 h-3 inline mr-1"></i>
|
|
43
|
+
Copy
|
|
44
|
+
</button>
|
|
45
|
+
</div>
|
|
46
|
+
<div class="p-4 overflow-x-auto">
|
|
47
|
+
<pre class="text-sm text-gray-700 dark:text-gray-300"><code>{{ renderCurlExample(path, method, operation, baseUrl) }}</code></pre>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<!-- Python Panel -->
|
|
53
|
+
<div id="python-{{ getOperationId(path, method, operation) }}" class="code-panel hidden">
|
|
54
|
+
<div class="bg-gray-100 dark:bg-gray-800 rounded-lg overflow-hidden border border-gray-200 dark:border-transparent">
|
|
55
|
+
<div class="flex items-center justify-between px-4 py-2 bg-gray-200 dark:bg-gray-700 border-b border-gray-300 dark:border-gray-700">
|
|
56
|
+
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Python</span>
|
|
57
|
+
<button class="text-xs text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition-colors"
|
|
58
|
+
data-copy="{{ renderPythonExample(path, method, operation, baseUrl) }}">
|
|
59
|
+
<i data-lucide="copy" class="w-3 h-3 inline mr-1"></i>
|
|
60
|
+
Copy
|
|
61
|
+
</button>
|
|
62
|
+
</div>
|
|
63
|
+
<div class="p-4 overflow-x-auto">
|
|
64
|
+
<pre class="text-sm text-gray-700 dark:text-gray-300"><code>{{ renderPythonExample(path, method, operation, baseUrl) }}</code></pre>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<!-- JavaScript Panel -->
|
|
70
|
+
<div id="javascript-{{ getOperationId(path, method, operation) }}" class="code-panel hidden">
|
|
71
|
+
<div class="bg-gray-100 dark:bg-gray-800 rounded-lg overflow-hidden border border-gray-200 dark:border-transparent">
|
|
72
|
+
<div class="flex items-center justify-between px-4 py-2 bg-gray-200 dark:bg-gray-700 border-b border-gray-300 dark:border-gray-700">
|
|
73
|
+
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">JavaScript</span>
|
|
74
|
+
<button class="text-xs text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition-colors"
|
|
75
|
+
data-copy="{{ renderJavaScriptExample(path, method, operation, baseUrl) }}">
|
|
76
|
+
<i data-lucide="copy" class="w-3 h-3 inline mr-1"></i>
|
|
77
|
+
Copy
|
|
78
|
+
</button>
|
|
79
|
+
</div>
|
|
80
|
+
<div class="p-4 overflow-x-auto">
|
|
81
|
+
<pre class="text-sm text-gray-700 dark:text-gray-300"><code>{{ renderJavaScriptExample(path, method, operation, baseUrl) }}</code></pre>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<!-- Go Panel -->
|
|
87
|
+
<div id="go-{{ getOperationId(path, method, operation) }}" class="code-panel hidden">
|
|
88
|
+
<div class="bg-gray-100 dark:bg-gray-800 rounded-lg overflow-hidden border border-gray-200 dark:border-transparent">
|
|
89
|
+
<div class="flex items-center justify-between px-4 py-2 bg-gray-200 dark:bg-gray-700 border-b border-gray-300 dark:border-gray-700">
|
|
90
|
+
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Go</span>
|
|
91
|
+
<button class="text-xs text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition-colors"
|
|
92
|
+
data-copy="{{ renderGoExample(path, method, operation, baseUrl) }}">
|
|
93
|
+
<i data-lucide="copy" class="w-3 h-3 inline mr-1"></i>
|
|
94
|
+
Copy
|
|
95
|
+
</button>
|
|
96
|
+
</div>
|
|
97
|
+
<div class="p-4 overflow-x-auto">
|
|
98
|
+
<pre class="text-sm text-gray-700 dark:text-gray-300"><code>{{ renderGoExample(path, method, operation, baseUrl) }}</code></pre>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
{% extends "layout.njk" %}
|
|
2
|
+
|
|
3
|
+
{% block content %}
|
|
4
|
+
<div class="p-8">
|
|
5
|
+
<!-- Overview Content (shown by default) -->
|
|
6
|
+
<div class="overview-content">
|
|
7
|
+
{% if currentSpec %}
|
|
8
|
+
<!-- API Overview -->
|
|
9
|
+
<div class="mb-12">
|
|
10
|
+
{% if showInfoInOverview %}
|
|
11
|
+
<div class="flex items-center gap-4 mb-6">
|
|
12
|
+
<div class="w-16 h-16 bg-gradient-to-br from-primary-500 to-primary-600 rounded-xl flex items-center justify-center">
|
|
13
|
+
<i data-lucide="file-text" class="w-8 h-8 text-white"></i>
|
|
14
|
+
</div>
|
|
15
|
+
<div>
|
|
16
|
+
<h1 class="text-4xl font-bold text-gray-900 dark:text-white">{{ currentSpec.info.title }}</h1>
|
|
17
|
+
{% if currentSpec.info.description %}
|
|
18
|
+
<p class="text-xl text-gray-600 dark:text-gray-400 mt-2 max-w-3xl">{{ currentSpec.info.description }}</p>
|
|
19
|
+
{% endif %}
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
{% endif %}
|
|
23
|
+
|
|
24
|
+
<!-- Quick Stats -->
|
|
25
|
+
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
|
|
26
|
+
<div class="bg-white dark:bg-gray-800 p-6 rounded-xl border border-gray-200 dark:border-gray-700 hover-lift">
|
|
27
|
+
<div class="flex items-center gap-3">
|
|
28
|
+
<div class="w-10 h-10 bg-blue-100 dark:bg-blue-900/30 rounded-lg flex items-center justify-center">
|
|
29
|
+
<i data-lucide="file-text" class="w-5 h-5 text-blue-600 dark:text-blue-400"></i>
|
|
30
|
+
</div>
|
|
31
|
+
<div>
|
|
32
|
+
<p class="text-sm text-gray-500 dark:text-gray-400 font-medium">Total Endpoints</p>
|
|
33
|
+
<p class="text-2xl font-bold text-gray-900 dark:text-white">
|
|
34
|
+
{% set totalEndpoints = 0 %}
|
|
35
|
+
{% for tag, paths in taggedPaths %}
|
|
36
|
+
{% for path, methods in paths %}
|
|
37
|
+
{% for method, operation in methods %}
|
|
38
|
+
{% set totalEndpoints = totalEndpoints + 1 %}
|
|
39
|
+
{% endfor %}
|
|
40
|
+
{% endfor %}
|
|
41
|
+
{% endfor %}
|
|
42
|
+
{{ totalEndpoints }}
|
|
43
|
+
</p>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<div class="bg-white dark:bg-gray-800 p-6 rounded-xl border border-gray-200 dark:border-gray-700 hover-lift">
|
|
49
|
+
<div class="flex items-center gap-3">
|
|
50
|
+
<div class="w-10 h-10 bg-green-100 dark:bg-green-900/30 rounded-lg flex items-center justify-center">
|
|
51
|
+
<i data-lucide="server" class="w-5 h-5 text-green-600 dark:text-green-400"></i>
|
|
52
|
+
</div>
|
|
53
|
+
<div>
|
|
54
|
+
<p class="text-sm text-gray-500 dark:text-gray-400 font-medium">Servers</p>
|
|
55
|
+
<p class="text-2xl font-bold text-gray-900 dark:text-white">{{ currentSpec.servers.length if currentSpec.servers else 0 }}</p>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<div class="bg-white dark:bg-gray-800 p-6 rounded-xl border border-gray-200 dark:border-gray-700 hover-lift">
|
|
61
|
+
<div class="flex items-center gap-3">
|
|
62
|
+
<div class="w-10 h-10 bg-purple-100 dark:bg-purple-900/30 rounded-lg flex items-center justify-center">
|
|
63
|
+
<i data-lucide="tag" class="w-5 h-5 text-purple-600 dark:text-purple-400"></i>
|
|
64
|
+
</div>
|
|
65
|
+
<div>
|
|
66
|
+
<p class="text-sm text-gray-500 dark:text-gray-400 font-medium">Tags</p>
|
|
67
|
+
<p class="text-2xl font-bold text-gray-900 dark:text-white">{{ taggedPaths | length }}</p>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div class="bg-white dark:bg-gray-800 p-6 rounded-xl border border-gray-200 dark:border-gray-700 hover-lift">
|
|
73
|
+
<div class="flex items-center gap-3">
|
|
74
|
+
<div class="w-10 h-10 bg-orange-100 dark:bg-orange-900/30 rounded-lg flex items-center justify-center">
|
|
75
|
+
<i data-lucide="shield" class="w-5 h-5 text-orange-600 dark:text-orange-400"></i>
|
|
76
|
+
</div>
|
|
77
|
+
<div>
|
|
78
|
+
<p class="text-sm text-gray-500 dark:text-gray-400 font-medium">Security Schemes</p>
|
|
79
|
+
<p class="text-2xl font-bold text-gray-900 dark:text-white">
|
|
80
|
+
{{ currentSpec.components.securitySchemes | length if currentSpec.components and currentSpec.components.securitySchemes else 0 }}
|
|
81
|
+
</p>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<!-- Method Distribution -->
|
|
88
|
+
<div class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 shadow-sm p-6 mb-8">
|
|
89
|
+
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">
|
|
90
|
+
HTTP Methods Distribution
|
|
91
|
+
</h2>
|
|
92
|
+
<div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-4">
|
|
93
|
+
<!-- Method counts are pre-calculated in TypeScript -->
|
|
94
|
+
|
|
95
|
+
{% for method in ['get', 'post', 'put', 'delete', 'patch', 'options'] %}
|
|
96
|
+
<div class="text-center">
|
|
97
|
+
<div class="min-w-fit px-3 py-2 mx-auto rounded-lg flex items-center justify-center mb-2 method-{{ method }}">
|
|
98
|
+
<span class="text-xs font-bold">
|
|
99
|
+
{{ method | upper }}
|
|
100
|
+
</span>
|
|
101
|
+
</div>
|
|
102
|
+
<p class="text-lg font-bold text-gray-900 dark:text-white">
|
|
103
|
+
{{ methodCounts[method] }}
|
|
104
|
+
</p>
|
|
105
|
+
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
106
|
+
{{ method | upper }}
|
|
107
|
+
</p>
|
|
108
|
+
</div>
|
|
109
|
+
{% endfor %}
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
{% set hasInfoCard = showInfoInOverview and currentSpec and currentSpec.info and (currentSpec.info.contact or currentSpec.info.license or currentSpec.info.termsOfService) %}
|
|
114
|
+
{% set hasServersCard = showServersInOverview and currentSpec and currentSpec.servers and currentSpec.servers.length > 0 %}
|
|
115
|
+
{% if hasInfoCard or hasServersCard %}
|
|
116
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
117
|
+
{% if hasInfoCard %}
|
|
118
|
+
<!-- Information (Overview) -->
|
|
119
|
+
<div class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 shadow-sm p-6">
|
|
120
|
+
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">Information</h2>
|
|
121
|
+
|
|
122
|
+
{% if currentSpec.info.contact %}
|
|
123
|
+
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-2">Contact</h3>
|
|
124
|
+
<div class="text-sm text-gray-600 dark:text-gray-300 space-y-1">
|
|
125
|
+
{% if currentSpec.info.contact.name %}
|
|
126
|
+
<p>{{ currentSpec.info.contact.name }}</p>
|
|
127
|
+
{% endif %}
|
|
128
|
+
{% if currentSpec.info.contact.email %}
|
|
129
|
+
<p>
|
|
130
|
+
<a href="mailto:{{ currentSpec.info.contact.email }}" class="text-primary-600 dark:text-primary-400 hover:underline">
|
|
131
|
+
{{ currentSpec.info.contact.email }}
|
|
132
|
+
</a>
|
|
133
|
+
</p>
|
|
134
|
+
{% endif %}
|
|
135
|
+
{% if currentSpec.info.contact.url %}
|
|
136
|
+
<p>
|
|
137
|
+
<a href="{{ currentSpec.info.contact.url }}" target="_blank" rel="noopener" class="inline-flex items-center gap-1 text-primary-600 dark:text-primary-400 hover:underline">
|
|
138
|
+
Website
|
|
139
|
+
<i data-lucide="external-link" class="w-3.5 h-3.5"></i>
|
|
140
|
+
</a>
|
|
141
|
+
</p>
|
|
142
|
+
{% endif %}
|
|
143
|
+
</div>
|
|
144
|
+
{% endif %}
|
|
145
|
+
|
|
146
|
+
{% if currentSpec.info.license %}
|
|
147
|
+
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300 mt-4 mb-2">License</h3>
|
|
148
|
+
<div class="text-sm">
|
|
149
|
+
{% if currentSpec.info.license.url %}
|
|
150
|
+
<a href="{{ currentSpec.info.license.url }}" target="_blank" rel="noopener" class="text-primary-600 dark:text-primary-400 hover:underline">
|
|
151
|
+
{{ currentSpec.info.license.name }}
|
|
152
|
+
</a>
|
|
153
|
+
{% else %}
|
|
154
|
+
<span class="text-gray-600 dark:text-gray-300">{{ currentSpec.info.license.name }}</span>
|
|
155
|
+
{% endif %}
|
|
156
|
+
</div>
|
|
157
|
+
{% endif %}
|
|
158
|
+
|
|
159
|
+
{% if currentSpec.info.termsOfService %}
|
|
160
|
+
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300 mt-4 mb-2">Terms of Service</h3>
|
|
161
|
+
<div class="text-sm">
|
|
162
|
+
<a href="{{ currentSpec.info.termsOfService }}" target="_blank" rel="noopener" class="text-primary-600 dark:text-primary-400 hover:underline">
|
|
163
|
+
Terms of Service
|
|
164
|
+
</a>
|
|
165
|
+
</div>
|
|
166
|
+
{% endif %}
|
|
167
|
+
</div>
|
|
168
|
+
{% endif %}
|
|
169
|
+
|
|
170
|
+
{% if hasServersCard %}
|
|
171
|
+
<!-- Servers (Overview) -->
|
|
172
|
+
<div class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 shadow-sm p-6">
|
|
173
|
+
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">Servers</h2>
|
|
174
|
+
<div class="grid grid-cols-1 gap-4">
|
|
175
|
+
{% for server in currentSpec.servers %}
|
|
176
|
+
<div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-4 border border-gray-200 dark:border-gray-600">
|
|
177
|
+
<code class="text-sm text-primary-600 dark:text-primary-400 font-mono">{{ server.url }}</code>
|
|
178
|
+
{% if server.description %}
|
|
179
|
+
<p class="text-xs text-gray-600 dark:text-gray-300 mt-1">{{ server.description }}</p>
|
|
180
|
+
{% endif %}
|
|
181
|
+
</div>
|
|
182
|
+
{% endfor %}
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
{% endif %}
|
|
186
|
+
</div>
|
|
187
|
+
{% endif %}
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
{% else %}
|
|
191
|
+
<!-- No Spec State -->
|
|
192
|
+
<div class="flex items-center justify-center h-96">
|
|
193
|
+
<div class="text-center">
|
|
194
|
+
<div class="w-24 h-24 bg-gray-100 dark:bg-gray-800 rounded-full flex items-center justify-center mx-auto mb-6">
|
|
195
|
+
<i data-lucide="file-x" class="w-12 h-12 text-gray-400"></i>
|
|
196
|
+
</div>
|
|
197
|
+
<h2 class="text-2xl font-semibold text-gray-900 dark:text-white mb-2">No OpenAPI specification found</h2>
|
|
198
|
+
<p class="text-gray-600 dark:text-gray-400 max-w-md mx-auto">
|
|
199
|
+
Please provide a valid OpenAPI 3.0 specification to generate documentation.
|
|
200
|
+
</p>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
{% endif %}
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
<!-- Individual Endpoint Details (hidden by default) -->
|
|
207
|
+
{% for tag, paths in taggedPaths %}
|
|
208
|
+
{% for path, methods in paths %}
|
|
209
|
+
{% for method, operation in methods %}
|
|
210
|
+
<div id="{{ getOperationId(path, method, operation) }}" class="endpoint-detail">
|
|
211
|
+
<!-- Endpoint Header -->
|
|
212
|
+
<div class="mb-8">
|
|
213
|
+
<div class="flex items-center gap-3 mb-4">
|
|
214
|
+
<span class="px-3 py-1 text-sm font-bold rounded-lg border method-{{ method }}">
|
|
215
|
+
{{ method | upper }}
|
|
216
|
+
</span>
|
|
217
|
+
<code class="text-lg font-mono font-medium text-gray-900 dark:text-white bg-gray-100 dark:bg-gray-800 px-3 py-1 rounded-lg">
|
|
218
|
+
{{ path }}
|
|
219
|
+
</code>
|
|
220
|
+
</div>
|
|
221
|
+
|
|
222
|
+
{% if operation.summary %}
|
|
223
|
+
<h1 class="text-2xl font-bold text-gray-900 dark:text-white mb-2">
|
|
224
|
+
{{ operation.summary }}
|
|
225
|
+
</h1>
|
|
226
|
+
{% endif %}
|
|
227
|
+
|
|
228
|
+
{% if operation.description %}
|
|
229
|
+
<p class="text-gray-600 dark:text-gray-400 leading-relaxed">
|
|
230
|
+
{{ operation.description }}
|
|
231
|
+
</p>
|
|
232
|
+
{% endif %}
|
|
233
|
+
|
|
234
|
+
<div class="flex items-center gap-4 mt-4">
|
|
235
|
+
{% if operation.deprecated %}
|
|
236
|
+
<div class="flex items-center gap-1 text-orange-600 dark:text-orange-400">
|
|
237
|
+
<i data-lucide="alert-circle" class="w-4 h-4"></i>
|
|
238
|
+
<span class="text-sm font-medium">Deprecated</span>
|
|
239
|
+
</div>
|
|
240
|
+
{% endif %}
|
|
241
|
+
|
|
242
|
+
{% if operation.security %}
|
|
243
|
+
<div class="flex items-center gap-1 text-red-600 dark:text-red-400">
|
|
244
|
+
<i data-lucide="lock" class="w-4 h-4"></i>
|
|
245
|
+
<span class="text-sm">Authentication required</span>
|
|
246
|
+
</div>
|
|
247
|
+
{% else %}
|
|
248
|
+
<div class="flex items-center gap-1 text-green-600 dark:text-green-400">
|
|
249
|
+
<i data-lucide="unlock" class="w-4 h-4"></i>
|
|
250
|
+
<span class="text-sm">No authentication required</span>
|
|
251
|
+
</div>
|
|
252
|
+
{% endif %}
|
|
253
|
+
</div>
|
|
254
|
+
</div>
|
|
255
|
+
|
|
256
|
+
<!-- Parameters -->
|
|
257
|
+
{% include "parameters.njk" %}
|
|
258
|
+
|
|
259
|
+
<!-- Request Body -->
|
|
260
|
+
{% include "request-body.njk" %}
|
|
261
|
+
|
|
262
|
+
<!-- Responses -->
|
|
263
|
+
{% include "responses.njk" %}
|
|
264
|
+
|
|
265
|
+
<!-- Code Examples -->
|
|
266
|
+
{% include "code-examples.njk" %}
|
|
267
|
+
</div>
|
|
268
|
+
{% endfor %}
|
|
269
|
+
{% endfor %}
|
|
270
|
+
{% endfor %}
|
|
271
|
+
</div>
|
|
272
|
+
{% endblock %}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<footer class="bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700 py-4 fixed bottom-0 left-0 right-0 z-10">
|
|
2
|
+
<div class="container mx-auto px-4">
|
|
3
|
+
<div class="flex justify-between items-center">
|
|
4
|
+
<!-- Left side: Always show Powered by text -->
|
|
5
|
+
<div class="text-sm text-gray-500 dark:text-gray-400">
|
|
6
|
+
Powered by <a href="https://bluejeans117.github.io/flexdoc" target="_blank" class="text-primary-600 dark:text-primary-400 hover:underline">FlexDoc</a>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<!-- Center: Copyright text -->
|
|
10
|
+
<div class="absolute left-1/2 transform -translate-x-1/2 text-sm text-gray-500 dark:text-gray-400">
|
|
11
|
+
{% if footer and footer.copyright %}
|
|
12
|
+
{{ footer.copyright }}
|
|
13
|
+
{% endif %}
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<!-- Right side: Links followed by OpenAPI Spec link -->
|
|
17
|
+
<div class="flex space-x-4 items-center justify-end">
|
|
18
|
+
{% if footer and footer.link %}
|
|
19
|
+
{% for link in footer.link %}
|
|
20
|
+
<a href="{{ link.url }}" target="_blank" class="text-sm text-primary-600 dark:text-primary-400 hover:underline flex items-center">
|
|
21
|
+
{% if link.icon %}
|
|
22
|
+
<i data-lucide="{{ link.icon }}" class="w-4 h-4 mr-1"></i>
|
|
23
|
+
{% endif %}
|
|
24
|
+
{{ link.text }}
|
|
25
|
+
</a>
|
|
26
|
+
{% endfor %}
|
|
27
|
+
{% endif %}
|
|
28
|
+
|
|
29
|
+
{% if specUrl %}
|
|
30
|
+
<a href="{{ specUrl }}" target="_blank" class="text-sm text-primary-600 dark:text-primary-400 hover:underline flex items-center">
|
|
31
|
+
<i data-lucide="file-json" class="w-4 h-4 mr-1"></i>
|
|
32
|
+
OpenAPI Spec
|
|
33
|
+
</a>
|
|
34
|
+
{% endif %}
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</footer>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<header class="bg-white dark:bg-gray-800 shadow-sm border-b border-gray-200 dark:border-gray-700 sticky top-0 z-50">
|
|
2
|
+
<div class="w-full px-6 sm:px-8 lg:px-10">
|
|
3
|
+
<div class="flex justify-between items-center h-16">
|
|
4
|
+
<div class="flex items-center space-x-4">
|
|
5
|
+
{% if logo %}
|
|
6
|
+
<div class="flex-shrink-0{{ logoContainerClass }}"{{ logoContainerStyle | safe }}>
|
|
7
|
+
{% if logoClickable %}
|
|
8
|
+
<a href="#" onClick="window.location.reload();" class="block">
|
|
9
|
+
<img src="{{ logo }}" alt="{{ logoAlt }}" class="h-8 w-auto"{{ logoStyle | safe }}>
|
|
10
|
+
</a>
|
|
11
|
+
{% else %}
|
|
12
|
+
<img src="{{ logo }}" alt="{{ logoAlt }}" class="h-8 w-auto"{{ logoStyle | safe }}>
|
|
13
|
+
{% endif %}
|
|
14
|
+
</div>
|
|
15
|
+
{% else %}
|
|
16
|
+
<div class="w-8 h-8 bg-gradient-to-br from-primary-500 to-primary-600 rounded-lg flex items-center justify-center">
|
|
17
|
+
<i data-lucide="file-text" class="w-5 h-5 text-white"></i>
|
|
18
|
+
</div>
|
|
19
|
+
{% endif %}
|
|
20
|
+
|
|
21
|
+
<div>
|
|
22
|
+
<h1 class="text-xl font-bold text-gray-900 dark:text-white">{{ title }}</h1>
|
|
23
|
+
{% if currentSpec and currentSpec.info and currentSpec.info.version %}
|
|
24
|
+
<p class="text-sm text-gray-500 dark:text-gray-400">v{{ currentSpec.info.version }}</p>
|
|
25
|
+
{% endif %}
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<div class="flex items-center space-x-4">
|
|
30
|
+
{% if not hideDownloadButton %}
|
|
31
|
+
{% if specUrl %}
|
|
32
|
+
<a href="{{ specUrl }}"
|
|
33
|
+
target="_blank"
|
|
34
|
+
class="inline-flex items-center px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
|
35
|
+
<i data-lucide="download" class="w-4 h-4 mr-2"></i>
|
|
36
|
+
Download Spec
|
|
37
|
+
</a>
|
|
38
|
+
{% else %}
|
|
39
|
+
<button id="download-spec-btn"
|
|
40
|
+
class="inline-flex items-center px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
|
41
|
+
<i data-lucide="download" class="w-4 h-4 mr-2"></i>
|
|
42
|
+
Download Spec
|
|
43
|
+
</button>
|
|
44
|
+
{% endif %}
|
|
45
|
+
{% endif %}
|
|
46
|
+
|
|
47
|
+
{% if not hideTopbar %}
|
|
48
|
+
<button id="theme-toggle"
|
|
49
|
+
class="p-2 rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-500 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700 hover:text-gray-700 dark:hover:text-gray-300 transition-colors">
|
|
50
|
+
<i data-lucide="moon" class="hidden dark:block w-5 h-5"></i>
|
|
51
|
+
<i data-lucide="sun" class="block dark:hidden w-5 h-5"></i>
|
|
52
|
+
</button>
|
|
53
|
+
{% endif %}
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
</header>
|