docula 0.50.0 → 1.0.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 +119 -5
- package/dist/docula.d.ts +254 -15
- package/dist/docula.js +1255 -227
- package/package.json +10 -12
- package/templates/classic/api.hbs +203 -17
- package/templates/classic/changelog-entry.hbs +2 -0
- package/templates/classic/changelog.hbs +2 -0
- package/templates/classic/css/api.css +753 -0
- package/templates/classic/css/base.css +29 -0
- package/templates/classic/docs.hbs +2 -0
- package/templates/classic/home.hbs +2 -0
- package/templates/classic/includes/api-try-it.hbs +61 -0
- package/templates/classic/js/api.js +282 -0
- package/templates/modern/api.hbs +203 -18
- package/templates/modern/css/api.css +1051 -0
- package/templates/modern/css/home.css +37 -0
- package/templates/modern/css/styles.css +65 -6
- package/templates/modern/home.hbs +13 -0
- package/templates/modern/includes/api-try-it.hbs +61 -0
- package/templates/modern/includes/header-bar.hbs +23 -3
- package/templates/modern/includes/header.hbs +2 -2
- package/templates/modern/includes/scripts.hbs +75 -17
- package/templates/modern/includes/theme-toggle.hbs +2 -2
- package/templates/modern/js/api.js +300 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
2
|
+
// Toggle operations
|
|
3
|
+
document.querySelectorAll('[data-toggle="operation"]').forEach(function(header) {
|
|
4
|
+
header.addEventListener('click', function() {
|
|
5
|
+
this.closest('.api-operation').classList.toggle('api-operation--collapsed');
|
|
6
|
+
});
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// Toggle sidebar groups
|
|
10
|
+
document.querySelectorAll('.api-sidebar__group-toggle').forEach(function(btn) {
|
|
11
|
+
btn.addEventListener('click', function() {
|
|
12
|
+
this.closest('.api-sidebar__group').classList.toggle('api-sidebar__group--collapsed');
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// Code example tabs
|
|
17
|
+
document.querySelectorAll('.api-code-tabs').forEach(function(tabs) {
|
|
18
|
+
var container = tabs.closest('.api-code-examples');
|
|
19
|
+
tabs.querySelectorAll('.api-code-tab').forEach(function(tab) {
|
|
20
|
+
tab.addEventListener('click', function() {
|
|
21
|
+
var target = this.getAttribute('data-tab');
|
|
22
|
+
container.querySelectorAll('.api-code-tab').forEach(function(t) { t.classList.remove('api-code-tab--active'); });
|
|
23
|
+
container.querySelectorAll('.api-code-panel').forEach(function(p) { p.classList.remove('api-code-panel--active'); });
|
|
24
|
+
this.classList.add('api-code-tab--active');
|
|
25
|
+
var panel = container.querySelector('[data-panel="' + target + '"]');
|
|
26
|
+
if (panel) panel.classList.add('api-code-panel--active');
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Copy to clipboard
|
|
32
|
+
document.querySelectorAll('[data-copy]').forEach(function(btn) {
|
|
33
|
+
btn.addEventListener('click', function() {
|
|
34
|
+
var code = this.closest('.api-code-panel').querySelector('code');
|
|
35
|
+
if (code) {
|
|
36
|
+
navigator.clipboard.writeText(code.textContent).then(function() {
|
|
37
|
+
btn.textContent = 'Copied!';
|
|
38
|
+
setTimeout(function() { btn.textContent = 'Copy'; }, 2000);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Search
|
|
45
|
+
var searchInput = document.getElementById('api-search');
|
|
46
|
+
if (searchInput) {
|
|
47
|
+
searchInput.addEventListener('input', function() {
|
|
48
|
+
var query = this.value.toLowerCase();
|
|
49
|
+
document.querySelectorAll('.api-sidebar__item').forEach(function(item) {
|
|
50
|
+
var path = (item.getAttribute('data-path') || '').toLowerCase();
|
|
51
|
+
var method = (item.getAttribute('data-method') || '').toLowerCase();
|
|
52
|
+
var text = item.textContent.toLowerCase();
|
|
53
|
+
var match = !query || path.indexOf(query) !== -1 || method.indexOf(query) !== -1 || text.indexOf(query) !== -1;
|
|
54
|
+
item.style.display = match ? '' : 'none';
|
|
55
|
+
});
|
|
56
|
+
document.querySelectorAll('.api-operation').forEach(function(op) {
|
|
57
|
+
var path = (op.querySelector('.api-operation__path') || {}).textContent || '';
|
|
58
|
+
var summary = (op.querySelector('.api-operation__summary') || {}).textContent || '';
|
|
59
|
+
var method = (op.querySelector('.method-badge') || {}).textContent || '';
|
|
60
|
+
var match = !query || path.toLowerCase().indexOf(query) !== -1 || summary.toLowerCase().indexOf(query) !== -1 || method.toLowerCase().indexOf(query) !== -1;
|
|
61
|
+
op.style.display = match ? '' : 'none';
|
|
62
|
+
});
|
|
63
|
+
document.querySelectorAll('.api-group').forEach(function(group) {
|
|
64
|
+
var visible = group.querySelectorAll('.api-operation:not([style*="display: none"])');
|
|
65
|
+
group.style.display = visible.length > 0 || !query ? '' : 'none';
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Scroll spy
|
|
71
|
+
var sidebarLinks = document.querySelectorAll('.api-sidebar__item');
|
|
72
|
+
if (sidebarLinks.length > 0) {
|
|
73
|
+
var observer = new IntersectionObserver(function(entries) {
|
|
74
|
+
entries.forEach(function(entry) {
|
|
75
|
+
if (entry.isIntersecting) {
|
|
76
|
+
sidebarLinks.forEach(function(l) { l.classList.remove('api-sidebar__item--active'); });
|
|
77
|
+
var link = document.querySelector('.api-sidebar__item[href="#' + entry.target.id + '"]');
|
|
78
|
+
if (link) link.classList.add('api-sidebar__item--active');
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}, { rootMargin: '-80px 0px -70% 0px' });
|
|
82
|
+
|
|
83
|
+
document.querySelectorAll('.api-operation').forEach(function(op) {
|
|
84
|
+
observer.observe(op);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Mobile sidebar toggle
|
|
89
|
+
var sidebarToggle = document.getElementById('api-sidebar-toggle');
|
|
90
|
+
var sidebar = document.getElementById('api-sidebar');
|
|
91
|
+
if (sidebarToggle && sidebar) {
|
|
92
|
+
sidebarToggle.addEventListener('click', function() {
|
|
93
|
+
sidebar.classList.toggle('api-sidebar--mobile-open');
|
|
94
|
+
});
|
|
95
|
+
sidebar.querySelectorAll('.api-sidebar__item').forEach(function(link) {
|
|
96
|
+
link.addEventListener('click', function() {
|
|
97
|
+
sidebar.classList.remove('api-sidebar--mobile-open');
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Collapse all sidebar groups by default
|
|
103
|
+
document.querySelectorAll('.api-sidebar__group').forEach(function(group) {
|
|
104
|
+
group.classList.add('api-sidebar__group--collapsed');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Auth type selector: show/hide value input
|
|
108
|
+
var authTypeSelect = document.getElementById('api-auth-type');
|
|
109
|
+
var authValueInput = document.getElementById('api-auth-value');
|
|
110
|
+
if (authTypeSelect && authValueInput) {
|
|
111
|
+
authTypeSelect.addEventListener('change', function() {
|
|
112
|
+
if (this.value === 'none') {
|
|
113
|
+
authValueInput.classList.add('api-auth__value--hidden');
|
|
114
|
+
authValueInput.value = '';
|
|
115
|
+
} else {
|
|
116
|
+
authValueInput.classList.remove('api-auth__value--hidden');
|
|
117
|
+
authValueInput.placeholder = this.value === 'apikey' ? 'Enter API key...' : 'Enter token...';
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Helper: expand an operation and its corresponding sidebar group
|
|
123
|
+
function expandOperationAndGroup(operationEl) {
|
|
124
|
+
if (!operationEl) return;
|
|
125
|
+
operationEl.classList.remove('api-operation--collapsed');
|
|
126
|
+
var contentGroup = operationEl.closest('.api-group');
|
|
127
|
+
if (contentGroup) {
|
|
128
|
+
var groupId = contentGroup.id.replace(/^group-/, '');
|
|
129
|
+
var sidebarGroup = document.querySelector('.api-sidebar__group[data-group="' + groupId + '"]');
|
|
130
|
+
if (sidebarGroup) sidebarGroup.classList.remove('api-sidebar__group--collapsed');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Expand operation via hash, or expand the first operation by default
|
|
135
|
+
if (window.location.hash) {
|
|
136
|
+
var target = document.querySelector(window.location.hash);
|
|
137
|
+
if (target && target.classList.contains('api-operation')) {
|
|
138
|
+
expandOperationAndGroup(target);
|
|
139
|
+
}
|
|
140
|
+
} else {
|
|
141
|
+
expandOperationAndGroup(document.querySelector('.api-operation'));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
window.addEventListener('hashchange', function() {
|
|
145
|
+
if (window.location.hash) {
|
|
146
|
+
var target = document.querySelector(window.location.hash);
|
|
147
|
+
if (target && target.classList.contains('api-operation')) {
|
|
148
|
+
expandOperationAndGroup(target);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Try It - Response tab switching
|
|
154
|
+
document.querySelectorAll('.api-try-it__response-tabs').forEach(function(tabs) {
|
|
155
|
+
var container = tabs.closest('.api-try-it__response');
|
|
156
|
+
tabs.querySelectorAll('.api-try-it__rtab').forEach(function(tab) {
|
|
157
|
+
tab.addEventListener('click', function() {
|
|
158
|
+
var target = this.getAttribute('data-try-rtab');
|
|
159
|
+
container.querySelectorAll('.api-try-it__rtab').forEach(function(t) { t.classList.remove('api-try-it__rtab--active'); });
|
|
160
|
+
container.querySelectorAll('.api-try-it__rpanel').forEach(function(p) { p.classList.remove('api-try-it__rpanel--active'); });
|
|
161
|
+
this.classList.add('api-try-it__rtab--active');
|
|
162
|
+
var panel = container.querySelector('[data-try-rpanel="' + target + '"]');
|
|
163
|
+
if (panel) panel.classList.add('api-try-it__rpanel--active');
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Try It - Helpers
|
|
169
|
+
function getStatusClass(status) {
|
|
170
|
+
if (status >= 200 && status < 300) return '2xx';
|
|
171
|
+
if (status >= 300 && status < 400) return '3xx';
|
|
172
|
+
if (status >= 400 && status < 500) return '4xx';
|
|
173
|
+
if (status >= 500) return '5xx';
|
|
174
|
+
return 'error';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function formatBody(text, contentType) {
|
|
178
|
+
if (contentType && contentType.indexOf('json') !== -1) {
|
|
179
|
+
try { return JSON.stringify(JSON.parse(text), null, 2); } catch(e) { /* ignore */ }
|
|
180
|
+
}
|
|
181
|
+
return text;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function resetResponseTabs(responseArea) {
|
|
185
|
+
var tabs = responseArea.querySelectorAll('.api-try-it__rtab');
|
|
186
|
+
var panels = responseArea.querySelectorAll('.api-try-it__rpanel');
|
|
187
|
+
tabs.forEach(function(t) { t.classList.remove('api-try-it__rtab--active'); });
|
|
188
|
+
panels.forEach(function(p) { p.classList.remove('api-try-it__rpanel--active'); });
|
|
189
|
+
tabs[0].classList.add('api-try-it__rtab--active');
|
|
190
|
+
panels[0].classList.add('api-try-it__rpanel--active');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Try It - Send request
|
|
194
|
+
document.querySelectorAll('[data-try-send]').forEach(function(btn) {
|
|
195
|
+
btn.addEventListener('click', function() {
|
|
196
|
+
var tryIt = this.closest('.api-try-it');
|
|
197
|
+
var method = tryIt.getAttribute('data-method');
|
|
198
|
+
var pathTemplate = tryIt.getAttribute('data-path');
|
|
199
|
+
var serverSelect = tryIt.querySelector('[data-try-server]');
|
|
200
|
+
var baseUrl = serverSelect ? serverSelect.value : '';
|
|
201
|
+
|
|
202
|
+
// Substitute path params
|
|
203
|
+
var path = pathTemplate;
|
|
204
|
+
tryIt.querySelectorAll('[data-param-in="path"]').forEach(function(row) {
|
|
205
|
+
var name = row.getAttribute('data-param-name');
|
|
206
|
+
var value = row.querySelector('[data-try-param]').value;
|
|
207
|
+
if (value) {
|
|
208
|
+
path = path.replace('{' + name + '}', encodeURIComponent(value));
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Build query string
|
|
213
|
+
var queryParts = [];
|
|
214
|
+
tryIt.querySelectorAll('[data-param-in="query"]').forEach(function(row) {
|
|
215
|
+
var name = row.getAttribute('data-param-name');
|
|
216
|
+
var value = row.querySelector('[data-try-param]').value;
|
|
217
|
+
if (value) queryParts.push(encodeURIComponent(name) + '=' + encodeURIComponent(value));
|
|
218
|
+
});
|
|
219
|
+
var queryString = queryParts.length ? '?' + queryParts.join('&') : '';
|
|
220
|
+
|
|
221
|
+
// Build headers
|
|
222
|
+
var headers = {};
|
|
223
|
+
tryIt.querySelectorAll('[data-param-in="header"]').forEach(function(row) {
|
|
224
|
+
var name = row.getAttribute('data-param-name');
|
|
225
|
+
var value = row.querySelector('[data-try-param]').value;
|
|
226
|
+
if (value) headers[name] = value;
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Inject global auth header
|
|
230
|
+
var authType = document.getElementById('api-auth-type');
|
|
231
|
+
var authValue = document.getElementById('api-auth-value');
|
|
232
|
+
if (authType && authValue && authValue.value.trim()) {
|
|
233
|
+
var authVal = authValue.value.trim();
|
|
234
|
+
if (authType.value === 'apikey') {
|
|
235
|
+
headers['x-api-key'] = authVal;
|
|
236
|
+
} else if (authType.value === 'bearer') {
|
|
237
|
+
headers['Authorization'] = 'Bearer ' + authVal;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Body
|
|
242
|
+
var bodyTextarea = tryIt.querySelector('[data-try-body]');
|
|
243
|
+
var body = bodyTextarea ? bodyTextarea.value.trim() : '';
|
|
244
|
+
if (body && !headers['Content-Type']) {
|
|
245
|
+
headers['Content-Type'] = tryIt.getAttribute('data-content-type') || 'application/json';
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
var url = baseUrl + path + queryString;
|
|
249
|
+
var fetchOptions = { method: method, headers: headers };
|
|
250
|
+
if (body && method !== 'GET' && method !== 'HEAD') {
|
|
251
|
+
fetchOptions.body = body;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Loading state
|
|
255
|
+
btn.disabled = true;
|
|
256
|
+
btn.textContent = 'Sending...';
|
|
257
|
+
var startTime = performance.now();
|
|
258
|
+
|
|
259
|
+
var responseArea = tryIt.querySelector('[data-try-response]');
|
|
260
|
+
var statusEl = tryIt.querySelector('[data-try-status]');
|
|
261
|
+
var timeEl = tryIt.querySelector('[data-try-time]');
|
|
262
|
+
var bodyEl = tryIt.querySelector('[data-try-response-body]');
|
|
263
|
+
var headersEl = tryIt.querySelector('[data-try-response-headers]');
|
|
264
|
+
|
|
265
|
+
fetch(url, fetchOptions).then(function(response) {
|
|
266
|
+
var elapsed = Math.round(performance.now() - startTime);
|
|
267
|
+
var statusClass = getStatusClass(response.status);
|
|
268
|
+
|
|
269
|
+
statusEl.textContent = response.status + ' ' + response.statusText;
|
|
270
|
+
statusEl.className = 'api-try-it__response-status api-try-it__response-status--' + statusClass;
|
|
271
|
+
timeEl.textContent = elapsed + 'ms';
|
|
272
|
+
|
|
273
|
+
// Collect response headers
|
|
274
|
+
var headerLines = [];
|
|
275
|
+
response.headers.forEach(function(value, key) {
|
|
276
|
+
headerLines.push(key + ': ' + value);
|
|
277
|
+
});
|
|
278
|
+
headersEl.textContent = headerLines.join('\n') || 'No headers';
|
|
279
|
+
|
|
280
|
+
var ct = response.headers.get('content-type') || '';
|
|
281
|
+
return response.text().then(function(text) {
|
|
282
|
+
bodyEl.textContent = formatBody(text, ct);
|
|
283
|
+
responseArea.classList.remove('api-try-it__response--hidden');
|
|
284
|
+
resetResponseTabs(responseArea);
|
|
285
|
+
});
|
|
286
|
+
}).catch(function(err) {
|
|
287
|
+
statusEl.textContent = 'Error';
|
|
288
|
+
statusEl.className = 'api-try-it__response-status api-try-it__response-status--error';
|
|
289
|
+
timeEl.textContent = '';
|
|
290
|
+
headersEl.textContent = '';
|
|
291
|
+
bodyEl.textContent = 'Request failed: ' + err.message + '\n\nThis may be caused by CORS restrictions. The API server must include appropriate CORS headers to allow browser requests.';
|
|
292
|
+
responseArea.classList.remove('api-try-it__response--hidden');
|
|
293
|
+
resetResponseTabs(responseArea);
|
|
294
|
+
}).finally(function() {
|
|
295
|
+
btn.disabled = false;
|
|
296
|
+
btn.textContent = 'Send Request';
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
});
|