drf-to-mkdoc 0.1.9__py3-none-any.whl → 0.2.0__py3-none-any.whl
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.
Potentially problematic release.
This version of drf-to-mkdoc might be problematic. Click here for more details.
- drf_to_mkdoc/management/commands/generate_docs.py +46 -71
- drf_to_mkdoc/management/commands/generate_model_docs.py +4 -5
- drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out-sidebar.js +879 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/try-out-sidebar.css +728 -0
- drf_to_mkdoc/utils/endpoint_detail_generator.py +9 -1
- drf_to_mkdoc/utils/model_detail_generator.py +23 -25
- {drf_to_mkdoc-0.1.9.dist-info → drf_to_mkdoc-0.2.0.dist-info}/METADATA +1 -1
- {drf_to_mkdoc-0.1.9.dist-info → drf_to_mkdoc-0.2.0.dist-info}/RECORD +11 -9
- {drf_to_mkdoc-0.1.9.dist-info → drf_to_mkdoc-0.2.0.dist-info}/WHEEL +0 -0
- {drf_to_mkdoc-0.1.9.dist-info → drf_to_mkdoc-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {drf_to_mkdoc-0.1.9.dist-info → drf_to_mkdoc-0.2.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,879 @@
|
|
|
1
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
2
|
+
// Get endpoint information from the page
|
|
3
|
+
const getEndpointInfo = () => {
|
|
4
|
+
let { path = '', method = 'GET' } = (window.__getTryOutEndpointInfo && window.__getTryOutEndpointInfo()) || {};
|
|
5
|
+
|
|
6
|
+
// First, try to find the method badge and code element in the same paragraph
|
|
7
|
+
const methodBadge = document.querySelector('.method-badge');
|
|
8
|
+
if (methodBadge) {
|
|
9
|
+
method = methodBadge.textContent.trim();
|
|
10
|
+
|
|
11
|
+
// Look for a code element in the same parent or nearby
|
|
12
|
+
const parent = methodBadge.closest('p, div, section');
|
|
13
|
+
if (parent) {
|
|
14
|
+
const codeElement = parent.querySelector('code');
|
|
15
|
+
if (codeElement) {
|
|
16
|
+
path = codeElement.textContent.trim();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Fallback: try to find in title
|
|
22
|
+
if (!path) {
|
|
23
|
+
const title = document.querySelector('h1');
|
|
24
|
+
if (title) {
|
|
25
|
+
const titleText = title.textContent.trim();
|
|
26
|
+
const pathMatch = titleText.match(/[A-Z]+\s+(.+)/);
|
|
27
|
+
if (pathMatch) {
|
|
28
|
+
path = pathMatch[1];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Additional fallback - look for code blocks with paths
|
|
34
|
+
if (!path) {
|
|
35
|
+
const codeBlocks = document.querySelectorAll('code');
|
|
36
|
+
for (const code of codeBlocks) {
|
|
37
|
+
const text = code.textContent.trim();
|
|
38
|
+
if (text.startsWith('/') && !text.includes('http')) {
|
|
39
|
+
path = text;
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Clean up the path to handle HTML entities and special characters
|
|
46
|
+
if (path) {
|
|
47
|
+
// Use DOMParser to safely decode HTML entities
|
|
48
|
+
const parser = new DOMParser();
|
|
49
|
+
const doc = parser.parseFromString(`<div>${path}</div>`, 'text/html');
|
|
50
|
+
const tempDiv = doc.querySelector('div');
|
|
51
|
+
path = tempDiv ? (tempDiv.textContent || tempDiv.innerText || path) : path;
|
|
52
|
+
|
|
53
|
+
// Remove any non-printable characters or replace problematic ones
|
|
54
|
+
path = path.replace(/[^\x20-\x7E]/g, ''); // Remove non-ASCII printable characters
|
|
55
|
+
path = path.replace(/¶/g, ''); // Specifically remove paragraph symbols
|
|
56
|
+
path = path.trim();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const info = {
|
|
60
|
+
method: method,
|
|
61
|
+
path: path || '/api/endpoint',
|
|
62
|
+
pathParams: path ? extractPathParams(path) : []
|
|
63
|
+
};
|
|
64
|
+
// expose for global functions
|
|
65
|
+
window.__getTryOutEndpointInfo = () => info;
|
|
66
|
+
return info;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Extract path parameters from URL
|
|
70
|
+
const extractPathParams = (path) => {
|
|
71
|
+
const matches = path.match(/\{([^}]+)\}/g) || [];
|
|
72
|
+
return matches.map(param => param.slice(1, -1)); // Remove { }
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Standard header suggestions
|
|
76
|
+
const standardHeaders = [
|
|
77
|
+
'Accept', 'Accept-Encoding', 'Accept-Language', 'Authorization',
|
|
78
|
+
'Cache-Control', 'Content-Type', 'Cookie', 'User-Agent',
|
|
79
|
+
'X-API-Key', 'X-Requested-With', 'X-CSRF-Token'
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
// HTML escaping function to prevent XSS
|
|
83
|
+
const escapeHtml = (s='') => s.replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
|
84
|
+
|
|
85
|
+
// Create the try-out panel HTML
|
|
86
|
+
const createTryOutPanel = (endpointInfo) => {
|
|
87
|
+
// Create path parameters HTML
|
|
88
|
+
let pathParamsHtml = '';
|
|
89
|
+
if (endpointInfo.pathParams.length > 0) {
|
|
90
|
+
pathParamsHtml = `
|
|
91
|
+
<div class="form-group">
|
|
92
|
+
<label class="form-label">Path Parameters</label>
|
|
93
|
+
<div class="kv-container" id="pathParams">
|
|
94
|
+
${endpointInfo.pathParams.map(p => {
|
|
95
|
+
const param = escapeHtml(p);
|
|
96
|
+
return `
|
|
97
|
+
<div class="kv-item">
|
|
98
|
+
<label class="param-label">${param}</label>
|
|
99
|
+
<input type="text" placeholder="Enter ${param} value" data-param="${param}" required>
|
|
100
|
+
</div>
|
|
101
|
+
`;
|
|
102
|
+
}).join('')}
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Create the panel HTML
|
|
109
|
+
return `
|
|
110
|
+
<div class="try-out-sidebar">
|
|
111
|
+
<div class="form-group">
|
|
112
|
+
<label class="form-label">Base URL</label>
|
|
113
|
+
<input type="text" class="form-input" id="baseUrl" value="${window.location.origin}" placeholder="https://api.example.com">
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<div class="tabs">
|
|
117
|
+
<button class="tab active" data-tab="parameters">Parameters</button>
|
|
118
|
+
<button class="tab" data-tab="headers">Headers</button>
|
|
119
|
+
<button class="tab" data-tab="body" style="${['POST', 'PUT', 'PATCH'].includes(endpointInfo.method) ? '' : 'display: none;'}">Body</button>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<div class="tab-content active" id="parametersTab">
|
|
123
|
+
${pathParamsHtml}
|
|
124
|
+
|
|
125
|
+
<div class="form-group">
|
|
126
|
+
<label class="form-label">Query Parameters</label>
|
|
127
|
+
<div class="kv-container" id="queryParams">
|
|
128
|
+
<div class="kv-item">
|
|
129
|
+
<input type="text" placeholder="Parameter name" list="queryParamSuggestions">
|
|
130
|
+
<input type="text" placeholder="Parameter value">
|
|
131
|
+
<button class="remove-btn" onclick="removeKvItem(this)">✕</button>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
<datalist id="queryParamSuggestions">
|
|
135
|
+
${(() => {
|
|
136
|
+
const suggestions = new Set();
|
|
137
|
+
|
|
138
|
+
// Find query parameters section by looking for h2 with id="query-parameters"
|
|
139
|
+
const queryParamsHeading = document.querySelector('h2[id="query-parameters"]');
|
|
140
|
+
if (queryParamsHeading) {
|
|
141
|
+
// Look for the next ul element after this heading
|
|
142
|
+
let nextElement = queryParamsHeading.nextElementSibling;
|
|
143
|
+
while (nextElement && nextElement.tagName !== 'UL') {
|
|
144
|
+
nextElement = nextElement.nextElementSibling;
|
|
145
|
+
}
|
|
146
|
+
if (nextElement) {
|
|
147
|
+
const queryParamElements = nextElement.querySelectorAll('li code');
|
|
148
|
+
queryParamElements.forEach(code => {
|
|
149
|
+
const name = (code.textContent || '').trim();
|
|
150
|
+
if (name) {
|
|
151
|
+
suggestions.add(name);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Find search parameters section by looking for h3 with id="search-parameters"
|
|
158
|
+
const searchParamsHeading = document.querySelector('h3[id="search-parameters"]');
|
|
159
|
+
if (searchParamsHeading) {
|
|
160
|
+
// Look for the next ul element after this heading
|
|
161
|
+
let nextElement = searchParamsHeading.nextElementSibling;
|
|
162
|
+
while (nextElement && nextElement.tagName !== 'UL') {
|
|
163
|
+
nextElement = nextElement.nextElementSibling;
|
|
164
|
+
}
|
|
165
|
+
if (nextElement) {
|
|
166
|
+
const searchParamItems = nextElement.querySelectorAll('li code');
|
|
167
|
+
if (searchParamItems.length > 0) {
|
|
168
|
+
suggestions.add('search');
|
|
169
|
+
}
|
|
170
|
+
// Also add the actual search parameter names
|
|
171
|
+
searchParamItems.forEach(code => {
|
|
172
|
+
const name = (code.textContent || '').trim();
|
|
173
|
+
if (name) {
|
|
174
|
+
suggestions.add(name);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Fallback: if no specific sections found, look for any ul li code elements
|
|
181
|
+
if (suggestions.size === 0) {
|
|
182
|
+
const fallbackElements = document.querySelectorAll('ul li code');
|
|
183
|
+
fallbackElements.forEach(code => {
|
|
184
|
+
const name = (code.textContent || '').trim();
|
|
185
|
+
if (name) {
|
|
186
|
+
suggestions.add(name);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return Array.from(suggestions).map(name => `<option value="${escapeHtml(name)}">`).join('');
|
|
192
|
+
})()}
|
|
193
|
+
</datalist>
|
|
194
|
+
<button class="add-btn" onclick="addQueryParam()">
|
|
195
|
+
<span>+</span> Add Parameter
|
|
196
|
+
</button>
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
<div class="tab-content" id="headersTab">
|
|
201
|
+
<div class="form-group">
|
|
202
|
+
<label class="form-label">Request Headers</label>
|
|
203
|
+
<div class="kv-container" id="requestHeaders">
|
|
204
|
+
<div class="kv-item">
|
|
205
|
+
<input type="text" value="Content-Type" list="headerSuggestions">
|
|
206
|
+
<input type="text" value="application/json">
|
|
207
|
+
<button class="remove-btn" onclick="removeKvItem(this)">✕</button>
|
|
208
|
+
</div>
|
|
209
|
+
<div class="kv-item">
|
|
210
|
+
<input type="text" value="Authorization" list="headerSuggestions">
|
|
211
|
+
<input type="text" placeholder="Bearer your-token">
|
|
212
|
+
<button class="remove-btn" onclick="removeKvItem(this)">✕</button>
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
<datalist id="headerSuggestions">
|
|
216
|
+
${standardHeaders.map(header => `<option value="${header}">`).join('')}
|
|
217
|
+
</datalist>
|
|
218
|
+
<button class="add-btn" onclick="addHeader()">
|
|
219
|
+
<span>+</span> Add Header
|
|
220
|
+
</button>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
<div class="tab-content" id="bodyTab">
|
|
225
|
+
<div class="form-group">
|
|
226
|
+
<label class="form-label">Request Body</label>
|
|
227
|
+
<textarea class="form-input textarea" id="requestBody" placeholder="Enter JSON payload here..."></textarea>
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
|
|
231
|
+
<button class="execute-btn" id="executeBtn" onclick="executeRequest()">
|
|
232
|
+
<span>▶</span> Execute Request
|
|
233
|
+
</button>
|
|
234
|
+
</div>
|
|
235
|
+
`;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// Check if mobile/tablet view
|
|
239
|
+
const isMobile = () => window.innerWidth <= 480;
|
|
240
|
+
const isTablet = () => window.innerWidth > 480 && window.innerWidth <= 1220; // Increased tablet breakpoint to match MkDocs
|
|
241
|
+
|
|
242
|
+
// Add the try-out panel to the sidebar or create mobile version
|
|
243
|
+
const addTryOutToSidebar = () => {
|
|
244
|
+
const endpointInfo = getEndpointInfo();
|
|
245
|
+
const tryOutPanel = createTryOutPanel(endpointInfo);
|
|
246
|
+
|
|
247
|
+
if (isMobile()) {
|
|
248
|
+
// Create mobile floating button and modal
|
|
249
|
+
createMobileTryOut(tryOutPanel);
|
|
250
|
+
} else if (isTablet()) {
|
|
251
|
+
// Tablet: Create mobile interface but don't interfere with hamburger
|
|
252
|
+
createMobileTryOut(tryOutPanel);
|
|
253
|
+
} else {
|
|
254
|
+
// Desktop: Add to sidebar
|
|
255
|
+
const leftSidebar = document.querySelector('.md-sidebar--primary');
|
|
256
|
+
if (leftSidebar) {
|
|
257
|
+
const panelContainer = document.createElement('div');
|
|
258
|
+
panelContainer.className = 'try-out-container';
|
|
259
|
+
|
|
260
|
+
const parser = new DOMParser();
|
|
261
|
+
const doc = parser.parseFromString(tryOutPanel, 'text/html');
|
|
262
|
+
const elements = doc.body.children;
|
|
263
|
+
while (elements.length > 0) {
|
|
264
|
+
panelContainer.appendChild(elements[0]);
|
|
265
|
+
}
|
|
266
|
+
leftSidebar.prepend(panelContainer);
|
|
267
|
+
|
|
268
|
+
// Add response modal to body
|
|
269
|
+
const modal = document.createElement('div');
|
|
270
|
+
modal.id = 'responseModal';
|
|
271
|
+
modal.style.display = 'none';
|
|
272
|
+
modal.setAttribute('role', 'dialog');
|
|
273
|
+
modal.setAttribute('aria-modal', 'true');
|
|
274
|
+
modal.setAttribute('aria-label', 'API Response');
|
|
275
|
+
|
|
276
|
+
const modalOverlay = document.createElement('div');
|
|
277
|
+
modalOverlay.className = 'modal-overlay';
|
|
278
|
+
modalOverlay.addEventListener('click', () => TryOutSidebar.closeResponseModal());
|
|
279
|
+
|
|
280
|
+
const modalContent = document.createElement('div');
|
|
281
|
+
modalContent.className = 'modal-content';
|
|
282
|
+
|
|
283
|
+
const modalHeader = document.createElement('div');
|
|
284
|
+
modalHeader.className = 'modal-header';
|
|
285
|
+
|
|
286
|
+
const modalTitle = document.createElement('h3');
|
|
287
|
+
modalTitle.textContent = 'API Response';
|
|
288
|
+
|
|
289
|
+
const modalClose = document.createElement('button');
|
|
290
|
+
modalClose.className = 'modal-close';
|
|
291
|
+
modalClose.setAttribute('aria-label', 'Close');
|
|
292
|
+
modalClose.textContent = '✕';
|
|
293
|
+
modalClose.addEventListener('click', () => TryOutSidebar.closeResponseModal());
|
|
294
|
+
|
|
295
|
+
modalHeader.appendChild(modalTitle);
|
|
296
|
+
modalHeader.appendChild(modalClose);
|
|
297
|
+
|
|
298
|
+
const modalBody = document.createElement('div');
|
|
299
|
+
modalBody.className = 'modal-body';
|
|
300
|
+
|
|
301
|
+
const responseHeader = document.createElement('div');
|
|
302
|
+
responseHeader.className = 'response-header';
|
|
303
|
+
|
|
304
|
+
const statusLabel = document.createElement('span');
|
|
305
|
+
statusLabel.textContent = 'Status: ';
|
|
306
|
+
|
|
307
|
+
const statusBadge = document.createElement('span');
|
|
308
|
+
statusBadge.className = 'status-badge';
|
|
309
|
+
statusBadge.id = 'modalStatusBadge';
|
|
310
|
+
|
|
311
|
+
responseHeader.appendChild(statusLabel);
|
|
312
|
+
responseHeader.appendChild(statusBadge);
|
|
313
|
+
|
|
314
|
+
const responseBody = document.createElement('div');
|
|
315
|
+
responseBody.className = 'response-body';
|
|
316
|
+
responseBody.id = 'modalResponseBody';
|
|
317
|
+
|
|
318
|
+
modalBody.appendChild(responseHeader);
|
|
319
|
+
modalBody.appendChild(responseBody);
|
|
320
|
+
|
|
321
|
+
modalContent.appendChild(modalHeader);
|
|
322
|
+
modalContent.appendChild(modalBody);
|
|
323
|
+
|
|
324
|
+
modal.appendChild(modalOverlay);
|
|
325
|
+
modal.appendChild(modalContent);
|
|
326
|
+
|
|
327
|
+
document.body.appendChild(modal);
|
|
328
|
+
|
|
329
|
+
// Initialize tabs
|
|
330
|
+
initTabs();
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
// Create mobile try-out interface
|
|
336
|
+
const createMobileTryOut = (tryOutPanel) => {
|
|
337
|
+
// Create floating action button
|
|
338
|
+
const fab = document.createElement('div');
|
|
339
|
+
fab.className = 'mobile-try-out-fab';
|
|
340
|
+
fab.setAttribute('role', 'button');
|
|
341
|
+
fab.setAttribute('tabindex', '0');
|
|
342
|
+
fab.setAttribute('aria-label', 'Open Try It Out');
|
|
343
|
+
fab.addEventListener('click', () => TryOutSidebar.openMobileTryOut());
|
|
344
|
+
|
|
345
|
+
const fabIcon = document.createElement('span');
|
|
346
|
+
fabIcon.textContent = '🚀';
|
|
347
|
+
fab.appendChild(fabIcon);
|
|
348
|
+
|
|
349
|
+
document.body.appendChild(fab);
|
|
350
|
+
|
|
351
|
+
// Create mobile modal
|
|
352
|
+
const mobileModal = document.createElement('div');
|
|
353
|
+
mobileModal.className = 'mobile-try-out-modal';
|
|
354
|
+
mobileModal.id = 'mobileTryOutModal';
|
|
355
|
+
mobileModal.style.display = 'none';
|
|
356
|
+
|
|
357
|
+
const mobileOverlay = document.createElement('div');
|
|
358
|
+
mobileOverlay.className = 'mobile-modal-overlay';
|
|
359
|
+
mobileOverlay.addEventListener('click', () => TryOutSidebar.closeMobileTryOut());
|
|
360
|
+
|
|
361
|
+
const mobileContent = document.createElement('div');
|
|
362
|
+
mobileContent.className = 'mobile-modal-content';
|
|
363
|
+
|
|
364
|
+
const mobileHeader = document.createElement('div');
|
|
365
|
+
mobileHeader.className = 'mobile-modal-header';
|
|
366
|
+
|
|
367
|
+
const mobileTitle = document.createElement('h3');
|
|
368
|
+
mobileTitle.textContent = '🚀 Try It Out';
|
|
369
|
+
|
|
370
|
+
const mobileClose = document.createElement('button');
|
|
371
|
+
mobileClose.className = 'mobile-modal-close';
|
|
372
|
+
mobileClose.setAttribute('aria-label', 'Close');
|
|
373
|
+
mobileClose.textContent = '✕';
|
|
374
|
+
mobileClose.addEventListener('click', () => TryOutSidebar.closeMobileTryOut());
|
|
375
|
+
|
|
376
|
+
mobileHeader.appendChild(mobileTitle);
|
|
377
|
+
mobileHeader.appendChild(mobileClose);
|
|
378
|
+
|
|
379
|
+
const mobileBody = document.createElement('div');
|
|
380
|
+
mobileBody.className = 'mobile-modal-body';
|
|
381
|
+
mobileBody.id = 'mobileTryOutBody';
|
|
382
|
+
|
|
383
|
+
mobileContent.appendChild(mobileHeader);
|
|
384
|
+
mobileContent.appendChild(mobileBody);
|
|
385
|
+
|
|
386
|
+
mobileModal.appendChild(mobileOverlay);
|
|
387
|
+
mobileModal.appendChild(mobileContent);
|
|
388
|
+
|
|
389
|
+
document.body.appendChild(mobileModal);
|
|
390
|
+
|
|
391
|
+
// Mount panel content into mobile body
|
|
392
|
+
const parser = new DOMParser();
|
|
393
|
+
const doc = parser.parseFromString(tryOutPanel, 'text/html');
|
|
394
|
+
const panelEl = doc.querySelector('.try-out-sidebar');
|
|
395
|
+
if (panelEl) {
|
|
396
|
+
panelEl.classList.add('mobile-try-out');
|
|
397
|
+
document.getElementById('mobileTryOutBody').appendChild(panelEl);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Add response modal for mobile
|
|
401
|
+
let responseModal = document.getElementById('responseModal');
|
|
402
|
+
if (!responseModal) {
|
|
403
|
+
responseModal = document.createElement('div');
|
|
404
|
+
responseModal.id = 'responseModal';
|
|
405
|
+
responseModal.style.display = 'none';
|
|
406
|
+
responseModal.setAttribute('role', 'dialog');
|
|
407
|
+
responseModal.setAttribute('aria-modal', 'true');
|
|
408
|
+
responseModal.setAttribute('aria-label', 'API Response');
|
|
409
|
+
|
|
410
|
+
const modalOverlay = document.createElement('div');
|
|
411
|
+
modalOverlay.className = 'modal-overlay';
|
|
412
|
+
modalOverlay.addEventListener('click', () => TryOutSidebar.closeResponseModal());
|
|
413
|
+
|
|
414
|
+
const modalContent = document.createElement('div');
|
|
415
|
+
modalContent.className = 'modal-content';
|
|
416
|
+
|
|
417
|
+
const modalHeader = document.createElement('div');
|
|
418
|
+
modalHeader.className = 'modal-header';
|
|
419
|
+
|
|
420
|
+
const modalTitle = document.createElement('h3');
|
|
421
|
+
modalTitle.textContent = 'API Response';
|
|
422
|
+
|
|
423
|
+
const modalClose = document.createElement('button');
|
|
424
|
+
modalClose.className = 'modal-close';
|
|
425
|
+
modalClose.setAttribute('aria-label', 'Close');
|
|
426
|
+
modalClose.textContent = '✕';
|
|
427
|
+
modalClose.addEventListener('click', () => TryOutSidebar.closeResponseModal());
|
|
428
|
+
|
|
429
|
+
modalHeader.appendChild(modalTitle);
|
|
430
|
+
modalHeader.appendChild(modalClose);
|
|
431
|
+
|
|
432
|
+
const modalBody = document.createElement('div');
|
|
433
|
+
modalBody.className = 'modal-body';
|
|
434
|
+
|
|
435
|
+
const responseHeader = document.createElement('div');
|
|
436
|
+
responseHeader.className = 'response-header';
|
|
437
|
+
|
|
438
|
+
const statusLabel = document.createElement('span');
|
|
439
|
+
statusLabel.textContent = 'Status: ';
|
|
440
|
+
|
|
441
|
+
const statusBadge = document.createElement('span');
|
|
442
|
+
statusBadge.className = 'status-badge';
|
|
443
|
+
statusBadge.id = 'modalStatusBadge';
|
|
444
|
+
|
|
445
|
+
responseHeader.appendChild(statusLabel);
|
|
446
|
+
responseHeader.appendChild(statusBadge);
|
|
447
|
+
|
|
448
|
+
const responseBody = document.createElement('div');
|
|
449
|
+
responseBody.className = 'response-body';
|
|
450
|
+
responseBody.id = 'modalResponseBody';
|
|
451
|
+
|
|
452
|
+
modalBody.appendChild(responseHeader);
|
|
453
|
+
modalBody.appendChild(responseBody);
|
|
454
|
+
|
|
455
|
+
modalContent.appendChild(modalHeader);
|
|
456
|
+
modalContent.appendChild(modalBody);
|
|
457
|
+
|
|
458
|
+
responseModal.appendChild(modalOverlay);
|
|
459
|
+
responseModal.appendChild(modalContent);
|
|
460
|
+
|
|
461
|
+
document.body.appendChild(responseModal);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Initialize tabs for mobile
|
|
465
|
+
setTimeout(() => initTabs(), 100);
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
// Initialize tabs
|
|
469
|
+
const initTabs = () => {
|
|
470
|
+
document.querySelectorAll('.try-out-sidebar .tab').forEach(tab => {
|
|
471
|
+
tab.addEventListener('click', () => {
|
|
472
|
+
// Remove active class from all tabs and contents
|
|
473
|
+
document.querySelectorAll('.try-out-sidebar .tab').forEach(t => t.classList.remove('active'));
|
|
474
|
+
document.querySelectorAll('.try-out-sidebar .tab-content').forEach(c => c.classList.remove('active'));
|
|
475
|
+
|
|
476
|
+
// Add active class to clicked tab and corresponding content
|
|
477
|
+
tab.classList.add('active');
|
|
478
|
+
const tabName = tab.getAttribute('data-tab');
|
|
479
|
+
document.getElementById(tabName + 'Tab').classList.add('active');
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
// Add try-out panel to sidebar
|
|
485
|
+
addTryOutToSidebar();
|
|
486
|
+
|
|
487
|
+
// Handle window resize
|
|
488
|
+
let resizeTimeout;
|
|
489
|
+
window.addEventListener('resize', () => {
|
|
490
|
+
clearTimeout(resizeTimeout);
|
|
491
|
+
resizeTimeout = setTimeout(() => {
|
|
492
|
+
// Re-initialize on resize to handle mobile/desktop transitions
|
|
493
|
+
const currentMobile = window.innerWidth <= 480;
|
|
494
|
+
const currentTablet = window.innerWidth > 480 && window.innerWidth <= 1220;
|
|
495
|
+
const fab = document.querySelector('.mobile-try-out-fab');
|
|
496
|
+
const sidebar = document.querySelector('.md-sidebar--primary .try-out-sidebar');
|
|
497
|
+
|
|
498
|
+
if ((currentMobile || currentTablet) && sidebar && !fab) {
|
|
499
|
+
// Switched to mobile/tablet, need to create mobile interface
|
|
500
|
+
location.reload(); // Simple solution to reinitialize
|
|
501
|
+
} else if (!currentMobile && !currentTablet && fab && !sidebar) {
|
|
502
|
+
// Switched to desktop, need to create sidebar interface
|
|
503
|
+
location.reload(); // Simple solution to reinitialize
|
|
504
|
+
}
|
|
505
|
+
}, 250);
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
// Create namespace to avoid global pollution
|
|
510
|
+
window.TryOutSidebar = {
|
|
511
|
+
openMobileTryOut: function() {
|
|
512
|
+
const modal = document.getElementById('mobileTryOutModal');
|
|
513
|
+
if (modal) {
|
|
514
|
+
modal.style.display = 'block';
|
|
515
|
+
document.body.style.overflow = 'hidden';
|
|
516
|
+
}
|
|
517
|
+
},
|
|
518
|
+
|
|
519
|
+
closeMobileTryOut: function() {
|
|
520
|
+
const modal = document.getElementById('mobileTryOutModal');
|
|
521
|
+
if (modal) {
|
|
522
|
+
modal.style.display = 'none';
|
|
523
|
+
document.body.style.overflow = '';
|
|
524
|
+
}
|
|
525
|
+
},
|
|
526
|
+
|
|
527
|
+
addQueryParam: function() {
|
|
528
|
+
const container = document.getElementById('queryParams');
|
|
529
|
+
if (!container) return;
|
|
530
|
+
|
|
531
|
+
const kvItem = document.createElement('div');
|
|
532
|
+
kvItem.className = 'kv-item';
|
|
533
|
+
|
|
534
|
+
const nameInput = document.createElement('input');
|
|
535
|
+
nameInput.type = 'text';
|
|
536
|
+
nameInput.placeholder = 'Parameter name';
|
|
537
|
+
nameInput.setAttribute('list', 'queryParamSuggestions');
|
|
538
|
+
|
|
539
|
+
const valueInput = document.createElement('input');
|
|
540
|
+
valueInput.type = 'text';
|
|
541
|
+
valueInput.placeholder = 'Parameter value';
|
|
542
|
+
|
|
543
|
+
const removeBtn = document.createElement('button');
|
|
544
|
+
removeBtn.className = 'remove-btn';
|
|
545
|
+
removeBtn.textContent = '✕';
|
|
546
|
+
removeBtn.addEventListener('click', () => TryOutSidebar.removeKvItem(removeBtn));
|
|
547
|
+
|
|
548
|
+
kvItem.appendChild(nameInput);
|
|
549
|
+
kvItem.appendChild(valueInput);
|
|
550
|
+
kvItem.appendChild(removeBtn);
|
|
551
|
+
container.appendChild(kvItem);
|
|
552
|
+
},
|
|
553
|
+
|
|
554
|
+
addHeader: function() {
|
|
555
|
+
const container = document.getElementById('requestHeaders');
|
|
556
|
+
if (!container) return;
|
|
557
|
+
|
|
558
|
+
const kvItem = document.createElement('div');
|
|
559
|
+
kvItem.className = 'kv-item';
|
|
560
|
+
|
|
561
|
+
const nameInput = document.createElement('input');
|
|
562
|
+
nameInput.type = 'text';
|
|
563
|
+
nameInput.placeholder = 'Header name';
|
|
564
|
+
nameInput.setAttribute('list', 'headerSuggestions');
|
|
565
|
+
|
|
566
|
+
const valueInput = document.createElement('input');
|
|
567
|
+
valueInput.type = 'text';
|
|
568
|
+
valueInput.placeholder = 'Header value';
|
|
569
|
+
|
|
570
|
+
const removeBtn = document.createElement('button');
|
|
571
|
+
removeBtn.className = 'remove-btn';
|
|
572
|
+
removeBtn.textContent = '✕';
|
|
573
|
+
removeBtn.addEventListener('click', () => TryOutSidebar.removeKvItem(removeBtn));
|
|
574
|
+
|
|
575
|
+
kvItem.appendChild(nameInput);
|
|
576
|
+
kvItem.appendChild(valueInput);
|
|
577
|
+
kvItem.appendChild(removeBtn);
|
|
578
|
+
container.appendChild(kvItem);
|
|
579
|
+
},
|
|
580
|
+
|
|
581
|
+
removeKvItem: function(button) {
|
|
582
|
+
if (button && button.parentElement) {
|
|
583
|
+
button.parentElement.remove();
|
|
584
|
+
TryOutSidebar.updateUrlFromParams();
|
|
585
|
+
}
|
|
586
|
+
},
|
|
587
|
+
|
|
588
|
+
updateUrlFromParams: function() {
|
|
589
|
+
// This function is no longer needed since we don't show the full URL
|
|
590
|
+
},
|
|
591
|
+
|
|
592
|
+
closeResponseModal: function() {
|
|
593
|
+
const modal = document.getElementById('responseModal');
|
|
594
|
+
if (modal) {
|
|
595
|
+
modal.style.display = 'none';
|
|
596
|
+
}
|
|
597
|
+
},
|
|
598
|
+
|
|
599
|
+
showResponseModal: function(status, responseText) {
|
|
600
|
+
const modal = document.getElementById('responseModal');
|
|
601
|
+
const statusBadge = document.getElementById('modalStatusBadge');
|
|
602
|
+
const responseBody = document.getElementById('modalResponseBody');
|
|
603
|
+
|
|
604
|
+
if (modal && statusBadge && responseBody) {
|
|
605
|
+
statusBadge.textContent = String(status);
|
|
606
|
+
const code = Number(status);
|
|
607
|
+
statusBadge.className = 'status-badge' + (Number.isFinite(code) ? ` status-${Math.floor(code/100)*100}` : '');
|
|
608
|
+
|
|
609
|
+
try {
|
|
610
|
+
const jsonResponse = JSON.parse(responseText);
|
|
611
|
+
responseBody.textContent = JSON.stringify(jsonResponse, null, 2);
|
|
612
|
+
} catch (e) {
|
|
613
|
+
responseBody.textContent = responseText;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
modal.style.display = 'block';
|
|
617
|
+
}
|
|
618
|
+
},
|
|
619
|
+
|
|
620
|
+
validateRequiredParams: function() {
|
|
621
|
+
const requiredInputs = document.querySelectorAll('#pathParams input[required]');
|
|
622
|
+
const emptyParams = [];
|
|
623
|
+
|
|
624
|
+
requiredInputs.forEach(input => {
|
|
625
|
+
if (!input.value.trim()) {
|
|
626
|
+
const paramName = input.getAttribute('data-param');
|
|
627
|
+
emptyParams.push(paramName);
|
|
628
|
+
input.classList.add('error');
|
|
629
|
+
input.addEventListener('input', () => input.classList.remove('error'), { once: true });
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
return emptyParams;
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
// Legacy global functions for backward compatibility (deprecated)
|
|
638
|
+
function openMobileTryOut() { TryOutSidebar.openMobileTryOut(); }
|
|
639
|
+
function closeMobileTryOut() { TryOutSidebar.closeMobileTryOut(); }
|
|
640
|
+
function addQueryParam() { TryOutSidebar.addQueryParam(); }
|
|
641
|
+
function addHeader() { TryOutSidebar.addHeader(); }
|
|
642
|
+
function removeKvItem(button) { TryOutSidebar.removeKvItem(button); }
|
|
643
|
+
function updateUrlFromParams() { TryOutSidebar.updateUrlFromParams(); }
|
|
644
|
+
function closeResponseModal() { TryOutSidebar.closeResponseModal(); }
|
|
645
|
+
function showResponseModal(status, responseText) { TryOutSidebar.showResponseModal(status, responseText); }
|
|
646
|
+
function validateRequiredParams() { return TryOutSidebar.validateRequiredParams(); }
|
|
647
|
+
|
|
648
|
+
async function executeRequest() {
|
|
649
|
+
const executeBtn = document.getElementById('executeBtn');
|
|
650
|
+
if (!executeBtn) return;
|
|
651
|
+
|
|
652
|
+
// Validate required parameters
|
|
653
|
+
const emptyParams = TryOutSidebar.validateRequiredParams();
|
|
654
|
+
if (emptyParams.length > 0) {
|
|
655
|
+
alert(`Please fill in the required parameters: ${emptyParams.join(', ')}`);
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Update button state
|
|
660
|
+
executeBtn.disabled = true;
|
|
661
|
+
executeBtn.textContent = '';
|
|
662
|
+
const spinner = document.createElement('div');
|
|
663
|
+
spinner.className = 'spinner';
|
|
664
|
+
const text = document.createTextNode(' Sending...');
|
|
665
|
+
executeBtn.appendChild(spinner);
|
|
666
|
+
executeBtn.appendChild(text);
|
|
667
|
+
|
|
668
|
+
try {
|
|
669
|
+
// Get base URL and construct full URL
|
|
670
|
+
// Validate and normalize the Base URL, restricting scheme to http/https
|
|
671
|
+
const baseInput = (document.getElementById('baseUrl').value || '').trim() || window.location.origin;
|
|
672
|
+
let base;
|
|
673
|
+
try {
|
|
674
|
+
base = new URL(baseInput, window.location.origin);
|
|
675
|
+
} catch (_) {
|
|
676
|
+
throw new Error('Invalid Base URL');
|
|
677
|
+
}
|
|
678
|
+
if (!/^https?:$/.test(base.protocol)) {
|
|
679
|
+
throw new Error('Base URL must use http or https');
|
|
680
|
+
}
|
|
681
|
+
const baseUrl = base.href;
|
|
682
|
+
// Get endpoint info from the current page
|
|
683
|
+
let path = '';
|
|
684
|
+
let method = 'GET';
|
|
685
|
+
|
|
686
|
+
// First, try to find the method badge and code element in the same paragraph
|
|
687
|
+
const methodBadge = document.querySelector('.method-badge');
|
|
688
|
+
if (methodBadge) {
|
|
689
|
+
method = methodBadge.textContent.trim();
|
|
690
|
+
|
|
691
|
+
// Look for a code element in the same parent or nearby
|
|
692
|
+
const parent = methodBadge.closest('p, div, section');
|
|
693
|
+
if (parent) {
|
|
694
|
+
const codeElement = parent.querySelector('code');
|
|
695
|
+
if (codeElement) {
|
|
696
|
+
path = codeElement.textContent.trim();
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// Fallback: try to find in title
|
|
702
|
+
if (!path) {
|
|
703
|
+
const title = document.querySelector('h1');
|
|
704
|
+
if (title) {
|
|
705
|
+
const titleText = title.textContent.trim();
|
|
706
|
+
const pathMatch = titleText.match(/([A-Z]+)\s+(.+)/);
|
|
707
|
+
if (pathMatch) {
|
|
708
|
+
method = pathMatch[1];
|
|
709
|
+
path = pathMatch[2];
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// Additional fallback - look for code blocks with paths
|
|
715
|
+
if (!path) {
|
|
716
|
+
const codeBlocks = document.querySelectorAll('code');
|
|
717
|
+
for (const code of codeBlocks) {
|
|
718
|
+
const text = code.textContent.trim();
|
|
719
|
+
if (text.startsWith('/') && !text.includes('http')) {
|
|
720
|
+
path = text;
|
|
721
|
+
break;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// Clean up the path to handle HTML entities and special characters
|
|
727
|
+
if (path) {
|
|
728
|
+
// Use DOMParser to safely decode HTML entities
|
|
729
|
+
const parser = new DOMParser();
|
|
730
|
+
const doc = parser.parseFromString(`<div>${path}</div>`, 'text/html');
|
|
731
|
+
const tempDiv = doc.querySelector('div');
|
|
732
|
+
path = tempDiv ? (tempDiv.textContent || tempDiv.innerText || path) : path;
|
|
733
|
+
|
|
734
|
+
// Remove any non-printable characters or replace problematic ones
|
|
735
|
+
path = path.replace(/[^\x20-\x7E]/g, ''); // Remove non-ASCII printable characters
|
|
736
|
+
path = path.replace(/¶/g, ''); // Specifically remove paragraph symbols
|
|
737
|
+
path = path.trim();
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
console.log('Extracted path:', path);
|
|
741
|
+
console.log('Extracted method:', method);
|
|
742
|
+
|
|
743
|
+
// Ensure baseUrl doesn't end with slash and path starts with slash
|
|
744
|
+
let cleanBaseUrl = baseUrl.replace(/\/$/, '');
|
|
745
|
+
let cleanPath = path || '/api/endpoint';
|
|
746
|
+
if (!cleanPath.startsWith('/')) {
|
|
747
|
+
cleanPath = '/' + cleanPath;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
let url = cleanBaseUrl + cleanPath;
|
|
751
|
+
console.log('Initial URL:', url);
|
|
752
|
+
|
|
753
|
+
// Replace path parameters
|
|
754
|
+
const pathParams = document.querySelectorAll('#pathParams .kv-item');
|
|
755
|
+
console.log('Found path params:', pathParams.length);
|
|
756
|
+
|
|
757
|
+
pathParams.forEach((item, index) => {
|
|
758
|
+
const label = item.querySelector('.param-label');
|
|
759
|
+
const input = item.querySelector('input');
|
|
760
|
+
if (label && input) {
|
|
761
|
+
const paramName = label.textContent.trim();
|
|
762
|
+
const paramValue = input.value.trim();
|
|
763
|
+
console.log(`Param ${index}: ${paramName} = ${paramValue}`);
|
|
764
|
+
|
|
765
|
+
if (paramName && paramValue) {
|
|
766
|
+
const beforeReplace = url;
|
|
767
|
+
const escaped = paramName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
768
|
+
url = url.replace(new RegExp(`\\{${escaped}\\}`, 'g'), paramValue);
|
|
769
|
+
console.log(`URL after replacing {${paramName}}: ${beforeReplace} -> ${url}`);
|
|
770
|
+
} else if (paramName && !paramValue) {
|
|
771
|
+
// This should not happen as validation should catch empty required params
|
|
772
|
+
console.warn(`Empty value for required parameter: ${paramName}`);
|
|
773
|
+
throw new Error(`Required parameter '${paramName}' has no value`);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
// Clean up any remaining unreplaced parameters or malformed URLs
|
|
779
|
+
const beforeCleanup = url;
|
|
780
|
+
|
|
781
|
+
// Remove double slashes but preserve protocol slashes
|
|
782
|
+
url = url.replace(/([^:])\/+/g, '$1/'); // Remove double slashes except after protocol
|
|
783
|
+
url = url.replace(/\{[^}]*\}/g, ''); // Remove any remaining parameter placeholders
|
|
784
|
+
|
|
785
|
+
// Don't remove trailing slash as it might be part of the endpoint
|
|
786
|
+
|
|
787
|
+
console.log('Final URL:', beforeCleanup, '->', url);
|
|
788
|
+
|
|
789
|
+
// Add query parameters
|
|
790
|
+
const queryParams = {};
|
|
791
|
+
document.querySelectorAll('#queryParams .kv-item').forEach(item => {
|
|
792
|
+
const inputs = item.querySelectorAll('input');
|
|
793
|
+
if (inputs.length >= 2) {
|
|
794
|
+
const key = inputs[0].value.trim();
|
|
795
|
+
const value = inputs[1].value.trim();
|
|
796
|
+
if (key && value) {
|
|
797
|
+
queryParams[key] = value;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
if (Object.keys(queryParams).length > 0) {
|
|
803
|
+
const queryString = new URLSearchParams(queryParams).toString();
|
|
804
|
+
url += (url.includes('?') ? '&' : '?') + queryString;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// Get headers
|
|
808
|
+
const headers = {};
|
|
809
|
+
document.querySelectorAll('#requestHeaders .kv-item').forEach(item => {
|
|
810
|
+
const inputs = item.querySelectorAll('input');
|
|
811
|
+
if (inputs.length >= 2) {
|
|
812
|
+
const key = inputs[0].value.trim();
|
|
813
|
+
const value = inputs[1].value.trim();
|
|
814
|
+
if (key && value) {
|
|
815
|
+
headers[key] = value;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
// Prepare request options
|
|
821
|
+
const requestOptions = {
|
|
822
|
+
method: method,
|
|
823
|
+
headers: headers
|
|
824
|
+
};
|
|
825
|
+
|
|
826
|
+
// Add request body for POST, PUT, PATCH
|
|
827
|
+
if (['POST', 'PUT', 'PATCH'].includes(method)) {
|
|
828
|
+
const bodyInput = document.getElementById('requestBody');
|
|
829
|
+
if (bodyInput && bodyInput.value.trim()) {
|
|
830
|
+
try {
|
|
831
|
+
// Validate JSON
|
|
832
|
+
JSON.parse(bodyInput.value);
|
|
833
|
+
requestOptions.body = bodyInput.value;
|
|
834
|
+
if (!headers['Content-Type']) {
|
|
835
|
+
requestOptions.headers['Content-Type'] = 'application/json';
|
|
836
|
+
}
|
|
837
|
+
} catch (e) {
|
|
838
|
+
throw new Error('Invalid JSON in request body');
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// Send the request
|
|
844
|
+
const response = await fetch(url, requestOptions);
|
|
845
|
+
const responseText = await response.text();
|
|
846
|
+
|
|
847
|
+
// Show response in modal
|
|
848
|
+
TryOutSidebar.showResponseModal(response.status, responseText);
|
|
849
|
+
|
|
850
|
+
} catch (error) {
|
|
851
|
+
// Enhanced error handling with specific error types
|
|
852
|
+
let errorMessage = 'Unknown error occurred';
|
|
853
|
+
let errorType = 'Error';
|
|
854
|
+
|
|
855
|
+
if (error.name === 'TypeError' && error.message.includes('fetch')) {
|
|
856
|
+
errorType = 'Network Error';
|
|
857
|
+
errorMessage = 'Failed to connect to the server. Please check your internet connection and try again.';
|
|
858
|
+
} else if (error.name === 'SyntaxError' && error.message.includes('JSON')) {
|
|
859
|
+
errorType = 'JSON Parse Error';
|
|
860
|
+
errorMessage = 'Invalid JSON in request body. Please check your input and try again.';
|
|
861
|
+
} else if (error.message.includes('CORS')) {
|
|
862
|
+
errorType = 'CORS Error';
|
|
863
|
+
errorMessage = 'Cross-origin request blocked. The server may not allow requests from this domain.';
|
|
864
|
+
} else if (error.message) {
|
|
865
|
+
errorMessage = error.message;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
TryOutSidebar.showResponseModal(errorType, errorMessage);
|
|
869
|
+
} finally {
|
|
870
|
+
// Reset button
|
|
871
|
+
executeBtn.disabled = false;
|
|
872
|
+
executeBtn.textContent = '';
|
|
873
|
+
const playIcon = document.createElement('span');
|
|
874
|
+
playIcon.textContent = '▶';
|
|
875
|
+
const text = document.createTextNode(' Execute Request');
|
|
876
|
+
executeBtn.appendChild(playIcon);
|
|
877
|
+
executeBtn.appendChild(text);
|
|
878
|
+
}
|
|
879
|
+
}
|