first commit
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// ASCII Art Generator
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function generateAscii() {
|
||||
const text = document.getElementById('asciiInput').value.trim();
|
||||
if (!text) { document.getElementById('asciiOutput').value = ''; return; }
|
||||
const d = await apiPost('/api/ascii/generate', { text });
|
||||
if (d.success) {
|
||||
document.getElementById('asciiOutput').value = d.result;
|
||||
setStatus('asciiStatus', 'success', 'Generated ✓');
|
||||
} else setStatus('asciiStatus', 'error', d.error);
|
||||
}
|
||||
setTimeout(generateAscii, 200);
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Base64
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function base64Op(op) {
|
||||
const text = document.getElementById('b64Input').value;
|
||||
if (!text) return setStatus('b64Status','error','Enter some text.');
|
||||
const d = await apiPost('/api/base64/' + op, { text });
|
||||
if (d.success) { document.getElementById('b64Output').value = d.result; setStatus('b64Status','success', op === 'encode' ? 'Encoded ✓' : 'Decoded ✓'); }
|
||||
else setStatus('b64Status','error', d.error);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Byte Size Converter
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function convertBytes() {
|
||||
const value = parseFloat(document.getElementById('byteValue').value);
|
||||
if (isNaN(value)) return;
|
||||
const unit = document.getElementById('byteUnit').value;
|
||||
const mode = document.querySelector('input[name="byteMode"]:checked').value;
|
||||
const d = await apiPost('/api/bytes/convert', { value, unit, mode });
|
||||
if (d.success) {
|
||||
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
||||
document.getElementById('byteResults').innerHTML = units.map(u =>
|
||||
`<div class="result-row">
|
||||
<div class="label">${u}</div>
|
||||
<div class="value" onclick="copyText(this.textContent)" title="Click to copy">${d[u]}</div>
|
||||
</div>`
|
||||
).join('');
|
||||
setStatus('byteStatus', 'success', `Converted (${mode === 'binary' ? '1024' : '1000'} base) ✓`);
|
||||
} else setStatus('byteStatus', 'error', d.error);
|
||||
}
|
||||
setTimeout(convertBytes, 200);
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Case Converter
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function convertCase() {
|
||||
const text = document.getElementById('caseInput').value;
|
||||
if (!text) return;
|
||||
const d = await apiPost('/api/text/case', { text });
|
||||
if (d.success) {
|
||||
document.getElementById('caseResults').innerHTML = [
|
||||
['UPPERCASE', d.uppercase], ['lowercase', d.lowercase], ['Title Case', d.titleCase],
|
||||
['camelCase', d.camelCase], ['snake_case', d.snakeCase], ['kebab-case', d.kebabCase],
|
||||
['dot.case', d.dotCase], ['desreveR', d.reversed]
|
||||
].map(([l, v]) => `<div class="result-row"><div class="label">${l}</div><div class="value" onclick="copyText(this.textContent)">${v}</div></div>`).join('');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Chmod Calculator
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function chmodFromNumeric() {
|
||||
const val = document.getElementById('chmodNumeric').value.trim();
|
||||
if (!val || !/^[0-7]{3,4}$/.test(val)) return;
|
||||
const d = await apiPost('/api/chmod/calculate', { numeric: val });
|
||||
if (d.success) updateChmodUI(d);
|
||||
}
|
||||
async function chmodFromSymbolic() {
|
||||
const val = document.getElementById('chmodSymbolic').value.trim();
|
||||
if (!val || val.length < 9) return;
|
||||
const d = await apiPost('/api/chmod/calculate', { symbolic: val });
|
||||
if (d.success) updateChmodUI(d);
|
||||
}
|
||||
function updateChmodUI(d) {
|
||||
document.getElementById('chmodNumeric').value = d.numeric;
|
||||
document.getElementById('chmodSymbolic').value = d.symbolic;
|
||||
document.getElementById('chmodCommand').textContent = `chmod ${d.numeric} filename`;
|
||||
const roles = ['owner', 'group', 'others'];
|
||||
const perms = ['read', 'write', 'execute'];
|
||||
let html = '<div style="display:grid;grid-template-columns:100px repeat(3,1fr);gap:6px;font-size:0.82rem;">';
|
||||
html += '<div></div>' + perms.map(p => `<div style="text-align:center;color:var(--text-muted);text-transform:uppercase;font-size:0.7rem;font-weight:600;">${p}</div>`).join('');
|
||||
for (const role of roles) {
|
||||
html += `<div style="color:var(--text-secondary);font-weight:600;text-transform:capitalize;">${role}</div>`;
|
||||
for (const perm of perms) {
|
||||
const on = d[role][perm];
|
||||
html += `<div style="text-align:center;padding:6px;background:${on ? 'var(--green)' : 'var(--bg-input)'};color:${on ? '#fff' : 'var(--text-muted)'};border-radius:var(--radius-sm);font-weight:600;border:1px solid var(--border);">${on ? '✓' : '—'}</div>`;
|
||||
}
|
||||
}
|
||||
html += '</div>';
|
||||
document.getElementById('chmodMatrix').innerHTML = html;
|
||||
setStatus('chmodStatus', 'success', `${d.numeric} = ${d.symbolic} ✓`);
|
||||
}
|
||||
setTimeout(chmodFromNumeric, 200);
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Color Converter
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function convertColor() {
|
||||
const color = document.getElementById('colorInput').value.trim();
|
||||
if (!color) return setStatus('colorStatus','error','Enter a color.');
|
||||
const d = await apiPost('/api/color/convert', { color });
|
||||
if (d.success) {
|
||||
document.getElementById('colorPreview').style.background = d.hex;
|
||||
document.getElementById('colorPicker').value = d.hex;
|
||||
const c = document.getElementById('colorResults');
|
||||
c.innerHTML = [
|
||||
['HEX', d.hex], ['RGB', d.rgb], ['HSL', d.hsl]
|
||||
].map(([l, v]) => `<div class="result-row"><div class="label">${l}</div><div class="value" onclick="copyText(this.textContent)">${v}</div></div>`).join('');
|
||||
setStatus('colorStatus','success','Converted ✓');
|
||||
} else setStatus('colorStatus','error', d.error);
|
||||
}
|
||||
|
||||
// Keyboard shortcut
|
||||
document.getElementById('colorInput').addEventListener('keydown', e => { if(e.key==='Enter') convertColor(); });
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Word Counter
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function updateCounter() {
|
||||
const text = document.getElementById('counterInput').value;
|
||||
const d = await apiPost('/api/text/stats', { text });
|
||||
if (d.success) {
|
||||
document.getElementById('counterResults').innerHTML = `
|
||||
<div class="ip-grid">
|
||||
<div class="ip-card"><div class="label">Characters</div><div class="value">${d.characters}</div></div>
|
||||
<div class="ip-card"><div class="label">No Spaces</div><div class="value">${d.charactersNoSpaces}</div></div>
|
||||
<div class="ip-card"><div class="label">Words</div><div class="value">${d.words}</div></div>
|
||||
<div class="ip-card"><div class="label">Sentences</div><div class="value">${d.sentences}</div></div>
|
||||
<div class="ip-card"><div class="label">Paragraphs</div><div class="value">${d.paragraphs}</div></div>
|
||||
<div class="ip-card"><div class="label">Lines</div><div class="value">${d.lines}</div></div>
|
||||
<div class="ip-card"><div class="label">Reading Time</div><div class="value">${d.readingTime}</div></div>
|
||||
</div>
|
||||
${d.topChars.length ? '<div class="panel-label" style="margin-top:14px;">Top Characters</div><div style="display:flex;gap:6px;flex-wrap:wrap;">' + d.topChars.map(([ch,n]) => `<span style="background:var(--bg-input);border:1px solid var(--border);padding:4px 10px;border-radius:var(--radius-sm);font-family:var(--font-mono);font-size:0.8rem;"><strong style="color:var(--accent);">${ch}</strong> <span style="color:var(--text-muted);">×${n}</span></span>`).join('') + '</div>' : ''}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Cron Parser
|
||||
// ═══════════════════════════════════════════════════════
|
||||
function setCronPreset(expr) {
|
||||
document.getElementById('cronInput').value = expr;
|
||||
parseCron();
|
||||
}
|
||||
|
||||
async function parseCron() {
|
||||
const expr = document.getElementById('cronInput').value.trim();
|
||||
if (!expr) return setStatus('cronStatus', 'error', 'Enter a cron expression.');
|
||||
const d = await apiPost('/api/cron/parse', { expression: expr });
|
||||
if (d.success) {
|
||||
document.getElementById('cronResult').style.display = 'block';
|
||||
document.getElementById('cronDescription').textContent = d.description;
|
||||
document.getElementById('cronFields').innerHTML = Object.entries(d.fields).map(([k, v]) =>
|
||||
`<div class="result-row"><div class="label">${k}</div><div class="value">${v}</div></div>`
|
||||
).join('');
|
||||
document.getElementById('cronNextRuns').innerHTML = d.nextRuns.map((t, i) =>
|
||||
`<div class="result-row"><div class="label">Run #${i + 1}</div><div class="value" onclick="copyText(this.textContent)" title="Click to copy">${t}</div></div>`
|
||||
).join('');
|
||||
setStatus('cronStatus', 'success', 'Parsed ✓');
|
||||
} else {
|
||||
document.getElementById('cronResult').style.display = 'none';
|
||||
setStatus('cronStatus', 'error', d.error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// CSS Minifier
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function minifyCSS() {
|
||||
const css = document.getElementById('cssInput').value;
|
||||
if (!css) return setStatus('cssStatus','error','Enter some CSS.');
|
||||
const d = await apiPost('/api/css/minify', { css });
|
||||
if (d.success) {
|
||||
document.getElementById('cssOutput').value = d.result;
|
||||
setStatus('cssStatus','success', `Minified ✓ — saved ${d.saved} chars (${d.percentage}% smaller)`);
|
||||
} else setStatus('cssStatus','error', d.error);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Diff Checker
|
||||
// ═══════════════════════════════════════════════════════
|
||||
function computeDiff() {
|
||||
const a = document.getElementById('diffA').value.split('\n');
|
||||
const b = document.getElementById('diffB').value.split('\n');
|
||||
const max = Math.max(a.length, b.length);
|
||||
let html = '';
|
||||
for (let i = 0; i < max; i++) {
|
||||
const la = a[i] !== undefined ? a[i] : null;
|
||||
const lb = b[i] !== undefined ? b[i] : null;
|
||||
const esc = s => s.replace(/[<>&]/g, c => ({'<':'<','>':'>','&':'&'}[c]));
|
||||
if (la === null) html += `<div class="diff-line diff-add">+ ${esc(lb)}</div>`;
|
||||
else if (lb === null) html += `<div class="diff-line diff-del">- ${esc(la)}</div>`;
|
||||
else if (la === lb) html += `<div class="diff-line diff-same"> ${esc(la)}</div>`;
|
||||
else {
|
||||
html += `<div class="diff-line diff-del">- ${esc(la)}</div>`;
|
||||
html += `<div class="diff-line diff-add">+ ${esc(lb)}</div>`;
|
||||
}
|
||||
}
|
||||
document.getElementById('diffOutput').innerHTML = html || '<span style="color:var(--text-muted);">Texts are identical.</span>';
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// ENV ↔ JSON Converter
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function envToJson() {
|
||||
const input = document.getElementById('envInput').value.trim();
|
||||
if (!input) return setStatus('envJsonStatus', 'error', 'Paste some .env content first.');
|
||||
const d = await apiPost('/api/convert/env-to-json', { env: input });
|
||||
if (d.success) {
|
||||
document.getElementById('envJsonOutput').value = JSON.stringify(d.result, null, 2);
|
||||
setStatus('envJsonStatus', 'success', `Converted ✓ — ${Object.keys(d.result).length} variables`);
|
||||
} else setStatus('envJsonStatus', 'error', d.error);
|
||||
}
|
||||
async function jsonToEnv() {
|
||||
const input = document.getElementById('envJsonInput').value.trim();
|
||||
if (!input) return setStatus('envJsonStatus', 'error', 'Paste some JSON first.');
|
||||
const d = await apiPost('/api/convert/json-to-env', { json: input });
|
||||
if (d.success) {
|
||||
document.getElementById('envJsonOutput').value = d.result;
|
||||
setStatus('envJsonStatus', 'success', `Converted ✓ — ${d.count} variables`);
|
||||
} else setStatus('envJsonStatus', 'error', d.error);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// String Escape
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function escapeOp(op) {
|
||||
const text = document.getElementById('escInput').value;
|
||||
if (!text) return setStatus('escStatus','error','Enter text.');
|
||||
const d = await apiPost('/api/' + op, { text });
|
||||
if (d.success) { document.getElementById('escOutput').value = d.result; setStatus('escStatus','success', op === 'escape' ? 'Escaped ✓' : 'Unescaped ✓'); }
|
||||
else setStatus('escStatus','error', d.error);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Hash
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function generateHash() {
|
||||
const text = document.getElementById('hashInput').value;
|
||||
if (!text) return setStatus('hashStatus','error','Enter text to hash.');
|
||||
const d = await apiPost('/api/hash', { text });
|
||||
if (d.success) {
|
||||
const c = document.getElementById('hashResults');
|
||||
c.innerHTML = Object.entries(d.hashes).map(([algo, hash]) => `
|
||||
<div class="result-row">
|
||||
<div class="label">${algo.toUpperCase()}</div>
|
||||
<div class="value" onclick="copyText(this.textContent)" title="Click to copy">${hash}</div>
|
||||
</div>`).join('');
|
||||
setStatus('hashStatus','success','Generated ✓');
|
||||
} else setStatus('hashStatus','error', d.error);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// HMAC Generator
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function generateHmac() {
|
||||
const message = document.getElementById('hmacMessage').value;
|
||||
const secret = document.getElementById('hmacSecret').value;
|
||||
const algorithm = document.getElementById('hmacAlgo').value;
|
||||
if (!message) return setStatus('hmacStatus', 'error', 'Enter a message.');
|
||||
if (!secret) return setStatus('hmacStatus', 'error', 'Enter a secret key.');
|
||||
const d = await apiPost('/api/hmac', { message, secret, algorithm });
|
||||
if (d.success) {
|
||||
document.getElementById('hmacResults').innerHTML = `
|
||||
<div class="result-row">
|
||||
<div class="label">${d.algorithm.toUpperCase()} HMAC</div>
|
||||
<div class="value" onclick="copyText(this.textContent)" title="Click to copy">${d.hmac}</div>
|
||||
</div>`;
|
||||
setStatus('hmacStatus', 'success', 'Generated ✓');
|
||||
} else setStatus('hmacStatus', 'error', d.error);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// HTML Entities
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function htmlEntOp(op) {
|
||||
const text = document.getElementById('htmlEntInput').value;
|
||||
if (!text) return setStatus('htmlEntStatus','error','Enter text.');
|
||||
const d = await apiPost('/api/html/' + op, { text });
|
||||
if (d.success) { document.getElementById('htmlEntOutput').value = d.result; setStatus('htmlEntStatus','success', op === 'encode' ? 'Encoded ✓' : 'Decoded ✓'); }
|
||||
else setStatus('htmlEntStatus','error', d.error);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// HTTP Status Codes Reference
|
||||
// ═══════════════════════════════════════════════════════
|
||||
const HTTP_CODES = [
|
||||
// 1xx
|
||||
{ code: 100, text: 'Continue', desc: 'The server has received the request headers, and the client should proceed to send the request body.', cat: '1xx' },
|
||||
{ code: 101, text: 'Switching Protocols', desc: 'The server is switching protocols as requested by the client (e.g., to WebSocket).', cat: '1xx' },
|
||||
{ code: 102, text: 'Processing', desc: 'The server has received and is processing the request, but no response is available yet.', cat: '1xx' },
|
||||
{ code: 103, text: 'Early Hints', desc: 'Used to return some response headers before the final HTTP message.', cat: '1xx' },
|
||||
// 2xx
|
||||
{ code: 200, text: 'OK', desc: 'The request has succeeded. The meaning depends on the HTTP method used.', cat: '2xx' },
|
||||
{ code: 201, text: 'Created', desc: 'The request has been fulfilled and a new resource has been created.', cat: '2xx' },
|
||||
{ code: 202, text: 'Accepted', desc: 'The request has been accepted for processing, but processing is not complete.', cat: '2xx' },
|
||||
{ code: 203, text: 'Non-Authoritative Information', desc: 'The returned meta-information is from a local or third-party copy.', cat: '2xx' },
|
||||
{ code: 204, text: 'No Content', desc: 'The server has fulfilled the request but does not need to return an entity-body.', cat: '2xx' },
|
||||
{ code: 205, text: 'Reset Content', desc: 'The server has fulfilled the request and the user agent should reset the document view.', cat: '2xx' },
|
||||
{ code: 206, text: 'Partial Content', desc: 'The server is delivering only part of the resource due to a range header sent by the client.', cat: '2xx' },
|
||||
{ code: 207, text: 'Multi-Status', desc: 'A Multi-Status response conveys information about multiple resources (WebDAV).', cat: '2xx' },
|
||||
{ code: 208, text: 'Already Reported', desc: 'Members of a DAV binding have already been enumerated in a previous reply.', cat: '2xx' },
|
||||
{ code: 226, text: 'IM Used', desc: 'The server has fulfilled a GET request for the resource with instance manipulations applied.', cat: '2xx' },
|
||||
// 3xx
|
||||
{ code: 300, text: 'Multiple Choices', desc: 'There are multiple options for the resource, each with specific attributes.', cat: '3xx' },
|
||||
{ code: 301, text: 'Moved Permanently', desc: 'This and all future requests should be directed to the given URI.', cat: '3xx' },
|
||||
{ code: 302, text: 'Found', desc: 'The resource was found at a different URI temporarily.', cat: '3xx' },
|
||||
{ code: 303, text: 'See Other', desc: 'The response can be found at another URI using a GET method.', cat: '3xx' },
|
||||
{ code: 304, text: 'Not Modified', desc: 'The resource has not been modified since the last request.', cat: '3xx' },
|
||||
{ code: 307, text: 'Temporary Redirect', desc: 'The request should be repeated with another URI, but future requests should still use the original URI.', cat: '3xx' },
|
||||
{ code: 308, text: 'Permanent Redirect', desc: 'This and all future requests should be directed to the given URI (no method change).', cat: '3xx' },
|
||||
// 4xx
|
||||
{ code: 400, text: 'Bad Request', desc: 'The server cannot process the request due to a client error (e.g., malformed request syntax).', cat: '4xx' },
|
||||
{ code: 401, text: 'Unauthorized', desc: 'Authentication is required and has failed or has not been provided.', cat: '4xx' },
|
||||
{ code: 402, text: 'Payment Required', desc: 'Reserved for future use. Some APIs use this for rate limiting or paid features.', cat: '4xx' },
|
||||
{ code: 403, text: 'Forbidden', desc: 'The server understood the request but refuses to authorize it.', cat: '4xx' },
|
||||
{ code: 404, text: 'Not Found', desc: 'The requested resource could not be found on this server.', cat: '4xx' },
|
||||
{ code: 405, text: 'Method Not Allowed', desc: 'The request method is not supported for the requested resource.', cat: '4xx' },
|
||||
{ code: 406, text: 'Not Acceptable', desc: 'The requested resource can only generate content not acceptable per the Accept headers.', cat: '4xx' },
|
||||
{ code: 407, text: 'Proxy Authentication Required', desc: 'The client must authenticate itself with the proxy.', cat: '4xx' },
|
||||
{ code: 408, text: 'Request Timeout', desc: 'The server timed out waiting for the request.', cat: '4xx' },
|
||||
{ code: 409, text: 'Conflict', desc: 'The request could not be processed because of conflict in the current state of the resource.', cat: '4xx' },
|
||||
{ code: 410, text: 'Gone', desc: 'The resource requested is no longer available and will not be available again.', cat: '4xx' },
|
||||
{ code: 411, text: 'Length Required', desc: 'The request did not specify the length of its content, which is required.', cat: '4xx' },
|
||||
{ code: 412, text: 'Precondition Failed', desc: 'The server does not meet one of the preconditions set by the requester.', cat: '4xx' },
|
||||
{ code: 413, text: 'Payload Too Large', desc: 'The request is larger than the server is willing or able to process.', cat: '4xx' },
|
||||
{ code: 414, text: 'URI Too Long', desc: 'The URI provided was too long for the server to process.', cat: '4xx' },
|
||||
{ code: 415, text: 'Unsupported Media Type', desc: 'The request entity has a media type which the server does not support.', cat: '4xx' },
|
||||
{ code: 416, text: 'Range Not Satisfiable', desc: 'The client has asked for a portion of the file that the server cannot supply.', cat: '4xx' },
|
||||
{ code: 418, text: "I'm a Teapot", desc: "The server refuses to brew coffee because it is, permanently, a teapot. (RFC 2324)", cat: '4xx' },
|
||||
{ code: 422, text: 'Unprocessable Entity', desc: 'The request was well-formed but was unable to be followed due to semantic errors.', cat: '4xx' },
|
||||
{ code: 425, text: 'Too Early', desc: 'The server is unwilling to risk processing a request that might be replayed.', cat: '4xx' },
|
||||
{ code: 429, text: 'Too Many Requests', desc: 'The user has sent too many requests in a given amount of time (rate limiting).', cat: '4xx' },
|
||||
{ code: 451, text: 'Unavailable For Legal Reasons', desc: 'The resource is unavailable due to legal demands (e.g., censorship or government order).', cat: '4xx' },
|
||||
// 5xx
|
||||
{ code: 500, text: 'Internal Server Error', desc: 'An unexpected condition was encountered by the server.', cat: '5xx' },
|
||||
{ code: 501, text: 'Not Implemented', desc: 'The server does not support the functionality required to fulfill the request.', cat: '5xx' },
|
||||
{ code: 502, text: 'Bad Gateway', desc: 'The server received an invalid response from an upstream server.', cat: '5xx' },
|
||||
{ code: 503, text: 'Service Unavailable', desc: 'The server is currently unable to handle the request (overloaded or maintenance).', cat: '5xx' },
|
||||
{ code: 504, text: 'Gateway Timeout', desc: 'The server did not receive a timely response from an upstream server.', cat: '5xx' },
|
||||
{ code: 505, text: 'HTTP Version Not Supported', desc: 'The server does not support the HTTP protocol version used in the request.', cat: '5xx' },
|
||||
{ code: 507, text: 'Insufficient Storage', desc: 'The server is unable to store the representation needed to complete the request.', cat: '5xx' },
|
||||
{ code: 508, text: 'Loop Detected', desc: 'The server detected an infinite loop while processing the request.', cat: '5xx' },
|
||||
{ code: 511, text: 'Network Authentication Required', desc: 'The client needs to authenticate to gain network access (captive portal).', cat: '5xx' },
|
||||
];
|
||||
|
||||
const catColors = { '1xx': 'var(--accent)', '2xx': 'var(--green)', '3xx': 'var(--cyan)', '4xx': 'var(--orange)', '5xx': 'var(--red)' };
|
||||
let currentHttpCat = 'all';
|
||||
|
||||
function renderHttpStatus(filtered) {
|
||||
const list = document.getElementById('httpStatusList');
|
||||
if (filtered.length === 0) {
|
||||
list.innerHTML = '<div style="text-align:center;color:var(--text-muted);padding:32px;">No matching status codes found.</div>';
|
||||
return;
|
||||
}
|
||||
list.innerHTML = filtered.map(c => `
|
||||
<div class="result-row" style="flex-direction:column;align-items:flex-start;gap:6px;margin-bottom:8px;cursor:pointer;" onclick="copyText('${c.code} ${c.text}')">
|
||||
<div style="display:flex;align-items:center;gap:10px;width:100%;">
|
||||
<span style="font-family:var(--font-mono);font-size:1.1rem;font-weight:800;color:${catColors[c.cat]};">${c.code}</span>
|
||||
<span style="font-weight:600;color:var(--text-primary);">${c.text}</span>
|
||||
<span class="tag" style="background:${catColors[c.cat]}20;color:${catColors[c.cat]};margin-left:auto;">${c.cat}</span>
|
||||
</div>
|
||||
<div style="font-size:0.8rem;color:var(--text-secondary);line-height:1.5;">${c.desc}</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function filterHttpStatus() {
|
||||
const q = document.getElementById('httpStatusInput').value.toLowerCase();
|
||||
let filtered = HTTP_CODES;
|
||||
if (currentHttpCat !== 'all') filtered = filtered.filter(c => c.cat === currentHttpCat);
|
||||
if (q) filtered = filtered.filter(c => String(c.code).includes(q) || c.text.toLowerCase().includes(q) || c.desc.toLowerCase().includes(q));
|
||||
renderHttpStatus(filtered);
|
||||
}
|
||||
|
||||
function filterHttpCat(cat, btn) {
|
||||
currentHttpCat = cat;
|
||||
document.querySelectorAll('#httpCatBtns .btn').forEach(b => {
|
||||
b.className = b === btn ? 'btn btn-sm btn-primary' : 'btn btn-sm btn-secondary';
|
||||
});
|
||||
filterHttpStatus();
|
||||
}
|
||||
|
||||
// Initialize
|
||||
setTimeout(() => renderHttpStatus(HTTP_CODES), 100);
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// IP Lookup
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function lookupIP() {
|
||||
const ip = document.getElementById('ipInput').value.trim();
|
||||
setStatus('ipStatus','info','Looking up...');
|
||||
const d = await apiGet(ip ? '/api/ip/' + ip : '/api/ip');
|
||||
if (d.success && d.status !== 'fail') {
|
||||
const fields = [
|
||||
['IP Address', d.query], ['Country', d.country], ['Region', d.regionName],
|
||||
['City', d.city], ['ZIP', d.zip], ['Latitude', d.lat],
|
||||
['Longitude', d.lon], ['Timezone', d.timezone], ['ISP', d.isp],
|
||||
['Organization', d.org], ['AS', d.as]
|
||||
];
|
||||
document.getElementById('ipResults').innerHTML = fields.map(([l, v]) =>
|
||||
`<div class="ip-card"><div class="label">${l}</div><div class="value">${v || '—'}</div></div>`).join('');
|
||||
setStatus('ipStatus','success','Lookup complete ✓');
|
||||
} else setStatus('ipStatus','error', d.message || d.error || 'Lookup failed');
|
||||
}
|
||||
|
||||
// Keyboard shortcut
|
||||
document.getElementById('ipInput').addEventListener('keydown', e => { if(e.key==='Enter') lookupIP(); });
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// JSON Formatter
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function formatJSON() {
|
||||
const input = document.getElementById('jsonInput').value.trim();
|
||||
if (!input) return setStatus('jsonStatus','error','Paste some JSON first.');
|
||||
const iv = document.getElementById('jsonIndent').value;
|
||||
const indent = iv === '\\t' ? '\t' : parseInt(iv);
|
||||
const d = await apiPost('/api/json/format', { json: input, indent });
|
||||
if (d.success) { document.getElementById('jsonOutput').value = d.result; setStatus('jsonStatus','success','Formatted ✓'); }
|
||||
else setStatus('jsonStatus','error', d.error);
|
||||
}
|
||||
async function minifyJSON() {
|
||||
const input = document.getElementById('jsonInput').value.trim();
|
||||
if (!input) return setStatus('jsonStatus','error','Paste some JSON first.');
|
||||
const d = await apiPost('/api/json/minify', { json: input });
|
||||
if (d.success) { document.getElementById('jsonOutput').value = d.result; setStatus('jsonStatus','success','Minified ✓'); }
|
||||
else setStatus('jsonStatus','error', d.error);
|
||||
}
|
||||
async function validateJSON() {
|
||||
const input = document.getElementById('jsonInput').value.trim();
|
||||
if (!input) return setStatus('jsonStatus','error','Paste some JSON first.');
|
||||
const d = await apiPost('/api/json/validate', { json: input });
|
||||
setStatus('jsonStatus', d.valid ? 'success' : 'error', d.message);
|
||||
}
|
||||
function clearJSON() {
|
||||
document.getElementById('jsonInput').value=''; document.getElementById('jsonOutput').value='';
|
||||
document.getElementById('jsonStatus').className='status';
|
||||
}
|
||||
|
||||
// Keyboard shortcuts
|
||||
document.getElementById('jsonInput').addEventListener('keydown', e => {
|
||||
if(e.key==='Enter' && (e.ctrlKey||e.metaKey)) formatJSON();
|
||||
if(e.key==='Tab') { e.preventDefault(); const t=e.target,s=t.selectionStart,en=t.selectionEnd; t.value=t.value.substring(0,s)+' '+t.value.substring(en); t.selectionStart=t.selectionEnd=s+2; }
|
||||
});
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// JSON ↔ CSV Converter
|
||||
// ═══════════════════════════════════════════════════════
|
||||
let lastJsoncsvType = 'csv'; // track last conversion type for download
|
||||
|
||||
async function jsonToCsv() {
|
||||
const input = document.getElementById('jsoncsvJsonInput').value.trim();
|
||||
if (!input) return setStatus('jsoncsvStatus', 'error', 'Paste some JSON first.');
|
||||
const d = await apiPost('/api/convert/json-to-csv', { json: input });
|
||||
if (d.success) {
|
||||
document.getElementById('jsoncsvOutput').value = d.result;
|
||||
lastJsoncsvType = 'csv';
|
||||
setStatus('jsoncsvStatus', 'success', `Converted ✓ — ${d.rows} rows, ${d.columns} columns`);
|
||||
} else setStatus('jsoncsvStatus', 'error', d.error);
|
||||
}
|
||||
|
||||
async function csvToJson() {
|
||||
const input = document.getElementById('jsoncsvCsvInput').value.trim();
|
||||
if (!input) return setStatus('jsoncsvStatus', 'error', 'Paste some CSV first.');
|
||||
const d = await apiPost('/api/convert/csv-to-json', { csv: input });
|
||||
if (d.success) {
|
||||
document.getElementById('jsoncsvOutput').value = JSON.stringify(d.result, null, 2);
|
||||
lastJsoncsvType = 'json';
|
||||
setStatus('jsoncsvStatus', 'success', `Converted ✓ — ${d.result.length} records`);
|
||||
} else setStatus('jsoncsvStatus', 'error', d.error);
|
||||
}
|
||||
|
||||
function downloadJsoncsvOutput() {
|
||||
const output = document.getElementById('jsoncsvOutput').value;
|
||||
if (!output) return setStatus('jsoncsvStatus', 'error', 'Nothing to download.');
|
||||
const ext = lastJsoncsvType === 'csv' ? 'csv' : 'json';
|
||||
const mime = lastJsoncsvType === 'csv' ? 'text/csv' : 'application/json';
|
||||
const blob = new Blob([output], { type: mime });
|
||||
const a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = `converted.${ext}`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(a.href);
|
||||
setStatus('jsoncsvStatus', 'success', 'Downloaded ✓');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// JWT Decoder
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function decodeJWT() {
|
||||
const token = document.getElementById('jwtInput').value.trim();
|
||||
if (!token) return setStatus('jwtStatus','error','Paste a JWT.');
|
||||
const d = await apiPost('/api/jwt/decode', { token });
|
||||
if (d.success) {
|
||||
const expStr = d.expired === null ? '—' : d.expired ? '<span style="color:var(--red)">EXPIRED ✗</span>' : '<span style="color:var(--green)">VALID ✓</span>';
|
||||
document.getElementById('jwtResults').innerHTML = `
|
||||
<div class="result-card">
|
||||
<div class="panel-label">Header</div>
|
||||
<pre style="background:var(--bg-input);padding:12px;border-radius:var(--radius-sm);font-size:0.82rem;color:var(--cyan);overflow-x:auto;">${JSON.stringify(d.header, null, 2)}</pre>
|
||||
<div class="panel-label" style="margin-top:14px;">Payload</div>
|
||||
<pre style="background:var(--bg-input);padding:12px;border-radius:var(--radius-sm);font-size:0.82rem;color:var(--green);overflow-x:auto;">${JSON.stringify(d.payload, null, 2)}</pre>
|
||||
<div class="panel-label" style="margin-top:14px;">Expiry</div>
|
||||
<div style="font-size:0.9rem;">${expStr}</div>
|
||||
</div>`;
|
||||
setStatus('jwtStatus','success','Decoded ✓');
|
||||
} else setStatus('jwtStatus','error', d.error);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Lorem Ipsum
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function generateLorem() {
|
||||
const d = await apiPost('/api/lorem', { paragraphs: parseInt(document.getElementById('loremCount').value) });
|
||||
if (d.success) document.getElementById('loremOutput').value = d.result;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Markdown Preview
|
||||
// ═══════════════════════════════════════════════════════
|
||||
function renderMarkdown() {
|
||||
const md = document.getElementById('mdInput').value;
|
||||
if (!md) { document.getElementById('mdPreview').innerHTML = '<span style="color:var(--text-muted)">Preview will appear here...</span>'; return; }
|
||||
let html = md
|
||||
.replace(/^### (.+)$/gm, '<h3>$1</h3>')
|
||||
.replace(/^## (.+)$/gm, '<h2>$1</h2>')
|
||||
.replace(/^# (.+)$/gm, '<h1>$1</h1>')
|
||||
.replace(/^---$/gm, '<hr>')
|
||||
.replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>')
|
||||
.replace(/`([^`]+)`/g, '<code>$1</code>')
|
||||
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
||||
.replace(/\*(.+?)\*/g, '<em>$1</em>')
|
||||
.replace(/~~(.+?)~~/g, '<del>$1</del>')
|
||||
.replace(/^\> (.+)$/gm, '<blockquote>$1</blockquote>')
|
||||
.replace(/^\- (.+)$/gm, '<li>$1</li>')
|
||||
.replace(/^\* (.+)$/gm, '<li>$1</li>')
|
||||
.replace(/^\d+\. (.+)$/gm, '<li>$1</li>')
|
||||
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>')
|
||||
.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1" style="max-width:100%;border-radius:8px;">')
|
||||
.replace(/\n\n/g, '</p><p>')
|
||||
.replace(/\n/g, '<br>');
|
||||
html = '<p>' + html + '</p>';
|
||||
// Wrap consecutive li in ul
|
||||
html = html.replace(/(<li>.*?<\/li>)+/gs, '<ul>$&</ul>');
|
||||
document.getElementById('mdPreview').innerHTML = html;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Number Base Converter
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function convertNumber() {
|
||||
const v = document.getElementById('numInput').value.trim();
|
||||
if (!v) return setStatus('numStatus','error','Enter a number.');
|
||||
const d = await apiPost('/api/number/convert', { value: v, fromBase: document.getElementById('numBase').value });
|
||||
if (d.success) {
|
||||
document.getElementById('numResults').innerHTML = [
|
||||
['Decimal', d.decimal], ['Binary', d.binary], ['Octal', d.octal], ['Hexadecimal', d.hex]
|
||||
].map(([l, v]) => `<div class="result-row"><div class="label">${l}</div><div class="value" onclick="copyText(this.textContent)">${v}</div></div>`).join('');
|
||||
setStatus('numStatus','success','Converted ✓');
|
||||
} else setStatus('numStatus','error', d.error);
|
||||
}
|
||||
|
||||
// Keyboard shortcut
|
||||
document.getElementById('numInput').addEventListener('keydown', e => { if(e.key==='Enter') convertNumber(); });
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Password Generator
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function generatePassword() {
|
||||
const d = await apiPost('/api/password', {
|
||||
length: parseInt(document.getElementById('pwLength').value),
|
||||
uppercase: document.getElementById('pwUpper').checked,
|
||||
lowercase: document.getElementById('pwLower').checked,
|
||||
numbers: document.getElementById('pwNumbers').checked,
|
||||
symbols: document.getElementById('pwSymbols').checked,
|
||||
count: 5
|
||||
});
|
||||
if (d.success) {
|
||||
document.getElementById('pwDisplay').textContent = d.passwords[0];
|
||||
// Strength
|
||||
const len = d.passwords[0].length;
|
||||
const str = len >= 20 ? 100 : len >= 12 ? 75 : len >= 8 ? 50 : 25;
|
||||
const colors = { 25: 'var(--red)', 50: 'var(--orange)', 75: 'var(--yellow)', 100: 'var(--green)' };
|
||||
const fill = document.getElementById('pwStrength');
|
||||
fill.style.width = str + '%';
|
||||
fill.style.background = colors[str];
|
||||
// Batch
|
||||
document.getElementById('pwBatch').innerHTML = '<div class="panel-label">More Passwords</div>' +
|
||||
d.passwords.slice(1).map(pw => `<div class="result-row"><div class="value" onclick="copyText(this.textContent)" style="max-width:100%;text-align:left;">${pw}</div></div>`).join('');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Placeholder Image Generator
|
||||
// ═══════════════════════════════════════════════════════
|
||||
function generatePlaceholder() {
|
||||
const w = parseInt(document.getElementById('phWidth').value) || 400;
|
||||
const h = parseInt(document.getElementById('phHeight').value) || 300;
|
||||
const text = document.getElementById('phText').value || `${w} × ${h}`;
|
||||
const bg = document.getElementById('phBg').value;
|
||||
const fg = document.getElementById('phFg').value;
|
||||
const fontSize = parseInt(document.getElementById('phFontSize').value) || 28;
|
||||
|
||||
document.getElementById('phBgText').value = bg;
|
||||
document.getElementById('phFgText').value = fg;
|
||||
document.getElementById('phFontSizeVal').textContent = fontSize + 'px';
|
||||
|
||||
const canvas = document.getElementById('phCanvas');
|
||||
canvas.width = w;
|
||||
canvas.height = h;
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// Background
|
||||
ctx.fillStyle = bg;
|
||||
ctx.fillRect(0, 0, w, h);
|
||||
|
||||
// Cross lines
|
||||
ctx.strokeStyle = fg + '20';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, 0); ctx.lineTo(w, h);
|
||||
ctx.moveTo(w, 0); ctx.lineTo(0, h);
|
||||
ctx.stroke();
|
||||
|
||||
// Text
|
||||
ctx.fillStyle = fg;
|
||||
ctx.font = `${fontSize}px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif`;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(text, w / 2, h / 2);
|
||||
}
|
||||
|
||||
function downloadPlaceholder() {
|
||||
const canvas = document.getElementById('phCanvas');
|
||||
const link = document.createElement('a');
|
||||
const w = document.getElementById('phWidth').value || 400;
|
||||
const h = document.getElementById('phHeight').value || 300;
|
||||
link.download = `placeholder-${w}x${h}.png`;
|
||||
link.href = canvas.toDataURL('image/png');
|
||||
link.click();
|
||||
setStatus('phStatus', 'success', 'Downloaded ✓');
|
||||
}
|
||||
|
||||
function copyPlaceholderDataUrl() {
|
||||
const canvas = document.getElementById('phCanvas');
|
||||
copyText(canvas.toDataURL('image/png'));
|
||||
setStatus('phStatus', 'success', 'Data URL copied ✓');
|
||||
}
|
||||
|
||||
// Initialize on load
|
||||
setTimeout(generatePlaceholder, 100);
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// QR Code (using qrcode-generator library)
|
||||
// ═══════════════════════════════════════════════════════
|
||||
function generateQR() {
|
||||
const data = document.getElementById('qrInput').value.trim();
|
||||
const container = document.getElementById('qrCanvas');
|
||||
const ph = document.getElementById('qrPlaceholder');
|
||||
if (!data) { container.style.display = 'none'; ph.style.display = ''; return; }
|
||||
if (typeof qrcode === 'undefined') { ph.textContent = 'Loading QR library...'; return; }
|
||||
|
||||
try {
|
||||
const qr = qrcode(0, 'M');
|
||||
qr.addData(data);
|
||||
qr.make();
|
||||
container.innerHTML = qr.createSvgTag({ cellSize: 4, margin: 8, scalable: true });
|
||||
// Style the SVG
|
||||
const svg = container.querySelector('svg');
|
||||
if (svg) {
|
||||
svg.style.width = '256px';
|
||||
svg.style.height = '256px';
|
||||
svg.style.background = '#fff';
|
||||
svg.style.borderRadius = '12px';
|
||||
}
|
||||
container.style.display = '';
|
||||
ph.style.display = 'none';
|
||||
} catch (err) {
|
||||
ph.textContent = err.message;
|
||||
container.style.display = 'none';
|
||||
ph.style.display = '';
|
||||
}
|
||||
}
|
||||
|
||||
function downloadQR() {
|
||||
const container = document.getElementById('qrCanvas');
|
||||
if (container.style.display === 'none') return;
|
||||
const svg = container.querySelector('svg');
|
||||
if (!svg) return;
|
||||
|
||||
// Convert SVG to PNG via canvas
|
||||
const svgData = new XMLSerializer().serializeToString(svg);
|
||||
const img = new Image();
|
||||
img.onload = function () {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 512;
|
||||
canvas.height = 512;
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.fillRect(0, 0, 512, 512);
|
||||
ctx.drawImage(img, 0, 0, 512, 512);
|
||||
const a = document.createElement('a');
|
||||
a.href = canvas.toDataURL('image/png');
|
||||
a.download = 'qrcode.png';
|
||||
a.click();
|
||||
};
|
||||
img.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgData)));
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// QR Code Reader (using jsQR library)
|
||||
// ═══════════════════════════════════════════════════════
|
||||
let qrCameraStream = null;
|
||||
let qrScanInterval = null;
|
||||
let qrScanHistoryList = [];
|
||||
|
||||
function startQrCamera() {
|
||||
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
||||
return setStatus('qrReaderStatus', 'error', 'Camera API not supported in this browser.');
|
||||
}
|
||||
|
||||
navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } })
|
||||
.then(stream => {
|
||||
qrCameraStream = stream;
|
||||
const video = document.getElementById('qrVideo');
|
||||
video.srcObject = stream;
|
||||
video.play();
|
||||
|
||||
document.getElementById('qrCameraContainer').style.display = 'block';
|
||||
document.getElementById('qrCameraBtn').style.display = 'none';
|
||||
document.getElementById('qrStopBtn').style.display = '';
|
||||
document.getElementById('qrImagePreview').style.display = 'none';
|
||||
|
||||
setStatus('qrReaderStatus', 'info', 'Camera active — scanning for QR codes...');
|
||||
|
||||
// Start scanning frames
|
||||
const canvas = document.getElementById('qrScanCanvas');
|
||||
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
||||
|
||||
qrScanInterval = setInterval(() => {
|
||||
if (video.readyState === video.HAVE_ENOUGH_DATA) {
|
||||
canvas.width = video.videoWidth;
|
||||
canvas.height = video.videoHeight;
|
||||
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
|
||||
if (typeof jsQR !== 'undefined') {
|
||||
const code = jsQR(imageData.data, imageData.width, imageData.height, { inversionAttempts: 'dontInvert' });
|
||||
if (code) {
|
||||
showQrResult(code.data);
|
||||
setStatus('qrReaderStatus', 'success', 'QR code detected ✓');
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 250);
|
||||
})
|
||||
.catch(err => {
|
||||
setStatus('qrReaderStatus', 'error', 'Camera access denied: ' + err.message);
|
||||
});
|
||||
}
|
||||
|
||||
function stopQrCamera() {
|
||||
if (qrScanInterval) { clearInterval(qrScanInterval); qrScanInterval = null; }
|
||||
if (qrCameraStream) {
|
||||
qrCameraStream.getTracks().forEach(t => t.stop());
|
||||
qrCameraStream = null;
|
||||
}
|
||||
const video = document.getElementById('qrVideo');
|
||||
video.srcObject = null;
|
||||
|
||||
document.getElementById('qrCameraContainer').style.display = 'none';
|
||||
document.getElementById('qrCameraBtn').style.display = '';
|
||||
document.getElementById('qrStopBtn').style.display = 'none';
|
||||
setStatus('qrReaderStatus', 'info', 'Camera stopped.');
|
||||
}
|
||||
|
||||
function scanQrFromFile(event) {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
// Stop camera if running
|
||||
stopQrCamera();
|
||||
|
||||
const img = document.getElementById('qrPreviewImg');
|
||||
const reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
img.src = e.target.result;
|
||||
document.getElementById('qrImagePreview').style.display = 'block';
|
||||
|
||||
const tempImg = new Image();
|
||||
tempImg.onload = function () {
|
||||
const canvas = document.getElementById('qrImgCanvas');
|
||||
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
||||
canvas.width = tempImg.width;
|
||||
canvas.height = tempImg.height;
|
||||
ctx.drawImage(tempImg, 0, 0);
|
||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
|
||||
if (typeof jsQR !== 'undefined') {
|
||||
const code = jsQR(imageData.data, imageData.width, imageData.height, { inversionAttempts: 'attemptBoth' });
|
||||
if (code) {
|
||||
showQrResult(code.data);
|
||||
setStatus('qrReaderStatus', 'success', 'QR code detected in image ✓');
|
||||
} else {
|
||||
setStatus('qrReaderStatus', 'error', 'No QR code found in this image.');
|
||||
}
|
||||
} else {
|
||||
setStatus('qrReaderStatus', 'error', 'QR scanning library not loaded.');
|
||||
}
|
||||
};
|
||||
tempImg.src = e.target.result;
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
|
||||
// Reset file input so same file can be re-selected
|
||||
event.target.value = '';
|
||||
}
|
||||
|
||||
function showQrResult(data) {
|
||||
document.getElementById('qrReaderPlaceholder').style.display = 'none';
|
||||
document.getElementById('qrReaderResult').style.display = 'block';
|
||||
document.getElementById('qrDecodedText').textContent = data;
|
||||
|
||||
// Detect type
|
||||
let type = 'Plain Text';
|
||||
const openBtn = document.getElementById('qrOpenLinkBtn');
|
||||
openBtn.style.display = 'none';
|
||||
|
||||
if (/^https?:\/\//i.test(data)) {
|
||||
type = '🔗 URL';
|
||||
openBtn.style.display = '';
|
||||
} else if (/^mailto:/i.test(data)) {
|
||||
type = '📧 Email';
|
||||
openBtn.style.display = '';
|
||||
} else if (/^tel:/i.test(data)) {
|
||||
type = '📞 Phone Number';
|
||||
} else if (/^BEGIN:VCARD/i.test(data)) {
|
||||
type = '👤 vCard Contact';
|
||||
} else if (/^BEGIN:VEVENT/i.test(data)) {
|
||||
type = '📅 Calendar Event';
|
||||
} else if (/^WIFI:/i.test(data)) {
|
||||
type = '📶 Wi-Fi Network';
|
||||
} else if (/^smsto:/i.test(data)) {
|
||||
type = '💬 SMS';
|
||||
} else if (/^geo:/i.test(data)) {
|
||||
type = '📍 Geolocation';
|
||||
}
|
||||
|
||||
document.getElementById('qrDecodedType').textContent = type;
|
||||
|
||||
// Add to history (keep last 10)
|
||||
const now = new Date().toLocaleTimeString();
|
||||
qrScanHistoryList.unshift({ data: data.length > 80 ? data.slice(0, 80) + '...' : data, time: now, full: data });
|
||||
if (qrScanHistoryList.length > 10) qrScanHistoryList.pop();
|
||||
|
||||
document.getElementById('qrScanHistory').innerHTML = qrScanHistoryList.map(h =>
|
||||
`<div class="result-row" style="cursor:pointer;" onclick="copyText('${h.full.replace(/'/g, "\\'")}')">
|
||||
<div class="label">${h.time}</div>
|
||||
<div class="value" style="font-size:0.78rem;">${h.data.replace(/</g, '<')}</div>
|
||||
</div>`
|
||||
).join('');
|
||||
}
|
||||
|
||||
// Stop camera when navigating away
|
||||
const origShowPage = window.showPage;
|
||||
if (origShowPage) {
|
||||
window.showPage = function (name) {
|
||||
if (name !== 'qrreader' && qrCameraStream) stopQrCamera();
|
||||
origShowPage(name);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Regex Tester
|
||||
// ═══════════════════════════════════════════════════════
|
||||
function testRegex() {
|
||||
const pattern = document.getElementById('regexPattern').value;
|
||||
const flags = document.getElementById('regexFlags').value;
|
||||
const input = document.getElementById('regexInput').value;
|
||||
const resEl = document.getElementById('regexResults');
|
||||
const listEl = document.getElementById('regexMatchList');
|
||||
if (!pattern || !input) { resEl.innerHTML = '<span style="color:var(--text-muted)">Enter a pattern and test string...</span>'; listEl.innerHTML=''; return; }
|
||||
try {
|
||||
const re = new RegExp(pattern, flags);
|
||||
// Highlight matches
|
||||
let highlighted = input.replace(/[<>&]/g, c => ({'<':'<','>':'>','&':'&'}[c]));
|
||||
const safePattern = new RegExp(pattern, flags);
|
||||
highlighted = input.replace(safePattern, m => `<span class="regex-match">${m.replace(/[<>&]/g, c => ({'<':'<','>':'>','&':'&'}[c]))}</span>`);
|
||||
resEl.innerHTML = highlighted || '<span style="color:var(--text-muted)">No matches</span>';
|
||||
// Match list
|
||||
const matches = [...input.matchAll(new RegExp(pattern, flags.includes('g') ? flags : flags + 'g'))];
|
||||
if (matches.length) {
|
||||
listEl.innerHTML = '<div class="panel-label">Matches (' + matches.length + ')</div>' +
|
||||
matches.map((m, i) => `<div class="result-row"><div class="label">Match ${i+1}</div><div class="value" onclick="copyText(this.textContent)">${m[0]}</div></div>`).join('');
|
||||
} else listEl.innerHTML = '';
|
||||
} catch (e) {
|
||||
resEl.innerHTML = `<span style="color:var(--red)">${e.message}</span>`;
|
||||
listEl.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Slug Generator
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function generateSlug() {
|
||||
const text = document.getElementById('slugInput').value;
|
||||
if (!text.trim()) { document.getElementById('slugOutput').textContent = '—'; return; }
|
||||
const separator = document.getElementById('slugSeparator').value;
|
||||
const lowercase = document.getElementById('slugLower').checked;
|
||||
const d = await apiPost('/api/text/slugify', { text, separator, lowercase });
|
||||
if (d.success) {
|
||||
document.getElementById('slugOutput').textContent = d.result;
|
||||
setStatus('slugStatus', 'success', 'Generated ✓');
|
||||
} else setStatus('slugStatus', 'error', d.error);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// SQL Formatter
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function formatSQL() {
|
||||
const input = document.getElementById('sqlInput').value.trim();
|
||||
if (!input) return setStatus('sqlStatus', 'error', 'Paste some SQL first.');
|
||||
const d = await apiPost('/api/sql/format', { sql: input });
|
||||
if (d.success) { document.getElementById('sqlOutput').value = d.result; setStatus('sqlStatus', 'success', 'Formatted ✓'); }
|
||||
else setStatus('sqlStatus', 'error', d.error);
|
||||
}
|
||||
async function minifySQL() {
|
||||
const input = document.getElementById('sqlInput').value.trim();
|
||||
if (!input) return setStatus('sqlStatus', 'error', 'Paste some SQL first.');
|
||||
const d = await apiPost('/api/sql/minify', { sql: input });
|
||||
if (d.success) { document.getElementById('sqlOutput').value = d.result; setStatus('sqlStatus', 'success', 'Minified ✓'); }
|
||||
else setStatus('sqlStatus', 'error', d.error);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Text Encoder / Decoder
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function encodeText(method) {
|
||||
const text = document.getElementById('textencInput').value;
|
||||
if (!text) return setStatus('textencStatus', 'error', 'Enter some text first.');
|
||||
const d = await apiPost('/api/text/encode', { text, method });
|
||||
if (d.success) {
|
||||
document.getElementById('textencOutput').value = d.result;
|
||||
setStatus('textencStatus', 'success', `Encoded with ${method} ✓`);
|
||||
} else setStatus('textencStatus', 'error', d.error);
|
||||
}
|
||||
|
||||
function swapTextEnc() {
|
||||
const input = document.getElementById('textencInput');
|
||||
const output = document.getElementById('textencOutput');
|
||||
const tmp = input.value;
|
||||
input.value = output.value;
|
||||
output.value = tmp;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Timestamp
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function convertTimestamp() {
|
||||
const v = document.getElementById('tsInput').value.trim();
|
||||
const d = await apiPost('/api/timestamp', { value: v || 'now' });
|
||||
if (d.success) {
|
||||
document.getElementById('tsResults').innerHTML = [
|
||||
['Unix (s)', d.unix], ['Unix (ms)', d.unixMs], ['ISO 8601', d.iso],
|
||||
['UTC', d.utc], ['Local', d.local], ['Relative', d.relative]
|
||||
].map(([l, v]) => `<div class="result-row"><div class="label">${l}</div><div class="value" onclick="copyText(this.textContent)">${v}</div></div>`).join('');
|
||||
setStatus('tsStatus','success','Converted ✓');
|
||||
} else setStatus('tsStatus','error', d.error);
|
||||
}
|
||||
|
||||
// Keyboard shortcut
|
||||
document.getElementById('tsInput').addEventListener('keydown', e => { if(e.key==='Enter') convertTimestamp(); });
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// URL Shortener
|
||||
// ═══════════════════════════════════════════════════════
|
||||
const urlHist = [];
|
||||
async function shortenURL() {
|
||||
const url = document.getElementById('urlInput').value.trim();
|
||||
if (!url) return setStatus('urlStatus','error','Enter a URL.');
|
||||
const d = await apiPost('/api/url/shorten', { url });
|
||||
if (d.success) {
|
||||
document.getElementById('shortUrlLink').href = d.shortUrl;
|
||||
document.getElementById('shortUrlLink').textContent = d.shortUrl;
|
||||
document.getElementById('urlResult').classList.add('visible');
|
||||
setStatus('urlStatus','success','Shortened ✓');
|
||||
urlHist.unshift({ short: d.shortUrl, original: url, time: new Date().toLocaleTimeString() });
|
||||
renderUrlHistory();
|
||||
} else setStatus('urlStatus','error', d.error);
|
||||
}
|
||||
function copyShortUrl() { copyText(document.getElementById('shortUrlLink').textContent); }
|
||||
function renderUrlHistory() {
|
||||
const c = document.getElementById('urlHistory');
|
||||
if (!urlHist.length) { c.textContent = 'No links shortened yet.'; return; }
|
||||
c.innerHTML = urlHist.slice(0,10).map(h => `
|
||||
<div class="result-row">
|
||||
<div style="overflow:hidden;flex:1;"><a href="${h.short}" target="_blank" style="color:var(--accent);font-family:var(--font-mono);font-size:0.8rem;text-decoration:none;">${h.short}</a>
|
||||
<div style="font-size:0.7rem;color:var(--text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${h.original}</div></div>
|
||||
<span style="font-size:0.68rem;color:var(--text-muted);margin-left:12px;">${h.time}</span>
|
||||
</div>`).join('');
|
||||
}
|
||||
|
||||
// Keyboard shortcut
|
||||
document.getElementById('urlInput').addEventListener('keydown', e => { if(e.key==='Enter') shortenURL(); });
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// UUID
|
||||
// ═══════════════════════════════════════════════════════
|
||||
let lastUUIDs = [];
|
||||
async function generateUUIDs() {
|
||||
const d = await apiPost('/api/uuid', { count: parseInt(document.getElementById('uuidCount').value) });
|
||||
if (d.success) {
|
||||
lastUUIDs = d.uuids;
|
||||
document.getElementById('uuidResults').innerHTML = d.uuids.map(u =>
|
||||
`<div class="result-row"><div class="value" onclick="copyText(this.textContent)" style="max-width:100%;text-align:left;">${u}</div></div>`).join('');
|
||||
}
|
||||
}
|
||||
function copyUUIDs() { if (lastUUIDs.length) copyText(lastUUIDs.join('\n')); }
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// YouTube
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function fetchYouTube() {
|
||||
const url = document.getElementById('ytInput').value.trim();
|
||||
if (!url) return setStatus('ytStatus','error','Enter a YouTube URL.');
|
||||
setStatus('ytStatus','info','Fetching...');
|
||||
const d = await apiPost('/api/youtube/info', { url });
|
||||
if (d.success) {
|
||||
document.getElementById('ytThumb').src = d.thumbnail;
|
||||
document.getElementById('ytThumb').onerror = function() { this.src = d.thumbnailHQ; };
|
||||
document.getElementById('ytTitle').textContent = d.title;
|
||||
document.getElementById('ytAuthor').textContent = d.author;
|
||||
document.getElementById('ytAuthor').href = d.authorUrl;
|
||||
document.getElementById('ytThumbMax').textContent = d.thumbnail;
|
||||
document.getElementById('ytThumbHQ').textContent = d.thumbnailHQ;
|
||||
document.getElementById('ytEmbed').value = `<iframe width="560" height="315" src="${d.embedUrl}" frameborder="0" allowfullscreen></iframe>`;
|
||||
document.getElementById('ytWatch').href = d.watchUrl;
|
||||
document.getElementById('ytResult').classList.add('visible');
|
||||
setStatus('ytStatus','success','Fetched ✓');
|
||||
} else { document.getElementById('ytResult').classList.remove('visible'); setStatus('ytStatus','error', d.error); }
|
||||
}
|
||||
|
||||
// Keyboard shortcut
|
||||
document.getElementById('ytInput').addEventListener('keydown', e => { if(e.key==='Enter') fetchYouTube(); });
|
||||
|
||||
Reference in New Issue
Block a user