Spaces:
Running
Running
Add Copy as curl/Python/fetch/HTTPie + Preview tab for HTML/JSON/CSV/XML/Markdown
Browse files
app.py
CHANGED
|
@@ -157,6 +157,69 @@ def ui():
|
|
| 157 |
.send-btn:hover { background: #0056b3; }
|
| 158 |
.send-btn:disabled { background: #86868b; cursor: not-allowed; }
|
| 159 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
/* Main Layout */
|
| 161 |
.main-layout {
|
| 162 |
display: grid;
|
|
@@ -317,6 +380,56 @@ def ui():
|
|
| 317 |
.json-number { color: #1c00cf; }
|
| 318 |
.json-boolean { color: #aa0d91; }
|
| 319 |
.json-null { color: #86868b; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 320 |
</style>
|
| 321 |
</head>
|
| 322 |
<body>
|
|
@@ -339,7 +452,29 @@ def ui():
|
|
| 339 |
</select>
|
| 340 |
<input type="text" class="url-input" id="url" placeholder="/hello" value="/hello">
|
| 341 |
<button class="send-btn" id="send">Send</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 342 |
</div>
|
|
|
|
| 343 |
|
| 344 |
<div class="main-layout">
|
| 345 |
<!-- Request Panel -->
|
|
@@ -368,12 +503,18 @@ def ui():
|
|
| 368 |
</div>
|
| 369 |
<div class="tabs" id="response-tabs">
|
| 370 |
<button class="tab active" data-tab="res-body">Body</button>
|
|
|
|
| 371 |
<button class="tab" data-tab="res-headers">Headers</button>
|
| 372 |
<button class="tab" data-tab="res-raw">Raw</button>
|
| 373 |
</div>
|
| 374 |
<div class="tab-content active" id="res-body-tab">
|
| 375 |
<div class="response-content" id="response">Send a request to see the response</div>
|
| 376 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 377 |
<div class="tab-content" id="res-headers-tab">
|
| 378 |
<div class="panel-body">
|
| 379 |
<div class="headers-list" id="response-headers"></div>
|
|
@@ -520,6 +661,9 @@ def ui():
|
|
| 520 |
}
|
| 521 |
document.getElementById('response').innerHTML = display;
|
| 522 |
|
|
|
|
|
|
|
|
|
|
| 523 |
} catch (err) {
|
| 524 |
document.getElementById('status').textContent = 'Error';
|
| 525 |
document.getElementById('status').className = 'status-value status-err';
|
|
@@ -535,6 +679,233 @@ def ui():
|
|
| 535 |
document.getElementById('send').onclick = sendRequest;
|
| 536 |
document.getElementById('url').onkeydown = (e) => { if (e.key === 'Enter') sendRequest(); };
|
| 537 |
sendRequest();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 538 |
</script>
|
| 539 |
</body>
|
| 540 |
</html>
|
|
|
|
| 157 |
.send-btn:hover { background: #0056b3; }
|
| 158 |
.send-btn:disabled { background: #86868b; cursor: not-allowed; }
|
| 159 |
|
| 160 |
+
/* Copy dropdown */
|
| 161 |
+
.copy-dropdown {
|
| 162 |
+
position: relative;
|
| 163 |
+
}
|
| 164 |
+
.copy-btn {
|
| 165 |
+
padding: 10px 16px;
|
| 166 |
+
background: #fff;
|
| 167 |
+
border: 1px solid #d2d2d7;
|
| 168 |
+
border-radius: 6px;
|
| 169 |
+
cursor: pointer;
|
| 170 |
+
font-weight: 500;
|
| 171 |
+
color: #1d1d1f;
|
| 172 |
+
transition: all 0.2s;
|
| 173 |
+
}
|
| 174 |
+
.copy-btn:hover { background: #f5f5f7; border-color: #007aff; }
|
| 175 |
+
.copy-menu {
|
| 176 |
+
display: none;
|
| 177 |
+
position: absolute;
|
| 178 |
+
top: 100%;
|
| 179 |
+
right: 0;
|
| 180 |
+
margin-top: 4px;
|
| 181 |
+
background: #fff;
|
| 182 |
+
border: 1px solid #d2d2d7;
|
| 183 |
+
border-radius: 8px;
|
| 184 |
+
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
| 185 |
+
z-index: 100;
|
| 186 |
+
min-width: 160px;
|
| 187 |
+
overflow: hidden;
|
| 188 |
+
}
|
| 189 |
+
.copy-menu.show { display: block; }
|
| 190 |
+
.copy-option {
|
| 191 |
+
display: block;
|
| 192 |
+
width: 100%;
|
| 193 |
+
padding: 10px 14px;
|
| 194 |
+
text-align: left;
|
| 195 |
+
background: none;
|
| 196 |
+
border: none;
|
| 197 |
+
cursor: pointer;
|
| 198 |
+
font-size: 13px;
|
| 199 |
+
color: #1d1d1f;
|
| 200 |
+
transition: background 0.1s;
|
| 201 |
+
}
|
| 202 |
+
.copy-option:hover { background: #f5f5f7; }
|
| 203 |
+
.copy-option-label { font-weight: 500; }
|
| 204 |
+
.copy-option-desc { font-size: 11px; color: #86868b; }
|
| 205 |
+
|
| 206 |
+
/* Toast notification */
|
| 207 |
+
.toast {
|
| 208 |
+
position: fixed;
|
| 209 |
+
bottom: 20px;
|
| 210 |
+
left: 50%;
|
| 211 |
+
transform: translateX(-50%) translateY(100px);
|
| 212 |
+
background: #1d1d1f;
|
| 213 |
+
color: #fff;
|
| 214 |
+
padding: 12px 24px;
|
| 215 |
+
border-radius: 8px;
|
| 216 |
+
font-size: 13px;
|
| 217 |
+
opacity: 0;
|
| 218 |
+
transition: all 0.3s;
|
| 219 |
+
z-index: 1000;
|
| 220 |
+
}
|
| 221 |
+
.toast.show { transform: translateX(-50%) translateY(0); opacity: 1; }
|
| 222 |
+
|
| 223 |
/* Main Layout */
|
| 224 |
.main-layout {
|
| 225 |
display: grid;
|
|
|
|
| 380 |
.json-number { color: #1c00cf; }
|
| 381 |
.json-boolean { color: #aa0d91; }
|
| 382 |
.json-null { color: #86868b; }
|
| 383 |
+
|
| 384 |
+
/* Preview */
|
| 385 |
+
.preview-container {
|
| 386 |
+
padding: 16px;
|
| 387 |
+
min-height: 200px;
|
| 388 |
+
max-height: 500px;
|
| 389 |
+
overflow: auto;
|
| 390 |
+
}
|
| 391 |
+
.preview-placeholder { color: #86868b; }
|
| 392 |
+
.preview-iframe {
|
| 393 |
+
width: 100%;
|
| 394 |
+
min-height: 400px;
|
| 395 |
+
border: 1px solid #d2d2d7;
|
| 396 |
+
border-radius: 6px;
|
| 397 |
+
background: #fff;
|
| 398 |
+
}
|
| 399 |
+
.preview-image {
|
| 400 |
+
max-width: 100%;
|
| 401 |
+
border-radius: 6px;
|
| 402 |
+
border: 1px solid #d2d2d7;
|
| 403 |
+
}
|
| 404 |
+
.preview-json {
|
| 405 |
+
background: #f5f5f7;
|
| 406 |
+
padding: 16px;
|
| 407 |
+
border-radius: 8px;
|
| 408 |
+
font-family: 'SF Mono', Menlo, monospace;
|
| 409 |
+
font-size: 13px;
|
| 410 |
+
line-height: 1.6;
|
| 411 |
+
}
|
| 412 |
+
.preview-json .key { color: #007aff; font-weight: 500; }
|
| 413 |
+
.preview-json .string { color: #c41a16; }
|
| 414 |
+
.preview-json .number { color: #1c00cf; }
|
| 415 |
+
.preview-json .boolean { color: #aa0d91; }
|
| 416 |
+
.preview-json .null { color: #86868b; font-style: italic; }
|
| 417 |
+
.preview-json .bracket { color: #1d1d1f; }
|
| 418 |
+
.preview-json details { margin-left: 20px; }
|
| 419 |
+
.preview-json summary { cursor: pointer; margin-left: -20px; }
|
| 420 |
+
.preview-json summary:hover { color: #007aff; }
|
| 421 |
+
.preview-text {
|
| 422 |
+
background: #f5f5f7;
|
| 423 |
+
padding: 16px;
|
| 424 |
+
border-radius: 8px;
|
| 425 |
+
font-family: 'SF Mono', Menlo, monospace;
|
| 426 |
+
font-size: 13px;
|
| 427 |
+
white-space: pre-wrap;
|
| 428 |
+
}
|
| 429 |
+
.preview-xml { color: #1d1d1f; }
|
| 430 |
+
.preview-xml .tag { color: #aa0d91; }
|
| 431 |
+
.preview-xml .attr { color: #007aff; }
|
| 432 |
+
.preview-xml .value { color: #c41a16; }
|
| 433 |
</style>
|
| 434 |
</head>
|
| 435 |
<body>
|
|
|
|
| 452 |
</select>
|
| 453 |
<input type="text" class="url-input" id="url" placeholder="/hello" value="/hello">
|
| 454 |
<button class="send-btn" id="send">Send</button>
|
| 455 |
+
<div class="copy-dropdown">
|
| 456 |
+
<button class="copy-btn" id="copy-toggle">Copy as...</button>
|
| 457 |
+
<div class="copy-menu" id="copy-menu">
|
| 458 |
+
<button class="copy-option" data-format="curl">
|
| 459 |
+
<div class="copy-option-label">cURL</div>
|
| 460 |
+
<div class="copy-option-desc">Command line</div>
|
| 461 |
+
</button>
|
| 462 |
+
<button class="copy-option" data-format="python">
|
| 463 |
+
<div class="copy-option-label">Python requests</div>
|
| 464 |
+
<div class="copy-option-desc">requests library</div>
|
| 465 |
+
</button>
|
| 466 |
+
<button class="copy-option" data-format="fetch">
|
| 467 |
+
<div class="copy-option-label">JavaScript fetch</div>
|
| 468 |
+
<div class="copy-option-desc">Browser/Node.js</div>
|
| 469 |
+
</button>
|
| 470 |
+
<button class="copy-option" data-format="httpie">
|
| 471 |
+
<div class="copy-option-label">HTTPie</div>
|
| 472 |
+
<div class="copy-option-desc">CLI for humans</div>
|
| 473 |
+
</button>
|
| 474 |
+
</div>
|
| 475 |
+
</div>
|
| 476 |
</div>
|
| 477 |
+
<div class="toast" id="toast">Copied to clipboard!</div>
|
| 478 |
|
| 479 |
<div class="main-layout">
|
| 480 |
<!-- Request Panel -->
|
|
|
|
| 503 |
</div>
|
| 504 |
<div class="tabs" id="response-tabs">
|
| 505 |
<button class="tab active" data-tab="res-body">Body</button>
|
| 506 |
+
<button class="tab" data-tab="res-preview">Preview</button>
|
| 507 |
<button class="tab" data-tab="res-headers">Headers</button>
|
| 508 |
<button class="tab" data-tab="res-raw">Raw</button>
|
| 509 |
</div>
|
| 510 |
<div class="tab-content active" id="res-body-tab">
|
| 511 |
<div class="response-content" id="response">Send a request to see the response</div>
|
| 512 |
</div>
|
| 513 |
+
<div class="tab-content" id="res-preview-tab">
|
| 514 |
+
<div class="preview-container" id="preview-container">
|
| 515 |
+
<div class="preview-placeholder">Send a request to see preview</div>
|
| 516 |
+
</div>
|
| 517 |
+
</div>
|
| 518 |
<div class="tab-content" id="res-headers-tab">
|
| 519 |
<div class="panel-body">
|
| 520 |
<div class="headers-list" id="response-headers"></div>
|
|
|
|
| 661 |
}
|
| 662 |
document.getElementById('response').innerHTML = display;
|
| 663 |
|
| 664 |
+
// Preview
|
| 665 |
+
renderPreview(text, res.headers.get('content-type') || '');
|
| 666 |
+
|
| 667 |
} catch (err) {
|
| 668 |
document.getElementById('status').textContent = 'Error';
|
| 669 |
document.getElementById('status').className = 'status-value status-err';
|
|
|
|
| 679 |
document.getElementById('send').onclick = sendRequest;
|
| 680 |
document.getElementById('url').onkeydown = (e) => { if (e.key === 'Enter') sendRequest(); };
|
| 681 |
sendRequest();
|
| 682 |
+
|
| 683 |
+
// Preview rendering
|
| 684 |
+
function renderPreview(text, contentType) {
|
| 685 |
+
const container = document.getElementById('preview-container');
|
| 686 |
+
const ct = contentType.toLowerCase();
|
| 687 |
+
|
| 688 |
+
if (ct.includes('text/html')) {
|
| 689 |
+
// Render HTML in sandboxed iframe
|
| 690 |
+
const iframe = document.createElement('iframe');
|
| 691 |
+
iframe.className = 'preview-iframe';
|
| 692 |
+
iframe.sandbox = 'allow-same-origin';
|
| 693 |
+
iframe.srcdoc = text;
|
| 694 |
+
container.innerHTML = '';
|
| 695 |
+
container.appendChild(iframe);
|
| 696 |
+
} else if (ct.includes('image/')) {
|
| 697 |
+
// Show image (requires blob URL for binary)
|
| 698 |
+
const url = document.getElementById('url').value;
|
| 699 |
+
container.innerHTML = `<img class="preview-image" src="${url}" alt="Image preview">`;
|
| 700 |
+
} else if (ct.includes('application/json') || ct.includes('json')) {
|
| 701 |
+
// Interactive JSON tree
|
| 702 |
+
try {
|
| 703 |
+
const json = JSON.parse(text);
|
| 704 |
+
container.innerHTML = '<div class="preview-json">' + renderJSONTree(json) + '</div>';
|
| 705 |
+
} catch {
|
| 706 |
+
container.innerHTML = '<div class="preview-text">' + text.replace(/</g, '<') + '</div>';
|
| 707 |
+
}
|
| 708 |
+
} else if (ct.includes('xml')) {
|
| 709 |
+
// Syntax highlighted XML
|
| 710 |
+
const highlighted = text
|
| 711 |
+
.replace(/</g, '<').replace(/>/g, '>')
|
| 712 |
+
.replace(/<(\\/?[\\w:-]+)/g, '<<span class="tag">$1</span>')
|
| 713 |
+
.replace(/(\\w+)=("[^"]*")/g, '<span class="attr">$1</span>=<span class="value">$2</span>');
|
| 714 |
+
container.innerHTML = '<pre class="preview-text preview-xml">' + highlighted + '</pre>';
|
| 715 |
+
} else if (ct.includes('csv')) {
|
| 716 |
+
// Render CSV as table
|
| 717 |
+
container.innerHTML = renderCSVTable(text);
|
| 718 |
+
} else if (ct.includes('markdown')) {
|
| 719 |
+
// Basic markdown rendering
|
| 720 |
+
container.innerHTML = '<div class="preview-text">' + renderBasicMarkdown(text) + '</div>';
|
| 721 |
+
} else {
|
| 722 |
+
// Plain text
|
| 723 |
+
container.innerHTML = '<pre class="preview-text">' + text.replace(/</g, '<').replace(/>/g, '>') + '</pre>';
|
| 724 |
+
}
|
| 725 |
+
}
|
| 726 |
+
|
| 727 |
+
function renderJSONTree(obj, level = 0) {
|
| 728 |
+
if (obj === null) return '<span class="null">null</span>';
|
| 729 |
+
if (typeof obj === 'boolean') return '<span class="boolean">' + obj + '</span>';
|
| 730 |
+
if (typeof obj === 'number') return '<span class="number">' + obj + '</span>';
|
| 731 |
+
if (typeof obj === 'string') return '<span class="string">"' + obj.replace(/</g, '<') + '"</span>';
|
| 732 |
+
|
| 733 |
+
if (Array.isArray(obj)) {
|
| 734 |
+
if (obj.length === 0) return '<span class="bracket">[]</span>';
|
| 735 |
+
let html = '<span class="bracket">[</span>';
|
| 736 |
+
if (level < 2) {
|
| 737 |
+
html += '<br>' + obj.map((v, i) =>
|
| 738 |
+
' '.repeat(level + 1) + renderJSONTree(v, level + 1) + (i < obj.length - 1 ? ',' : '')
|
| 739 |
+
).join('<br>') + '<br>' + ' '.repeat(level);
|
| 740 |
+
} else {
|
| 741 |
+
html += obj.map((v, i) => renderJSONTree(v, level + 1) + (i < obj.length - 1 ? ', ' : '')).join('');
|
| 742 |
+
}
|
| 743 |
+
html += '<span class="bracket">]</span>';
|
| 744 |
+
return html;
|
| 745 |
+
}
|
| 746 |
+
|
| 747 |
+
if (typeof obj === 'object') {
|
| 748 |
+
const keys = Object.keys(obj);
|
| 749 |
+
if (keys.length === 0) return '<span class="bracket">{}</span>';
|
| 750 |
+
let html = '<span class="bracket">{</span>';
|
| 751 |
+
if (level < 2) {
|
| 752 |
+
html += '<br>' + keys.map((k, i) =>
|
| 753 |
+
' '.repeat(level + 1) + '<span class="key">"' + k + '"</span>: ' + renderJSONTree(obj[k], level + 1) + (i < keys.length - 1 ? ',' : '')
|
| 754 |
+
).join('<br>') + '<br>' + ' '.repeat(level);
|
| 755 |
+
} else {
|
| 756 |
+
html += keys.map((k, i) =>
|
| 757 |
+
'<span class="key">"' + k + '"</span>: ' + renderJSONTree(obj[k], level + 1) + (i < keys.length - 1 ? ', ' : '')
|
| 758 |
+
).join('');
|
| 759 |
+
}
|
| 760 |
+
html += '<span class="bracket">}</span>';
|
| 761 |
+
return html;
|
| 762 |
+
}
|
| 763 |
+
return String(obj);
|
| 764 |
+
}
|
| 765 |
+
|
| 766 |
+
function renderCSVTable(csv) {
|
| 767 |
+
const lines = csv.trim().split('\\n');
|
| 768 |
+
if (lines.length === 0) return '<div class="preview-text">Empty CSV</div>';
|
| 769 |
+
let html = '<table style="width:100%;border-collapse:collapse;font-size:13px;">';
|
| 770 |
+
lines.forEach((line, i) => {
|
| 771 |
+
const cells = line.split(',');
|
| 772 |
+
const tag = i === 0 ? 'th' : 'td';
|
| 773 |
+
const style = 'padding:8px 12px;border:1px solid #d2d2d7;text-align:left;' + (i === 0 ? 'background:#f5f5f7;font-weight:600;' : '');
|
| 774 |
+
html += '<tr>' + cells.map(c => `<${tag} style="${style}">${c.trim()}</${tag}>`).join('') + '</tr>';
|
| 775 |
+
});
|
| 776 |
+
html += '</table>';
|
| 777 |
+
return html;
|
| 778 |
+
}
|
| 779 |
+
|
| 780 |
+
function renderBasicMarkdown(md) {
|
| 781 |
+
return md
|
| 782 |
+
.replace(/^### (.+)$/gm, '<h3 style="margin:12px 0 8px;font-size:16px;">$1</h3>')
|
| 783 |
+
.replace(/^## (.+)$/gm, '<h2 style="margin:16px 0 8px;font-size:18px;">$1</h2>')
|
| 784 |
+
.replace(/^# (.+)$/gm, '<h1 style="margin:20px 0 12px;font-size:22px;">$1</h1>')
|
| 785 |
+
.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>')
|
| 786 |
+
.replace(/\\*(.+?)\\*/g, '<em>$1</em>')
|
| 787 |
+
.replace(/`([^`]+)`/g, '<code style="background:#e8e8e8;padding:2px 6px;border-radius:4px;">$1</code>')
|
| 788 |
+
.replace(/^- (.+)$/gm, '<li style="margin:4px 0;">$1</li>')
|
| 789 |
+
.replace(/\\n/g, '<br>');
|
| 790 |
+
}
|
| 791 |
+
|
| 792 |
+
// Copy as... functionality
|
| 793 |
+
const copyToggle = document.getElementById('copy-toggle');
|
| 794 |
+
const copyMenu = document.getElementById('copy-menu');
|
| 795 |
+
const toast = document.getElementById('toast');
|
| 796 |
+
|
| 797 |
+
copyToggle.onclick = (e) => {
|
| 798 |
+
e.stopPropagation();
|
| 799 |
+
copyMenu.classList.toggle('show');
|
| 800 |
+
};
|
| 801 |
+
|
| 802 |
+
document.addEventListener('click', () => copyMenu.classList.remove('show'));
|
| 803 |
+
|
| 804 |
+
function getBaseUrl() {
|
| 805 |
+
return window.location.origin;
|
| 806 |
+
}
|
| 807 |
+
|
| 808 |
+
function generateCode(format) {
|
| 809 |
+
const method = document.getElementById('method').value;
|
| 810 |
+
const url = document.getElementById('url').value;
|
| 811 |
+
const body = document.getElementById('body').value;
|
| 812 |
+
const headersText = document.getElementById('headers').value;
|
| 813 |
+
const fullUrl = getBaseUrl() + url;
|
| 814 |
+
|
| 815 |
+
const headers = {};
|
| 816 |
+
headersText.split('\\n').forEach(line => {
|
| 817 |
+
const [key, ...vals] = line.split(':');
|
| 818 |
+
if (key && vals.length) headers[key.trim()] = vals.join(':').trim();
|
| 819 |
+
});
|
| 820 |
+
|
| 821 |
+
const hasBody = ['POST', 'PUT', 'PATCH'].includes(method) && body;
|
| 822 |
+
|
| 823 |
+
switch (format) {
|
| 824 |
+
case 'curl': {
|
| 825 |
+
let cmd = `curl -X ${method} "${fullUrl}"`;
|
| 826 |
+
for (const [k, v] of Object.entries(headers)) {
|
| 827 |
+
cmd += ` \\\\\\n -H "${k}: ${v}"`;
|
| 828 |
+
}
|
| 829 |
+
if (hasBody) {
|
| 830 |
+
cmd += ` \\\\\\n -d '${body.replace(/'/g, "\\'")}'`;
|
| 831 |
+
}
|
| 832 |
+
return cmd;
|
| 833 |
+
}
|
| 834 |
+
case 'python': {
|
| 835 |
+
let code = `import requests\\n\\n`;
|
| 836 |
+
code += `url = "${fullUrl}"\\n`;
|
| 837 |
+
if (Object.keys(headers).length) {
|
| 838 |
+
code += `headers = ${JSON.stringify(headers, null, 4)}\\n`;
|
| 839 |
+
}
|
| 840 |
+
if (hasBody) {
|
| 841 |
+
code += `data = ${body}\\n\\n`;
|
| 842 |
+
code += `response = requests.${method.toLowerCase()}(url`;
|
| 843 |
+
if (Object.keys(headers).length) code += `, headers=headers`;
|
| 844 |
+
code += `, json=data)`;
|
| 845 |
+
} else {
|
| 846 |
+
code += `\\nresponse = requests.${method.toLowerCase()}(url`;
|
| 847 |
+
if (Object.keys(headers).length) code += `, headers=headers`;
|
| 848 |
+
code += `)`;
|
| 849 |
+
}
|
| 850 |
+
code += `\\nprint(response.status_code)\\nprint(response.json())`;
|
| 851 |
+
return code;
|
| 852 |
+
}
|
| 853 |
+
case 'fetch': {
|
| 854 |
+
let code = `fetch("${fullUrl}", {\\n`;
|
| 855 |
+
code += ` method: "${method}",\\n`;
|
| 856 |
+
if (Object.keys(headers).length) {
|
| 857 |
+
code += ` headers: ${JSON.stringify(headers, null, 4).replace(/\\n/g, '\\n ')},\\n`;
|
| 858 |
+
}
|
| 859 |
+
if (hasBody) {
|
| 860 |
+
code += ` body: JSON.stringify(${body})\\n`;
|
| 861 |
+
}
|
| 862 |
+
code += `})\\n`;
|
| 863 |
+
code += `.then(res => res.json())\\n`;
|
| 864 |
+
code += `.then(data => console.log(data))\\n`;
|
| 865 |
+
code += `.catch(err => console.error(err));`;
|
| 866 |
+
return code;
|
| 867 |
+
}
|
| 868 |
+
case 'httpie': {
|
| 869 |
+
let cmd = `http ${method} "${fullUrl}"`;
|
| 870 |
+
for (const [k, v] of Object.entries(headers)) {
|
| 871 |
+
cmd += ` "${k}:${v}"`;
|
| 872 |
+
}
|
| 873 |
+
if (hasBody) {
|
| 874 |
+
try {
|
| 875 |
+
const jsonBody = JSON.parse(body);
|
| 876 |
+
for (const [k, v] of Object.entries(jsonBody)) {
|
| 877 |
+
const val = typeof v === 'string' ? `="${v}"` : `:=${JSON.stringify(v)}`;
|
| 878 |
+
cmd += ` ${k}${val}`;
|
| 879 |
+
}
|
| 880 |
+
} catch {
|
| 881 |
+
cmd += ` --raw '${body}'`;
|
| 882 |
+
}
|
| 883 |
+
}
|
| 884 |
+
return cmd;
|
| 885 |
+
}
|
| 886 |
+
}
|
| 887 |
+
}
|
| 888 |
+
|
| 889 |
+
function showToast(msg) {
|
| 890 |
+
toast.textContent = msg;
|
| 891 |
+
toast.classList.add('show');
|
| 892 |
+
setTimeout(() => toast.classList.remove('show'), 2000);
|
| 893 |
+
}
|
| 894 |
+
|
| 895 |
+
document.querySelectorAll('.copy-option').forEach(btn => {
|
| 896 |
+
btn.onclick = async (e) => {
|
| 897 |
+
e.stopPropagation();
|
| 898 |
+
const format = btn.dataset.format;
|
| 899 |
+
const code = generateCode(format);
|
| 900 |
+
try {
|
| 901 |
+
await navigator.clipboard.writeText(code.replace(/\\\\n/g, '\\n'));
|
| 902 |
+
showToast(`Copied as ${format.toUpperCase()}!`);
|
| 903 |
+
} catch {
|
| 904 |
+
showToast('Failed to copy');
|
| 905 |
+
}
|
| 906 |
+
copyMenu.classList.remove('show');
|
| 907 |
+
};
|
| 908 |
+
});
|
| 909 |
</script>
|
| 910 |
</body>
|
| 911 |
</html>
|