| 1 |
(function () { |
| 2 |
'use strict'; |
| 3 |
|
| 4 |
var METHOD_COLORS = { |
| 5 |
GET: { bg: '#22c55e', color: '#fff' }, |
| 6 |
POST: { bg: '#3b82f6', color: '#fff' }, |
| 7 |
PUT: { bg: '#f59e0b', color: '#fff' }, |
| 8 |
PATCH: { bg: '#8b5cf6', color: '#fff' }, |
| 9 |
DELETE: { bg: '#ef4444', color: '#fff' } |
| 10 |
}; |
| 11 |
|
| 12 |
var AUTH_LABELS = { |
| 13 |
none: 'No Auth', |
| 14 |
bearer: 'Bearer Token', |
| 15 |
'api-key': 'API Key', |
| 16 |
basic: 'Basic Auth' |
| 17 |
}; |
| 18 |
|
| 19 |
function applyCSS(el, styles) { |
| 20 |
Object.keys(styles).forEach(function (k) { el.style[k] = styles[k]; }); |
| 21 |
} |
| 22 |
|
| 23 |
function makeEl(tag, className, styles) { |
| 24 |
var el = document.createElement(tag); |
| 25 |
if (className) el.className = className; |
| 26 |
if (styles) applyCSS(el, styles); |
| 27 |
return el; |
| 28 |
} |
| 29 |
|
| 30 |
function copyText(text) { |
| 31 |
if (navigator.clipboard && navigator.clipboard.writeText) { |
| 32 |
return navigator.clipboard.writeText(text); |
| 33 |
} |
| 34 |
var ta = document.createElement('textarea'); |
| 35 |
ta.value = text; |
| 36 |
ta.style.position = 'fixed'; |
| 37 |
ta.style.opacity = '0'; |
| 38 |
document.body.appendChild(ta); |
| 39 |
ta.select(); |
| 40 |
try { document.execCommand('copy'); } catch (e) {} |
| 41 |
document.body.removeChild(ta); |
| 42 |
return Promise.resolve(); |
| 43 |
} |
| 44 |
|
| 45 |
function buildParamsTable(params, tableBg, tableAltBg, borderColor) { |
| 46 |
if (!params || params.length === 0) { |
| 47 |
var empty = makeEl('p', 'bkbg-apir-empty'); |
| 48 |
empty.textContent = 'No parameters defined.'; |
| 49 |
return empty; |
| 50 |
} |
| 51 |
|
| 52 |
var table = makeEl('table', 'bkbg-apir-table'); |
| 53 |
table.style.setProperty('--bkbg-apir-table-bg', tableBg || '#fff'); |
| 54 |
table.style.setProperty('--bkbg-apir-table-alt', tableAltBg || '#f8fafc'); |
| 55 |
|
| 56 |
var thead = document.createElement('thead'); |
| 57 |
var hRow = document.createElement('tr'); |
| 58 |
['Name', 'Type', 'In', 'Required', 'Description', 'Example'].forEach(function (h) { |
| 59 |
var th = document.createElement('th'); th.textContent = h; hRow.appendChild(th); |
| 60 |
}); |
| 61 |
thead.appendChild(hRow); table.appendChild(thead); |
| 62 |
|
| 63 |
var tbody = document.createElement('tbody'); |
| 64 |
params.forEach(function (p, i) { |
| 65 |
var row = document.createElement('tr'); |
| 66 |
if (i % 2) row.className = 'alt'; |
| 67 |
|
| 68 |
// Name |
| 69 |
var tdName = document.createElement('td'); |
| 70 |
var cName = document.createElement('code'); cName.textContent = p.name; tdName.appendChild(cName); |
| 71 |
|
| 72 |
// Type |
| 73 |
var tdType = document.createElement('td'); |
| 74 |
var spanType = makeEl('span', 'bkbg-apir-type'); spanType.textContent = p.type; tdType.appendChild(spanType); |
| 75 |
|
| 76 |
// Location |
| 77 |
var tdLoc = document.createElement('td'); |
| 78 |
var spanLoc = makeEl('span', 'bkbg-apir-loc bkbg-apir-loc-' + p.location); spanLoc.textContent = p.location; tdLoc.appendChild(spanLoc); |
| 79 |
|
| 80 |
// Required |
| 81 |
var tdReq = document.createElement('td'); |
| 82 |
var spanReq = makeEl('span', p.required ? 'bkbg-apir-req' : 'bkbg-apir-opt'); |
| 83 |
spanReq.textContent = p.required ? '✓' : '—'; tdReq.appendChild(spanReq); |
| 84 |
|
| 85 |
// Description |
| 86 |
var tdDesc = document.createElement('td'); tdDesc.textContent = p.description; |
| 87 |
|
| 88 |
// Example |
| 89 |
var tdEx = document.createElement('td'); |
| 90 |
if (p.example) { var cEx = document.createElement('code'); cEx.textContent = p.example; tdEx.appendChild(cEx); } |
| 91 |
else { tdEx.textContent = '—'; } |
| 92 |
|
| 93 |
[tdName, tdType, tdLoc, tdReq, tdDesc, tdEx].forEach(function (td) { row.appendChild(td); }); |
| 94 |
tbody.appendChild(row); |
| 95 |
}); |
| 96 |
table.appendChild(tbody); |
| 97 |
return table; |
| 98 |
} |
| 99 |
|
| 100 |
function buildCodePanel(code, lang, showCopy, theme) { |
| 101 |
var isDark = theme !== 'light'; |
| 102 |
var codeBg = isDark ? '#1e1e2e' : '#f8fafc'; |
| 103 |
var codeColor = isDark ? '#a9b1d6' : '#334155'; |
| 104 |
|
| 105 |
var panel = makeEl('div', 'bkbg-apir-code-panel'); |
| 106 |
applyCSS(panel, { background: codeBg, color: codeColor }); |
| 107 |
|
| 108 |
if (showCopy) { |
| 109 |
var copyBtn = makeEl('button', 'bkbg-apir-copy'); |
| 110 |
copyBtn.textContent = '📋 Copy'; |
| 111 |
copyBtn.addEventListener('click', function () { |
| 112 |
copyText(code).then(function () { |
| 113 |
copyBtn.textContent = '✓ Copied!'; |
| 114 |
setTimeout(function () { copyBtn.textContent = '📋 Copy'; }, 2000); |
| 115 |
}); |
| 116 |
}); |
| 117 |
panel.appendChild(copyBtn); |
| 118 |
} |
| 119 |
|
| 120 |
var langBadge = makeEl('span', 'bkbg-apir-lang-badge'); langBadge.textContent = lang; |
| 121 |
panel.appendChild(langBadge); |
| 122 |
|
| 123 |
var pre = makeEl('pre', 'bkbg-apir-code'); |
| 124 |
applyCSS(pre, { background: codeBg, color: codeColor }); |
| 125 |
pre.textContent = code; |
| 126 |
panel.appendChild(pre); |
| 127 |
|
| 128 |
return panel; |
| 129 |
} |
| 130 |
|
| 131 |
function typoCssVarsForEl(typo, prefix, el) { |
| 132 |
if (!typo || typeof typo !== 'object') return; |
| 133 |
var map = { |
| 134 |
family:'font-family', weight:'font-weight', style:'font-style', |
| 135 |
decoration:'text-decoration', transform:'text-transform', |
| 136 |
sizeDesktop:'font-size-d', sizeTablet:'font-size-t', sizeMobile:'font-size-m', |
| 137 |
lineHeightDesktop:'line-height-d', lineHeightTablet:'line-height-t', lineHeightMobile:'line-height-m', |
| 138 |
letterSpacingDesktop:'letter-spacing-d', letterSpacingTablet:'letter-spacing-t', letterSpacingMobile:'letter-spacing-m', |
| 139 |
wordSpacingDesktop:'word-spacing-d', wordSpacingTablet:'word-spacing-t', wordSpacingMobile:'word-spacing-m' |
| 140 |
}; |
| 141 |
Object.keys(map).forEach(function(k) { |
| 142 |
if (typo[k] !== undefined && typo[k] !== '') { |
| 143 |
var v = typo[k]; |
| 144 |
if (['sizeDesktop','sizeTablet','sizeMobile'].indexOf(k) !== -1) v = v + (typo.sizeUnit || 'px'); |
| 145 |
else if (['lineHeightDesktop','lineHeightTablet','lineHeightMobile'].indexOf(k) !== -1) v = v + (typo.lineHeightUnit || ''); |
| 146 |
else if (['letterSpacingDesktop','letterSpacingTablet','letterSpacingMobile'].indexOf(k) !== -1) v = v + (typo.letterSpacingUnit || 'px'); |
| 147 |
else if (['wordSpacingDesktop','wordSpacingTablet','wordSpacingMobile'].indexOf(k) !== -1) v = v + (typo.wordSpacingUnit || 'px'); |
| 148 |
el.style.setProperty(prefix + map[k], String(v)); |
| 149 |
} |
| 150 |
}); |
| 151 |
} |
| 152 |
|
| 153 |
function init() { |
| 154 |
document.querySelectorAll('.bkbg-apir-app').forEach(function (appEl) { |
| 155 |
if (appEl.dataset.rendered) return; |
| 156 |
appEl.dataset.rendered = '1'; |
| 157 |
|
| 158 |
var opts; |
| 159 |
try { opts = JSON.parse(appEl.dataset.opts || '{}'); } catch (e) { opts = {}; } |
| 160 |
var o = Object.assign({ |
| 161 |
method: 'GET', |
| 162 |
endpoint: '/api/v1/users/{id}', |
| 163 |
baseUrl: 'https://api.example.com', |
| 164 |
title: 'API Endpoint', |
| 165 |
description: '', |
| 166 |
authType: 'bearer', |
| 167 |
params: [], |
| 168 |
requestExample: '', |
| 169 |
responseExample: '', |
| 170 |
requestLang: 'curl', |
| 171 |
responseLang: 'json', |
| 172 |
showCopyButton: true, |
| 173 |
showBaseUrl: true, |
| 174 |
showAuth: true, |
| 175 |
defaultTab: 'params', |
| 176 |
codeTheme: 'dark', |
| 177 |
getBg: '#22c55e', |
| 178 |
postBg: '#3b82f6', |
| 179 |
putBg: '#f59e0b', |
| 180 |
patchBg: '#8b5cf6', |
| 181 |
deleteBg: '#ef4444', |
| 182 |
headerBg: '#1e1e2e', |
| 183 |
headerColor: '#e2e8f0', |
| 184 |
tableBg: '#ffffff', |
| 185 |
tableAltBg: '#f8fafc', |
| 186 |
borderColor: '#e2e8f0', |
| 187 |
borderRadius: 8, |
| 188 |
fontSize: 13 |
| 189 |
}, opts); |
| 190 |
|
| 191 |
// Determine method color |
| 192 |
var methodBgKey = o.method.toLowerCase() + 'Bg'; |
| 193 |
var methodColors = METHOD_COLORS[o.method] || { bg: '#64748b', color: '#fff' }; |
| 194 |
// Override from saved opts |
| 195 |
var customBg = o[methodBgKey]; |
| 196 |
if (customBg) methodColors = { bg: customBg, color: '#fff' }; |
| 197 |
|
| 198 |
// Outer wrapper |
| 199 |
var wrap = makeEl('div', 'bkbg-apir-wrap'); |
| 200 |
wrap.style.setProperty('--bkbg-apir-border', o.borderColor); |
| 201 |
wrap.style.setProperty('--bkbg-apir-radius', o.borderRadius + 'px'); |
| 202 |
wrap.style.setProperty('--bkbg-apir-fs', o.fontSize + 'px'); |
| 203 |
typoCssVarsForEl(o.textTypo, '--bkbg-apir-', wrap); |
| 204 |
|
| 205 |
// ── Header ──────────────────────────────────────────────── |
| 206 |
var header = makeEl('div', 'bkbg-apir-header'); |
| 207 |
applyCSS(header, { background: o.headerBg, color: o.headerColor }); |
| 208 |
|
| 209 |
var methodBadge = makeEl('span', 'bkbg-apir-method'); |
| 210 |
methodBadge.textContent = o.method; |
| 211 |
applyCSS(methodBadge, { background: methodColors.bg, color: methodColors.color }); |
| 212 |
header.appendChild(methodBadge); |
| 213 |
|
| 214 |
if (o.showBaseUrl && o.baseUrl) { |
| 215 |
var baseUrlSpan = makeEl('span', 'bkbg-apir-base-url'); |
| 216 |
baseUrlSpan.textContent = o.baseUrl; |
| 217 |
header.appendChild(baseUrlSpan); |
| 218 |
} |
| 219 |
|
| 220 |
var endpointSpan = makeEl('span', 'bkbg-apir-endpoint'); |
| 221 |
endpointSpan.textContent = o.endpoint; |
| 222 |
header.appendChild(endpointSpan); |
| 223 |
|
| 224 |
if (o.showAuth && o.authType && o.authType !== 'none') { |
| 225 |
var authBadge = makeEl('span', 'bkbg-apir-auth-badge'); |
| 226 |
authBadge.textContent = AUTH_LABELS[o.authType] || o.authType; |
| 227 |
header.appendChild(authBadge); |
| 228 |
} |
| 229 |
wrap.appendChild(header); |
| 230 |
|
| 231 |
// ── Meta (title + description) ───────────────────────── |
| 232 |
var meta = makeEl('div', 'bkbg-apir-meta'); |
| 233 |
|
| 234 |
var titleEl = document.createElement('h3'); |
| 235 |
titleEl.className = 'bkbg-apir-title'; |
| 236 |
titleEl.textContent = o.title; |
| 237 |
meta.appendChild(titleEl); |
| 238 |
|
| 239 |
if (o.description) { |
| 240 |
var descEl = makeEl('p', 'bkbg-apir-desc'); |
| 241 |
descEl.textContent = o.description; |
| 242 |
meta.appendChild(descEl); |
| 243 |
} |
| 244 |
wrap.appendChild(meta); |
| 245 |
|
| 246 |
// ── Tab bar ──────────────────────────────────────────── |
| 247 |
var activeTab = o.defaultTab || 'params'; |
| 248 |
var tabBar = makeEl('div', 'bkbg-apir-tabbar'); |
| 249 |
var tabDefs = [ |
| 250 |
{ key: 'params', label: 'Parameters' }, |
| 251 |
{ key: 'request', label: 'Request' }, |
| 252 |
{ key: 'response', label: 'Response' } |
| 253 |
]; |
| 254 |
|
| 255 |
var panels = {}; |
| 256 |
|
| 257 |
// ── Body ─────────────────────────────────────────────── |
| 258 |
var body = makeEl('div', 'bkbg-apir-body'); |
| 259 |
|
| 260 |
// Params panel |
| 261 |
var paramsWrap = makeEl('div', 'bkbg-apir-params'); |
| 262 |
paramsWrap.appendChild(buildParamsTable(o.params, o.tableBg, o.tableAltBg, o.borderColor)); |
| 263 |
body.appendChild(paramsWrap); |
| 264 |
panels.params = paramsWrap; |
| 265 |
|
| 266 |
// Request panel |
| 267 |
var requestPanel = buildCodePanel(o.requestExample, o.requestLang, o.showCopyButton, o.codeTheme); |
| 268 |
body.appendChild(requestPanel); |
| 269 |
panels.request = requestPanel; |
| 270 |
|
| 271 |
// Response panel |
| 272 |
var responsePanel = buildCodePanel(o.responseExample, o.responseLang, o.showCopyButton, o.codeTheme); |
| 273 |
body.appendChild(responsePanel); |
| 274 |
panels.response = responsePanel; |
| 275 |
|
| 276 |
function switchTab(key) { |
| 277 |
activeTab = key; |
| 278 |
tabDefs.forEach(function (td) { |
| 279 |
var btn = tabBar.querySelector('[data-tab="' + td.key + '"]'); |
| 280 |
if (btn) btn.className = 'bkbg-apir-tab' + (key === td.key ? ' is-active' : ''); |
| 281 |
if (panels[td.key]) panels[td.key].style.display = key === td.key ? '' : 'none'; |
| 282 |
}); |
| 283 |
} |
| 284 |
|
| 285 |
tabDefs.forEach(function (td) { |
| 286 |
var btn = makeEl('button', 'bkbg-apir-tab' + (activeTab === td.key ? ' is-active' : '')); |
| 287 |
btn.textContent = td.label; |
| 288 |
btn.dataset.tab = td.key; |
| 289 |
btn.addEventListener('click', function () { switchTab(td.key); }); |
| 290 |
tabBar.appendChild(btn); |
| 291 |
}); |
| 292 |
|
| 293 |
// Initialize visibility |
| 294 |
tabDefs.forEach(function (td) { |
| 295 |
if (panels[td.key]) panels[td.key].style.display = td.key === activeTab ? '' : 'none'; |
| 296 |
}); |
| 297 |
|
| 298 |
wrap.appendChild(tabBar); |
| 299 |
wrap.appendChild(body); |
| 300 |
|
| 301 |
appEl.parentNode.insertBefore(wrap, appEl); |
| 302 |
appEl.style.display = 'none'; |
| 303 |
}); |
| 304 |
} |
| 305 |
|
| 306 |
if (document.readyState !== 'loading') { init(); } |
| 307 |
else { document.addEventListener('DOMContentLoaded', init); } |
| 308 |
})(); |
| 309 |
|