1(function () { 2 'use strict'; 3 var api = window.QuickRouterDebug; 4 var active = null; 5 function element(tag, attrs, value) { 6 var node = document.createElement(tag); 7 Object.keys(attrs || {}).forEach(function (key) { node.setAttribute(key, attrs[key]); }); 8 if (value !== undefined) node.textContent = value; 9 return node; 10 } 11 function field(parent, label, type, value) { 12 var wrapper = element('label', { class: 'api-debug-field' }); 13 wrapper.appendChild(element('span', {}, label)); 14 var input = element(type === 'textarea' ? 'textarea' : 'input', type === 'textarea' ? { spellcheck: 'false' } : { type: type }); 15 if (type !== 'file') input.value = value || ''; 16 wrapper.appendChild(input); parent.appendChild(wrapper); return input; 17 } 18 function section(parent, title) { 19 var node = element('section', { class: 'api-debug-section' }); 20 node.appendChild(element('h3', {}, title)); parent.appendChild(node); return node; 21 } 22 function kv(parent, name, value) { 23 var row = element('div', { class: 'api-debug-kv' }); 24 var enabled = element('input', { type: 'checkbox', 'aria-label': 'å¯ç¨åæ°' }); enabled.checked = true; 25 var key = element('input', { 'aria-label': 'åæ°å', value: name || '', placeholder: 'åæ°å' }); 26 var val = element('input', { 'aria-label': 'åæ°å¼', value: value || '', placeholder: 'åæ°å¼' }); 27 var remove = element('button', { type: 'button', 'aria-label': 'å é¤åæ°' }, 'Ã'); 28 remove.addEventListener('click', function () { row.remove(); }); 29 row.append(enabled, key, val, remove); parent.appendChild(row); 30 } 31 function pairs(parent) { 32 return Array.from(parent.querySelectorAll('.api-debug-kv')).map(function (row) { 33 var inputs = row.querySelectorAll('input'); 34 return { enabled: inputs[0].checked, name: inputs[1].value.trim(), value: inputs[2].value }; 35 }); 36 } 37 function addKV(parent, label) { 38 var button = element('button', { type: 'button', class: 'api-debug-add' }, label); 39 button.addEventListener('click', function () { kv(parent, '', ''); }); parent.appendChild(button); 40 } 41 function close() { 42 if (!active) return; 43 active.controller && active.controller.abort(); 44 if (active.blobURL) URL.revokeObjectURL(active.blobURL); 45 document.removeEventListener('keydown', active.keydown); 46 document.body.style.overflow = active.overflow; 47 active.overlay.remove(); var opener = active.opener; active = null; opener.focus(); 48 } 49 function open(button) { 50 close(); 51 var configNode = document.querySelector('.api-request-config'); 52 if (!configNode) return; 53 var config = JSON.parse(configNode.textContent); 54 var state = { opener: button, overflow: document.body.style.overflow }; 55 var overlay = element('div', { class: 'debug-overlay api-debug-overlay', id: 'debugOverlay' }); 56 var panel = element('div', { class: 'debug-panel-sidebar api-debug-panel', role: 'dialog', 'aria-modal'
56: 'true', 'aria-label': 'å¨çº¿è°è¯' }); 57 var header = element('div', { class: 'debug-header' }); 58 header.appendChild(element('strong', {}, config.method + ' · å¨çº¿è°è¯')); 59 var dismiss = element('button', { type: 'button', id: 'debugClose', 'aria-label': 'å ³éè°è¯' }, 'Ã'); 60 dismiss.addEventListener('click', close); header.appendChild(dismiss); panel.appendChild(header); 61 var content = element('div', { class: 'api-debug-content' }); panel.appendChild(content); 62 var url = field(content, '请æ±å°å', 'url', config.url); url.id = 'debugUrl'; 63 var auth = section(content, 'é´æ'); 64 var key = field(auth, 'API Key / ç³»ç»è®¿é®ä»¤ç', 'password', ''); key.id = 'debugApiKey'; 65 key.autocomplete = 'off'; 66 var headers = section(content, '请æ±å¤´'); 67 Object.keys(config.headers || {}).forEach(function (name) { kv(headers, name, config.headers[name]); }); 68 addKV(headers, 'æ·»å 请æ±å¤´'); 69 var pathInputs = {}, paths = Object.keys(config.path || {}); 70 if (paths.length) { 71 var path = section(content, 'è·¯å¾åæ°'); 72 paths.forEach(function (name) { pathInputs[name] = field(path, name, 'text', config.path[name]); }); 73 } 74 var query = section(content, 'Query åæ°'); 75 (config.query || []).forEach(function (pair) { kv(query, pair.name, pair.value); }); 76 addKV(query, 'æ·»å Query åæ°'); 77 var body = null, formFields = null, fileInputs = []; 78 if (config.bodyType === 'json' && !['GET', 'HEAD'].includes(config.method)) { 79 var bodySection = section(content, 'JSON 请æ±ä½'); 80 body = field(bodySection, 'è¯·æ± JSON', 'textarea', JSON.stringify(config.body || {}, null, 2)); body.id = 'debugBody'; 81 } else if (config.bodyType === 'multipart') { 82 formFields = section(content, 'è¡¨åææ¬å段'); 83 var fileNames = (config.files || []).map(function (entry) { return entry.name; }); 84 var formNames = new Set(); 85 Object.keys(config.body || {}).forEach(function (name) { 86 if (fileNames.includes(name)) return; 87 formNames.add(name); kv(formFields, name, String(config.body[name])); 88 }); 89 (config.fields || []).forEach(function (entry) { 90 if (!formNames.has(entry.name) && (entry.required || entry.value)) kv(formFields, entry.name, entry.value || ''); 91 }); 92 addKV(formFields, 'æ·»å 表ååæ®µ'); 93 var files = section(content, 'ä¸ä¼ æä»¶'); 94 (config.files || []).forEach(function (entry) { 95 var input = field(files, entry.name + (entry.required ? 'ï¼å¿ å¡«ï¼' : 'ï¼å¯éï¼'), 'file'); 96 input.multiple = /\[\]$/.test(entry.name); 97 fileInputs.push({ input: input, name: entry.name, required: entry.required }); 98 }); 99 var addFile = element('button', { type: 'button', class: 'api-debug-add' }, 'æ·»å æä»¶åæ®µ'); 100 addFile.addEventListener('click', function () { 101 var name = field(files, 'æä»¶å段å', 'text', ''); 102 var input = field(files, 'éæ©æä»¶', 'file'); input.multiple = true; 103 fileInputs.push({ input: input, nameInput: name, required: false }); 104 }); files.appendChild(addFile); 105 } 106 var mode = section(content, 'è¿åå¤ç'); 107 var choice = element('select', { 'aria-label': 'è¿åå¤çæ¹å¼' }); 108 choice.appendChild(element('option', { value: 'auto' }, 'èªå¨è¯å« JSONãæµå¼ååªä½æä»¶')); 109 choice.appendChild(element('option', { value: 'download' }, 'ä¸è½½ä¸ºæä»¶')); mode.appendChild(choice); 110 var controls = element('div', { class: 'api-debug-controls' }); 111 var send = element('button', { type: 'button', id: 'debugSend' }, 'åé请æ±'); 112 var cancel = element('button', { type: 'button', disabled: '', id: 'debugCancel' }, 'åæ¢è¯·æ±'); 113 var status = element('span', { id: 'debugStatus', role: 'status' }); 114 controls.append(send, cancel, status); panel.appendChild(controls); 115 var result = element('div', { class: 'api-debug-result' }); 116 var output = element('pre', { id: 'debugResponse', 'aria-label': 'è¿åç»æ' }, 'åéåå¨è¿éæ¾ç¤ºç»æã'); 117 var download = element('a', { class: 'api-debug-download', hidden: '' }, 'ä¸è½½æä»¶'); 118 result.append(output, download); panel.appendChild(result); 119 cancel.addEventListener('click', function () { state.controller && state.controller.abort(); }); 120 send.addEventListener('click', async function () {
121 try { 122 if (!key.value.trim()) throw new Error('请填å API Key æç³»ç»è®¿é®ä»¤ç'); 123 var selectedHeaders = {}; 124 pairs(headers).forEach(function (pair) { if (pair.name && pair.enabled && pair.name.toLowerCase() !== 'authorization') selectedHeaders[pair.name.toLowerCase()] = pair.value; }); 125 if ('new-api-user' in (config.headers || {}) && !selectedHeaders['new-api-user']) throw new Error('请填åä½ ç new-api-user ç¨æ· ID'); 126 selectedHeaders.Authorization = 'Bearer ' + key.value.trim(); 127 var values = {}; paths.forEach(function (name) { values[name] = pathInputs[name].value; }); 128 var queryValues = pairs(query).map(function (pair) { 129 pair.required = (config.query || []).some(function (entry) { return entry.name === pair.name && entry.required; }); 130 return pair; 131 }).filter(function (pair) { return pair.required || pair.value !== ''; }); 132 (config.query || []).forEach(function (entry) { 133 if (entry.required && !queryValues.some(function (pair) { return pair.name === entry.name && pair.enabled && pair.value.trim(); })) throw new Error('请填å Query åæ° ' + entry.name); 134 }); 135 state.controller = new AbortController(); 136 var request = api.buildRequest({ method: config.method, url: url.value.trim(), headers: selectedHeaders, path: values, query: queryValues, bodyType: config.bodyType, body: body && body.value, fields: formFields ? pairs(formFields) : [], files: fileInputs.map(function (file) { return { name: file.nameInput ? file.nameInput.value.trim() : file.name, required: file.required, values: Array.from(file.input.files) }; }), signal: state.controller.signal }); 137 if (state.blobURL) { URL.revokeObjectURL(state.blobURL); state.blobURL = null; } 138 download.hidden = true; output.textContent = ''; send.disabled = true; cancel.disabled = false; status.textContent = '请æ±ä¸â¦'; 139 var response = await fetch(request.url, request.options); 140 status.textContent = 'HTTP ' + response.status; 141 var resultData = await api.readResponse(response, { download: choice.value === 'download' || /\/content(?:\?|$)/.test(request.url), signal: state.controller.signal, fallbackName: /\/videos\//.test(request.url) ? 'video.mp4' : 'result.bin', onEvent: function (event) { output.textContent += 'event: ' + event.event + '\ndata: ' + event.data + '\n\n'; } }); 142 if (resultData.kind === 'file') { 143 state.blobURL = URL.createObjectURL(resultData.blob); download.href = state.blobURL; download.download = resultData.filename; download.hidden = false; 144 output.textContent = 'æä»¶å·²æ¥æ¶ï¼' + resultData.filename + '\n大å°ï¼' + resultData.blob.size + ' åè'; 145 } else if (resultData.kind === 'text') { 146 try { output.textContent = JSON.stringify(JSON.parse(resultData.text), null, 2); } 147 catch (_) { output.textContent = resultData.text; } 148 } else status.textContent += ' · æµå¼ç»æ'; 149 } catch (error) { 150 status.textContent = error.name === 'AbortError' ? '已忢' : '请æ±å¤±è´¥'; 151 if (error.name !== 'AbortError') output.textContent = error.message; 152 } finally { send.disabled = false; cancel.disabled = true; state.controller = null; } 153 }); 154 state.overlay = overlay; state.keydown = function (event) { 155 if (event.key === 'Escape') { event.preventDefault(); close(); } 156 if (event.key === 'Tab') { 157 var nodes = Array.from(panel.querySelectorAll('button,input,textarea,select,a[href]')).filter(function (n) { return !n.disabled && !n.hidden; }); 158 var first = nodes[0], last = nodes[nodes.length - 1]; 159 if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last.focus(); } 160 else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); } 161 } 162 }; 163 document.addEventListener('keydown', state.keydown); 164 overlay.addEventListener('click', function (event) { if (event.target === overlay) close(); }); 165 overlay.appendChild(panel); document.body.appendChild(overlay); document.body.style.overflow = 'hidden'; active = state; url.focus(); 166 } 167 document.addEventListener('click', function (event) { 168 var button = event.target.closest('.api-debug-btn'); 169 if (!button || !api) return; 170 event.preventDefault(); open(button); 171 }); 172})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.