| 1 |
(function () { |
| 2 |
'use strict'; |
| 3 |
|
| 4 |
function urlEncode(text) { return encodeURIComponent(text); } |
| 5 |
function urlDecode(text) { try { return {ok: true, val: decodeURIComponent(text)}; } catch(e) { return {ok: false, val: 'Invalid URI sequence — cannot decode.'}; } } |
| 6 |
function b64Encode(text) { try { return {ok: true, val: btoa(unescape(encodeURIComponent(text)))}; } catch(e) { return {ok: false, val: 'Encoding failed.'}; } } |
| 7 |
function b64Decode(text) { try { return {ok: true, val: decodeURIComponent(escape(atob(text)))}; } catch(e) { return {ok: false, val: 'Invalid Base64 — cannot decode.'}; } } |
| 8 |
|
| 9 |
function process(mode, text) { |
| 10 |
if (!text) return {result: '', error: ''}; |
| 11 |
if (mode === 'encode') return {result: urlEncode(text), error: ''}; |
| 12 |
if (mode === 'decode') { var d = urlDecode(text); return d.ok ? {result:d.val,error:''} : {result:'',error:d.val}; } |
| 13 |
if (mode === 'b64') { var e = b64Encode(text); return e.ok ? {result:e.val,error:''} : {result:'',error:e.val}; } |
| 14 |
if (mode === 'b64dec') { var r = b64Decode(text); return r.ok ? {result:r.val,error:''} : {result:'',error:r.val}; } |
| 15 |
return {result:'',error:''}; |
| 16 |
} |
| 17 |
|
| 18 |
function showToast(msg) { |
| 19 |
var t = document.getElementById('bkbg-ue-toast'); |
| 20 |
if (!t) { |
| 21 |
t = document.createElement('div'); |
| 22 |
t.id = 'bkbg-ue-toast'; |
| 23 |
t.className = 'bkbg-ue-toast'; |
| 24 |
document.body.appendChild(t); |
| 25 |
} |
| 26 |
t.textContent = msg; |
| 27 |
t.classList.add('show'); |
| 28 |
clearTimeout(t._to); |
| 29 |
t._to = setTimeout(function(){ t.classList.remove('show'); }, 2000); |
| 30 |
} |
| 31 |
|
| 32 |
function initApp(app) { |
| 33 |
var opts; |
| 34 |
try { opts = JSON.parse(app.getAttribute('data-opts') || '{}'); } catch(e) { return; } |
| 35 |
|
| 36 |
var accent = opts.accentColor || '#6c3fb5'; |
| 37 |
var cardBg = opts.cardBg || '#ffffff'; |
| 38 |
var tabActiveBg = opts.tabActiveBg || accent; |
| 39 |
var tabActiveClr= opts.tabActiveColor || '#ffffff'; |
| 40 |
var tabInactBg = opts.tabInactiveBg || '#f3f4f6'; |
| 41 |
var tabInactClr = opts.tabInactiveColor || '#6b7280'; |
| 42 |
var inputBg = opts.inputBg || '#f9fafb'; |
| 43 |
var inputBorder = opts.inputBorder || '#e5e7eb'; |
| 44 |
var inputColor = opts.inputColor || '#1f2937'; |
| 45 |
var outputBg = opts.outputBg || '#f3f4f6'; |
| 46 |
var outputBorder= opts.outputBorder || '#e5e7eb'; |
| 47 |
var outputColor = opts.outputColor || '#1f2937'; |
| 48 |
var errorColor = opts.errorColor || '#ef4444'; |
| 49 |
var errorBg = opts.errorBg || '#fef2f2'; |
| 50 |
var btnBg = opts.btnBg || accent; |
| 51 |
var btnClr = opts.btnColor || '#ffffff'; |
| 52 |
var labelColor = opts.labelColor || '#374151'; |
| 53 |
var cardR = (opts.cardRadius || 16) + 'px'; |
| 54 |
var inpR = (opts.inputRadius || 10) + 'px'; |
| 55 |
var tabR = (opts.tabRadius || 8) + 'px'; |
| 56 |
var maxW = (opts.maxWidth || 680) + 'px'; |
| 57 |
var ptop = (opts.paddingTop || 60) + 'px'; |
| 58 |
var pbot = (opts.paddingBottom||60) + 'px'; |
| 59 |
var rows = opts.textareaRows || 5; |
| 60 |
var taHeight = (rows * 1.6 + 2.5) + 'em'; |
| 61 |
|
| 62 |
var currentTab = opts.defaultMode || 'encode'; |
| 63 |
var currentB64 = 'enc'; // enc | dec |
| 64 |
|
| 65 |
app.style.paddingTop = ptop; |
| 66 |
app.style.paddingBottom = pbot; |
| 67 |
if (opts.sectionBg) app.style.background = opts.sectionBg; |
| 68 |
|
| 69 |
var card = document.createElement('div'); |
| 70 |
card.className = 'bkbg-ue-card'; |
| 71 |
Object.assign(card.style, {background:cardBg, borderRadius:cardR, padding:'32px', maxWidth:maxW, margin:'0 auto', boxShadow:'0 4px 24px rgba(0,0,0,.09)'}); |
| 72 |
app.appendChild(card); |
| 73 |
|
| 74 |
/* Header */ |
| 75 |
if (opts.showTitle && opts.title) { |
| 76 |
var titleEl = document.createElement('div'); |
| 77 |
titleEl.className = 'bkbg-ue-title'; |
| 78 |
titleEl.textContent = opts.title; |
| 79 |
if (opts.titleColor) titleEl.style.color = opts.titleColor; |
| 80 |
card.appendChild(titleEl); |
| 81 |
} |
| 82 |
if (opts.showSubtitle && opts.subtitle) { |
| 83 |
var subEl = document.createElement('div'); |
| 84 |
subEl.className = 'bkbg-ue-subtitle'; |
| 85 |
subEl.textContent = opts.subtitle; |
| 86 |
if (opts.subtitleColor) subEl.style.color = opts.subtitleColor; |
| 87 |
card.appendChild(subEl); |
| 88 |
} |
| 89 |
|
| 90 |
/* Tab bar */ |
| 91 |
var tabsData = [{id:'encode',label:'URL Encode'},{id:'decode',label:'URL Decode'}]; |
| 92 |
if (opts.showBase64Tab !== false) tabsData.push({id:'b64',label:'Base64'}); |
| 93 |
var tabBar = document.createElement('div'); |
| 94 |
tabBar.className = 'bkbg-ue-tabs'; |
| 95 |
Object.assign(tabBar.style, {display:'flex', gap:'4px', background:tabInactBg, borderRadius:tabR, padding:'4px', marginBottom:'18px'}); |
| 96 |
card.appendChild(tabBar); |
| 97 |
|
| 98 |
var tabBtns = {}; |
| 99 |
tabsData.forEach(function(td) { |
| 100 |
var btn = document.createElement('button'); |
| 101 |
btn.className = 'bkbg-ue-tab'; |
| 102 |
btn.textContent = td.label; |
| 103 |
btn.setAttribute('data-tab', td.id); |
| 104 |
Object.assign(btn.style, {flex:'1',padding:'8px',border:'none',borderRadius:tabR,cursor:'pointer',fontSize:'14px',fontFamily:'inherit',transition:'all .2s'}); |
| 105 |
btn.addEventListener('click', function() { |
| 106 |
currentTab = td.id; |
| 107 |
updateTabs(); |
| 108 |
inputTA.value = ''; |
| 109 |
update(); |
| 110 |
}); |
| 111 |
tabBtns[td.id] = btn; |
| 112 |
tabBar.appendChild(btn); |
| 113 |
}); |
| 114 |
|
| 115 |
/* B64 sub-mode */ |
| 116 |
var subTabWrap = document.createElement('div'); |
| 117 |
subTabWrap.className = 'bkbg-ue-subtabs'; |
| 118 |
Object.assign(subTabWrap.style, {display:'none', gap:'6px', marginBottom:'14px'}); |
| 119 |
card.appendChild(subTabWrap); |
| 120 |
|
| 121 |
var subTabEnc = document.createElement('button'); |
| 122 |
subTabEnc.className = 'bkbg-ue-subtab'; |
| 123 |
subTabEnc.textContent = 'Encode'; |
| 124 |
var subTabDec = document.createElement('button'); |
| 125 |
subTabDec.className = 'bkbg-ue-subtab'; |
| 126 |
subTabDec.textContent = 'Decode'; |
| 127 |
subTabWrap.appendChild(subTabEnc); |
| 128 |
subTabWrap.appendChild(subTabDec); |
| 129 |
|
| 130 |
subTabEnc.addEventListener('click', function(){ currentB64='enc'; inputTA.value=''; update(); updateSubTabs(); }); |
| 131 |
subTabDec.addEventListener('click', function(){ currentB64='dec'; inputTA.value=''; update(); updateSubTabs(); }); |
| 132 |
|
| 133 |
function makeLabel(text) { |
| 134 |
var l = document.createElement('label'); |
| 135 |
l.className = 'bkbg-ue-label'; |
| 136 |
l.textContent = text; |
| 137 |
Object.assign(l.style, {display:'block', fontSize:'12px', fontWeight:'600', color:labelColor, marginBottom:'5px'}); |
| 138 |
return l; |
| 139 |
} |
| 140 |
|
| 141 |
/* Input group */ |
| 142 |
var inpGroup = document.createElement('div'); |
| 143 |
inpGroup.className = 'bkbg-ue-group'; |
| 144 |
inpGroup.style.marginBottom = '16px'; |
| 145 |
var inpLabel = makeLabel('Text to Encode'); |
| 146 |
var inputTA = document.createElement('textarea'); |
| 147 |
inputTA.className = 'bkbg-ue-textarea'; |
| 148 |
inputTA.placeholder = opts.defaultText || ''; |
| 149 |
inputTA.rows = rows; |
| 150 |
Object.assign(inputTA.style,{width:'100%',padding:'10px 12px',borderRadius:inpR,border:'1.5px solid '+inputBorder,background:inputBg,color:inputColor,fontSize:'14px',fontFamily:'ui-monospace,Menlo,monospace',resize:'vertical',minHeight:taHeight,outline:'none',lineHeight:'1.6',boxSizing:'border-box'}); |
| 151 |
inputTA.addEventListener('input', update); |
| 152 |
inpGroup.appendChild(inpLabel); |
| 153 |
inpGroup.appendChild(inputTA); |
| 154 |
card.appendChild(inpGroup); |
| 155 |
|
| 156 |
/* Char count input */ |
| 157 |
var inpCount = document.createElement('div'); |
| 158 |
inpCount.className = 'bkbg-ue-charcount'; |
| 159 |
Object.assign(inpCount.style,{textAlign:'right',fontSize:'12px',color:'#9ca3af',marginTop:'-12px',marginBottom:'12px'}); |
| 160 |
if (opts.showCharCount !== false) card.appendChild(inpCount); |
| 161 |
|
| 162 |
/* Error box */ |
| 163 |
var errBox = document.createElement('div'); |
| 164 |
errBox.className = 'bkbg-ue-error'; |
| 165 |
Object.assign(errBox.style,{display:'none',background:errorBg,border:'1px solid '+errorColor,color:errorColor,padding:'10px 14px',borderRadius:'8px',fontSize:'14px',marginBottom:'12px'}); |
| 166 |
card.appendChild(errBox); |
| 167 |
|
| 168 |
/* Output group */ |
| 169 |
var outGroup = document.createElement('div'); |
| 170 |
outGroup.className = 'bkbg-ue-group'; |
| 171 |
outGroup.style.marginBottom = '16px'; |
| 172 |
var outLabel = makeLabel('Result'); |
| 173 |
var outputTA = document.createElement('textarea'); |
| 174 |
outputTA.className = 'bkbg-ue-textarea output'; |
| 175 |
outputTA.readOnly = true; |
| 176 |
outputTA.rows = rows; |
| 177 |
Object.assign(outputTA.style,{width:'100%',padding:'10px 12px',borderRadius:inpR,border:'1.5px solid '+outputBorder,background:outputBg,color:outputColor,fontSize:'14px',fontFamily:'ui-monospace,Menlo,monospace',resize:'vertical',minHeight:taHeight,outline:'none',lineHeight:'1.6',boxSizing:'border-box',cursor:'text',userSelect:'all'}); |
| 178 |
outGroup.appendChild(outLabel); |
| 179 |
outGroup.appendChild(outputTA); |
| 180 |
card.appendChild(outGroup); |
| 181 |
|
| 182 |
/* Char count output */ |
| 183 |
var outCount = document.createElement('div'); |
| 184 |
outCount.className = 'bkbg-ue-charcount'; |
| 185 |
Object.assign(outCount.style,{textAlign:'right',fontSize:'12px',color:'#9ca3af',marginTop:'-12px',marginBottom:'12px'}); |
| 186 |
if (opts.showCharCount !== false) card.appendChild(outCount); |
| 187 |
|
| 188 |
/* Actions */ |
| 189 |
var actions = document.createElement('div'); |
| 190 |
actions.className = 'bkbg-ue-actions'; |
| 191 |
Object.assign(actions.style,{display:'flex',gap:'8px',flexWrap:'wrap',marginTop:'6px'}); |
| 192 |
card.appendChild(actions); |
| 193 |
|
| 194 |
if (opts.showCopyButton !== false) { |
| 195 |
var copyBtn = document.createElement('button'); |
| 196 |
copyBtn.className = 'bkbg-ue-btn'; |
| 197 |
copyBtn.textContent = 'Copy Result'; |
| 198 |
Object.assign(copyBtn.style,{padding:'8px 18px',background:btnBg,color:btnClr,border:'none',borderRadius:inpR,fontWeight:'700',cursor:'pointer',fontSize:'13px',fontFamily:'inherit',transition:'background .2s'}); |
| 199 |
copyBtn.addEventListener('click', function(){ |
| 200 |
var txt = outputTA.value; |
| 201 |
if (!txt) return; |
| 202 |
if (navigator.clipboard) { |
| 203 |
navigator.clipboard.writeText(txt).then(function(){ showToast('Copied!'); }); |
| 204 |
} else { |
| 205 |
outputTA.select(); |
| 206 |
document.execCommand('copy'); |
| 207 |
showToast('Copied!'); |
| 208 |
} |
| 209 |
}); |
| 210 |
actions.appendChild(copyBtn); |
| 211 |
} |
| 212 |
|
| 213 |
if (opts.showClearButton !== false) { |
| 214 |
var clearBtn = document.createElement('button'); |
| 215 |
clearBtn.className = 'bkbg-ue-btn secondary'; |
| 216 |
clearBtn.textContent = 'Clear'; |
| 217 |
Object.assign(clearBtn.style,{padding:'8px 18px',background:'#f3f4f6',color:'#374151',border:'none',borderRadius:inpR,fontWeight:'700',cursor:'pointer',fontSize:'13px',fontFamily:'inherit',transition:'background .2s'}); |
| 218 |
clearBtn.addEventListener('click', function(){ inputTA.value=''; update(); }); |
| 219 |
actions.appendChild(clearBtn); |
| 220 |
} |
| 221 |
|
| 222 |
function updateTabs() { |
| 223 |
tabsData.forEach(function(td) { |
| 224 |
var btn = tabBtns[td.id]; |
| 225 |
var active = td.id === currentTab; |
| 226 |
btn.style.background = active ? tabActiveBg : 'transparent'; |
| 227 |
btn.style.color = active ? tabActiveClr : tabInactClr; |
| 228 |
btn.style.fontWeight = active ? '700' : '500'; |
| 229 |
btn.style.boxShadow = active ? '0 1px 6px rgba(108,63,181,.25)' : 'none'; |
| 230 |
}); |
| 231 |
subTabWrap.style.display = currentTab === 'b64' ? 'flex' : 'none'; |
| 232 |
inpLabel.textContent = currentTab === 'b64' ? 'Input Text' : (currentTab === 'encode' ? 'Text to Encode' : 'Encoded URL'); |
| 233 |
} |
| 234 |
|
| 235 |
function updateSubTabs() { |
| 236 |
subTabEnc.style.fontWeight = currentB64 === 'enc' ? '700' : '400'; |
| 237 |
subTabEnc.style.borderColor = currentB64 === 'enc' ? accent : '#d1d5db'; |
| 238 |
subTabEnc.style.background = currentB64 === 'enc' ? accent+'22' : '#fff'; |
| 239 |
subTabEnc.style.color = currentB64 === 'enc' ? accent : '#6b7280'; |
| 240 |
subTabDec.style.fontWeight = currentB64 === 'dec' ? '700' : '400'; |
| 241 |
subTabDec.style.borderColor = currentB64 === 'dec' ? accent : '#d1d5db'; |
| 242 |
subTabDec.style.background = currentB64 === 'dec' ? accent+'22' : '#fff'; |
| 243 |
subTabDec.style.color = currentB64 === 'dec' ? accent : '#6b7280'; |
| 244 |
} |
| 245 |
|
| 246 |
function update() { |
| 247 |
var txt = inputTA.value; |
| 248 |
var mode = currentTab === 'b64' ? (currentB64 === 'enc' ? 'b64' : 'b64dec') : currentTab; |
| 249 |
var res = process(mode, txt); |
| 250 |
outputTA.value = res.result; |
| 251 |
if (opts.showCharCount !== false) { |
| 252 |
inpCount.textContent = txt.length + ' characters'; |
| 253 |
outCount.textContent = res.result.length + ' characters'; |
| 254 |
} |
| 255 |
if (res.error) { |
| 256 |
errBox.textContent = '⚠ ' + res.error; |
| 257 |
errBox.style.display = 'block'; |
| 258 |
} else { |
| 259 |
errBox.style.display = 'none'; |
| 260 |
} |
| 261 |
} |
| 262 |
|
| 263 |
updateTabs(); |
| 264 |
updateSubTabs(); |
| 265 |
update(); |
| 266 |
} |
| 267 |
|
| 268 |
document.querySelectorAll('.bkbg-ue-app').forEach(initApp); |
| 269 |
})(); |
| 270 |
|