redesign
This commit is contained in:
1 parent
ad620c976a
commit
0973129c17
26 files changed
+2255
-2245
No files matched your search
+299
-742
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="8" fill="#131615"/><rect x="6" y="6" width="52" height="52" fill="none" stroke="#f69b74" stroke-width="2"/><text x="32" y="42" text-anchor="middle" fill="#f69b74" font-family="monospace" font-size="30" font-weight="700">a/l</text></svg>
|
||||
|
After Width: | Height: | Size: 330 B |
+210
-273
@@ -1,288 +1,225 @@
|
||||
(function () {
|
||||
/* Progressive enhancement for Adlist Converter. No framework and no network
|
||||
calls while composing a link: only /convert retrieves source data. */
|
||||
(() => {
|
||||
'use strict';
|
||||
const $ = (selector, parent = document) => parent.querySelector(selector);
|
||||
const $$ = (selector, parent = document) => Array.from(parent.querySelectorAll(selector));
|
||||
|
||||
const $ = (q, c = document) => c.querySelector(q);
|
||||
const $$ = (q, c = document) => Array.from(c.querySelectorAll(q));
|
||||
const toastEl = $('#toast');
|
||||
let toastTimer;
|
||||
function toast(message, isError = false) {
|
||||
if (!toastEl) return;
|
||||
toastEl.textContent = message;
|
||||
toastEl.classList.toggle('error', isError);
|
||||
toastEl.classList.add('show');
|
||||
clearTimeout(toastTimer);
|
||||
toastTimer = setTimeout(() => toastEl.classList.remove('show'), 2600);
|
||||
}
|
||||
function setTheme(theme) {
|
||||
const value = theme === 'light' ? 'light' : 'dark';
|
||||
document.documentElement.dataset.theme = value;
|
||||
const themeColor = $('meta[name="theme-color"]');
|
||||
if (themeColor) themeColor.content = value === 'light' ? '#f4f6f2' : '#131615';
|
||||
try { localStorage.setItem('adlist-theme', value); } catch (_) {}
|
||||
}
|
||||
setTheme(document.documentElement.dataset.theme || 'dark');
|
||||
|
||||
// --- theme ---
|
||||
const setTheme = (t) => {
|
||||
document.documentElement.setAttribute('data-theme', t);
|
||||
try { localStorage.setItem('theme', t) } catch { }
|
||||
};
|
||||
window.setTheme = setTheme;
|
||||
|
||||
const toast = (msg) => {
|
||||
const el = $('#toast'); if (!el) return;
|
||||
el.textContent = msg; el.classList.add('show');
|
||||
clearTimeout(el._t); el._t = setTimeout(() => el.classList.remove('show'), 2000);
|
||||
};
|
||||
const host = () => `${location.protocol}//${location.host}`;
|
||||
|
||||
// --- IP validators ---
|
||||
function isValidIPv4(ip) {
|
||||
if (!/^\d{1,3}(?:\.\d{1,3}){3}$/.test(ip)) return false;
|
||||
return ip.split('.').every(oct => {
|
||||
if (oct.length > 1 && oct[0] === '0') return oct === '0';
|
||||
const n = Number(oct);
|
||||
return n >= 0 && n <= 255;
|
||||
});
|
||||
function isValidIPv4(ip) {
|
||||
const octets = ip.split('.');
|
||||
return octets.length === 4 && octets.every(octet => {
|
||||
if (!/^(0|[1-9]\d{0,2})$/.test(octet)) return false;
|
||||
return Number(octet) <= 255;
|
||||
});
|
||||
}
|
||||
function isValidIPv6(ip) {
|
||||
if (!ip.includes(':') || ip.includes('%') || ip.split('::').length > 2) return false;
|
||||
let raw = ip;
|
||||
let ipv4Count = 0;
|
||||
if (raw.includes('.')) {
|
||||
const cut = raw.lastIndexOf(':');
|
||||
if (cut < 0 || !isValidIPv4(raw.slice(cut + 1))) return false;
|
||||
raw = raw.slice(0, cut) + ':0:0';
|
||||
ipv4Count = 1;
|
||||
}
|
||||
const parts = raw.split(':').filter(Boolean);
|
||||
if (!parts.every(part => /^[a-f\d]{1,4}$/i.test(part))) return false;
|
||||
if (raw.includes('::')) return parts.length < 8;
|
||||
return parts.length === 8 && !raw.startsWith(':') && !raw.endsWith(':');
|
||||
}
|
||||
function isValidIP(ip) { return isValidIPv4(ip) || isValidIPv6(ip); }
|
||||
|
||||
function isValidIPv6(ip) {
|
||||
let work = ip;
|
||||
const idx = work.lastIndexOf(':');
|
||||
if (idx !== -1) {
|
||||
const tail = work.slice(idx + 1);
|
||||
if (/^\d{1,3}(?:\.\d{1,3}){3}$/.test(tail)) {
|
||||
if (!isValidIPv4(tail)) return false;
|
||||
work = work.slice(0, idx) + ':0:0';
|
||||
}
|
||||
}
|
||||
if (work.split('::').length > 2) return false;
|
||||
const hasCompress = work.includes('::');
|
||||
const parts = work.split(':').filter(Boolean);
|
||||
if ((!hasCompress && parts.length !== 8) || (hasCompress && parts.length > 7)) return false;
|
||||
return parts.every(g => /^[0-9a-fA-F]{1,4}$/.test(g));
|
||||
function normalizeSource(input) {
|
||||
const value = (input || '').trim();
|
||||
if (!value || /[\r\n]/.test(value)) return null;
|
||||
try {
|
||||
const candidate = /^[a-z][a-z\d+.-]*:\/\//i.test(value) ? value : `https://${value}`;
|
||||
const parsed = new URL(candidate);
|
||||
if (!['https:', 'http:'].includes(parsed.protocol) || !parsed.hostname) return null;
|
||||
return parsed.toString();
|
||||
} catch (_) { return null; }
|
||||
}
|
||||
function conversionUrl(source, target) {
|
||||
const origin = /^https?:$/.test(window.location.protocol) ? window.location.origin : 'https://listapp.linuxiarz.pl';
|
||||
const url = new URL('/convert', origin);
|
||||
url.searchParams.set('url', source);
|
||||
url.searchParams.set('ip', target);
|
||||
return url.href;
|
||||
}
|
||||
function setError(input, message) {
|
||||
if (!input) return;
|
||||
input.setAttribute('aria-invalid', message ? 'true' : 'false');
|
||||
const error = $(`#${input.id === 'url-input' ? 'url-error' : 'ip-error'}`);
|
||||
if (error) { error.textContent = message; error.hidden = !message; }
|
||||
}
|
||||
const urlInput = $('#url-input');
|
||||
const ipInput = $('#ip-input');
|
||||
const result = $('#generated-link');
|
||||
const openLink = $('#open-link');
|
||||
const copyBtn = $('#copy-btn');
|
||||
const linkState = $('#link-state');
|
||||
const validationStatus = $('#validation-status');
|
||||
|
||||
function updateOutput(showErrors = false) {
|
||||
if (!urlInput || !ipInput || !result) return;
|
||||
const rawSource = urlInput.value.trim();
|
||||
const ip = ipInput.value.trim();
|
||||
const source = normalizeSource(rawSource);
|
||||
const ipValid = isValidIP(ip);
|
||||
if (showErrors || rawSource && !source) setError(urlInput, !source && rawSource ? 'Enter a valid HTTP or HTTPS address.' : '');
|
||||
if (showErrors || ip && !ipValid) setError(ipInput, !ipValid && ip ? 'Enter a valid IPv4 or IPv6 address.' : '');
|
||||
|
||||
$$('.preset').forEach(button => {
|
||||
const selected = ip === button.dataset.ip;
|
||||
button.classList.toggle('active', selected);
|
||||
button.setAttribute('aria-pressed', String(selected));
|
||||
});
|
||||
const ready = Boolean(source && ipValid);
|
||||
const href = ready ? conversionUrl(source, ip) : '';
|
||||
result.value = href;
|
||||
if (copyBtn) copyBtn.disabled = !ready;
|
||||
if (openLink) {
|
||||
openLink.href = href || '#';
|
||||
openLink.setAttribute('aria-disabled', String(!ready));
|
||||
openLink.tabIndex = ready ? 0 : -1;
|
||||
}
|
||||
|
||||
function isValidIP(ip) {
|
||||
const v = (ip || '').trim();
|
||||
return isValidIPv4(v) || isValidIPv6(v);
|
||||
if (linkState) {
|
||||
linkState.textContent = ready ? 'LINK READY' : 'AWAITING INPUT';
|
||||
linkState.classList.toggle('live', ready);
|
||||
}
|
||||
|
||||
// --- URL helpers ---
|
||||
function normalizeUrlMaybe(v) {
|
||||
const raw = (v || '').trim();
|
||||
if (!raw) return '';
|
||||
try {
|
||||
const test = raw.includes('://') ? raw : `https://${raw}`;
|
||||
const u = new URL(test);
|
||||
if (u.protocol !== 'http:' && u.protocol !== 'https:') throw new Error('scheme');
|
||||
return u.toString();
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
const status = validationStatus?.parentElement;
|
||||
if (status && validationStatus) {
|
||||
status.classList.toggle('ready', ready);
|
||||
status.classList.toggle('invalid', Boolean(rawSource && !source || ip && !ipValid));
|
||||
validationStatus.textContent = ready ? 'Endpoint ready to copy' : !rawSource ? 'Waiting for a source URL' : !source ? 'Invalid source URL' : !ipValid ? 'Invalid target address' : 'Waiting for valid input';
|
||||
}
|
||||
}
|
||||
[urlInput, ipInput].forEach(input => {
|
||||
if (!input) return;
|
||||
input.addEventListener('input', () => { setError(input, ''); updateOutput(); });
|
||||
input.addEventListener('blur', () => updateOutput(true));
|
||||
});
|
||||
$('#converter-form')?.addEventListener('submit', event => event.preventDefault());
|
||||
$$('.preset').forEach(button => button.addEventListener('click', () => {
|
||||
if (!ipInput) return;
|
||||
ipInput.value = button.dataset.ip || '';
|
||||
setError(ipInput, '');
|
||||
updateOutput();
|
||||
}));
|
||||
updateOutput();
|
||||
|
||||
function buildLink(url, ip) {
|
||||
if (!url || !ip) return '';
|
||||
try {
|
||||
const enc = encodeURIComponent(url);
|
||||
const ipClean = (ip || '').trim();
|
||||
return `${host()}/convert?url=${enc}&ip=${encodeURIComponent(ipClean)}`;
|
||||
} catch { return ''; }
|
||||
async function copy(text) {
|
||||
if (!text) { toast('Nothing to copy', true); return; }
|
||||
try {
|
||||
if (!navigator.clipboard?.writeText) throw new Error('Clipboard API unavailable');
|
||||
await navigator.clipboard.writeText(text);
|
||||
toast('Copied to clipboard');
|
||||
} catch (_) {
|
||||
const temporary = document.createElement('textarea');
|
||||
temporary.value = text;
|
||||
temporary.setAttribute('readonly', '');
|
||||
temporary.style.cssText = 'position:fixed;left:-9999px;top:0';
|
||||
document.body.append(temporary);
|
||||
temporary.select();
|
||||
try {
|
||||
if (!document.execCommand('copy')) throw new Error('Copy rejected');
|
||||
toast('Copied to clipboard');
|
||||
} catch (_) {
|
||||
toast('Copy failed — select the text manually', true);
|
||||
} finally { temporary.remove(); }
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
const t = e.target.closest('[data-action="toggle-theme"]');
|
||||
if (t) {
|
||||
e.preventDefault();
|
||||
const cur = document.documentElement.getAttribute('data-theme') || 'dark';
|
||||
setTheme(cur === 'dark' ? 'light' : 'dark');
|
||||
}
|
||||
});
|
||||
|
||||
const urlInput = $('#url-input');
|
||||
const ipInput = $('#ip-input');
|
||||
const ipPreset = $('#ip-preset');
|
||||
const out = $('#generated-link');
|
||||
const openBtn = $('#open-link');
|
||||
const copyBtn = $('#copy-btn');
|
||||
|
||||
function showError(input, msg) {
|
||||
const id = input.getAttribute('id');
|
||||
const box = document.querySelector(`.error[data-error-for="${id}"]`);
|
||||
if (box) box.textContent = msg || '';
|
||||
input.setAttribute('aria-invalid', msg ? 'true' : 'false');
|
||||
document.addEventListener('click', event => {
|
||||
const trigger = event.target.closest('[data-action]');
|
||||
if (!trigger) return;
|
||||
const action = trigger.dataset.action;
|
||||
if (action === 'toggle-theme') {
|
||||
setTheme(document.documentElement.dataset.theme === 'light' ? 'dark' : 'light');
|
||||
} else if (action === 'menu') {
|
||||
const open = document.body.classList.toggle('menu-open');
|
||||
trigger.setAttribute('aria-expanded', String(open));
|
||||
} else if (action === 'clear') {
|
||||
if (!urlInput || !ipInput) return;
|
||||
urlInput.value = '';
|
||||
ipInput.value = '195.187.6.34';
|
||||
setError(urlInput, ''); setError(ipInput, '');
|
||||
updateOutput();
|
||||
urlInput.focus();
|
||||
} else if (action === 'copy') {
|
||||
const node = $(trigger.dataset.target || '');
|
||||
void copy(node?.value ?? node?.textContent ?? '');
|
||||
} else if (action === 'copy-text') {
|
||||
const value = trigger.dataset.text || '';
|
||||
const absolute = value.startsWith('/') ? window.location.origin + value : value;
|
||||
void copy(absolute);
|
||||
} else if (action === 'use-source') {
|
||||
if (!urlInput || !ipInput) return;
|
||||
urlInput.value = trigger.dataset.url || '';
|
||||
ipInput.value = trigger.dataset.ip || '195.187.6.34';
|
||||
setError(urlInput, ''); setError(ipInput, '');
|
||||
updateOutput();
|
||||
$('#generator')?.scrollIntoView({behavior: 'smooth', block: 'start'});
|
||||
toast('Source loaded into generator');
|
||||
} else if (action === 'try-again') {
|
||||
window.location.reload();
|
||||
} else if (action === 'show-all') {
|
||||
const table = $(trigger.dataset.target || '');
|
||||
if (!table) return;
|
||||
$$('[data-extra-row]', table).forEach(row => { row.hidden = false; row.removeAttribute('data-extra-row'); });
|
||||
trigger.closest('.table-footer')?.remove();
|
||||
}
|
||||
|
||||
|
||||
function updatePreview() {
|
||||
const rawUrl = (urlInput?.value || '').trim();
|
||||
const ip = (ipInput?.value || '').trim();
|
||||
|
||||
if (!ip || !isValidIP(ip)) {
|
||||
if (out) out.value = '';
|
||||
if (openBtn) {
|
||||
openBtn.setAttribute('href', '#');
|
||||
openBtn.setAttribute('aria-disabled', 'true');
|
||||
openBtn.setAttribute('disabled', 'true');
|
||||
}
|
||||
if (copyBtn) copyBtn.setAttribute('disabled', 'true');
|
||||
$('.result-box')?.setAttribute('data-state', 'empty');
|
||||
return;
|
||||
}
|
||||
|
||||
const normalized = normalizeUrlMaybe(rawUrl);
|
||||
const guessed = rawUrl ? (rawUrl.includes('://') ? rawUrl : `https://${rawUrl}`) : '';
|
||||
const previewUrl = normalized || guessed;
|
||||
|
||||
if (!previewUrl) {
|
||||
if (out) out.value = '';
|
||||
if (openBtn) {
|
||||
openBtn.setAttribute('href', '#');
|
||||
openBtn.setAttribute('aria-disabled', 'true');
|
||||
openBtn.setAttribute('disabled', 'true');
|
||||
}
|
||||
if (copyBtn) copyBtn.setAttribute('disabled', 'true');
|
||||
$('.result-box')?.setAttribute('data-state', 'empty');
|
||||
return;
|
||||
}
|
||||
|
||||
const link = buildLink(previewUrl, ip);
|
||||
if (out) out.value = link;
|
||||
|
||||
const ok = !!normalized;
|
||||
if (openBtn) {
|
||||
if (ok) {
|
||||
openBtn.setAttribute('href', link);
|
||||
openBtn.setAttribute('aria-disabled', 'false');
|
||||
openBtn.removeAttribute('disabled');
|
||||
} else {
|
||||
openBtn.setAttribute('href', '#');
|
||||
openBtn.setAttribute('aria-disabled', 'true');
|
||||
openBtn.setAttribute('disabled', 'true');
|
||||
}
|
||||
}
|
||||
if (copyBtn) copyBtn.toggleAttribute('disabled', !ok);
|
||||
$('.result-box')?.setAttribute('data-state', ok ? 'ready' : 'empty');
|
||||
});
|
||||
document.addEventListener('keydown', event => {
|
||||
if (event.key === 'Escape' && document.body.classList.contains('menu-open')) {
|
||||
document.body.classList.remove('menu-open');
|
||||
$('[data-action="menu"]')?.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
});
|
||||
document.addEventListener('click', event => {
|
||||
if (!document.body.classList.contains('menu-open')) return;
|
||||
if (event.target.closest('.sidebar,.menu-toggle')) {
|
||||
if (event.target.closest('.sidebar a')) {
|
||||
document.body.classList.remove('menu-open');
|
||||
$('[data-action="menu"]')?.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
return;
|
||||
}
|
||||
document.body.classList.remove('menu-open');
|
||||
$('[data-action="menu"]')?.setAttribute('aria-expanded', 'false');
|
||||
});
|
||||
|
||||
// live update
|
||||
['input', 'change', 'blur'].forEach(evt => {
|
||||
urlInput?.addEventListener(evt, updatePreview);
|
||||
ipInput?.addEventListener(evt, updatePreview);
|
||||
$$('[data-filter]').forEach(filter => filter.addEventListener('input', () => {
|
||||
const table = $(filter.dataset.filter || '');
|
||||
if (!table) return;
|
||||
const query = filter.value.trim().toLowerCase();
|
||||
let visible = 0;
|
||||
$$('tbody tr:not(.empty-row)', table).forEach(row => {
|
||||
const match = row.textContent.toLowerCase().includes(query);
|
||||
const limited = !query && row.hasAttribute('data-extra-row');
|
||||
row.hidden = !match || limited;
|
||||
if (!row.hidden) visible++;
|
||||
});
|
||||
|
||||
// presets
|
||||
ipPreset?.addEventListener('change', () => {
|
||||
const v = ipPreset.value;
|
||||
if (!v) return;
|
||||
if (v !== 'custom') ipInput.value = v;
|
||||
ipInput.focus();
|
||||
const ok = isValidIP(ipInput.value.trim());
|
||||
showError(ipInput, ok ? '' : 'Invalid IP address');
|
||||
updatePreview();
|
||||
});
|
||||
|
||||
// event delegation
|
||||
document.addEventListener('click', (e) => {
|
||||
let t = e.target;
|
||||
|
||||
if (t.closest('[data-action="copy"]')) {
|
||||
e.preventDefault();
|
||||
const btn = t.closest('[data-action="copy"]');
|
||||
const sel = btn.getAttribute('data-target') || '#generated-link';
|
||||
const el = $(sel);
|
||||
if (!el) return;
|
||||
const text = el.value || el.textContent || '';
|
||||
navigator.clipboard?.writeText(text).then(() => {
|
||||
btn.classList.add('copied'); setTimeout(() => btn.classList.remove('copied'), 1200);
|
||||
toast('Link copied');
|
||||
}).catch(() => {
|
||||
const range = document.createRange(); range.selectNodeContents(el);
|
||||
const selObj = getSelection(); selObj.removeAllRanges(); selObj.addRange(range);
|
||||
try { document.execCommand('copy'); toast('Link copied'); } catch { }
|
||||
selObj.removeAllRanges();
|
||||
});
|
||||
}
|
||||
|
||||
if (t.closest('[data-action="copy-text"]')) {
|
||||
e.preventDefault();
|
||||
const btn = t.closest('[data-action="copy-text"]');
|
||||
let text = btn.getAttribute('data-text') || '';
|
||||
if (!text) return;
|
||||
if (text.startsWith('/')) text = host() + text;
|
||||
navigator.clipboard?.writeText(text).then(() => toast('Copied'));
|
||||
}
|
||||
|
||||
if (t.closest('[data-action="clear"]')) {
|
||||
e.preventDefault();
|
||||
urlInput.value = '';
|
||||
if (ipInput) ipInput.value = '';
|
||||
if (ipPreset) ipPreset.value = '';
|
||||
showError(urlInput, '');
|
||||
showError(ipInput, '');
|
||||
updatePreview();
|
||||
urlInput.focus();
|
||||
}
|
||||
|
||||
if (t.closest('[data-action="collapse"]')) {
|
||||
e.preventDefault();
|
||||
const btn = t.closest('[data-action="collapse"]');
|
||||
const panel = $('#' + (btn.getAttribute('aria-controls') || ''));
|
||||
if (!panel) return;
|
||||
const expanded = btn.getAttribute('aria-expanded') === 'true';
|
||||
const next = !expanded;
|
||||
btn.setAttribute('aria-expanded', next ? 'true' : 'false');
|
||||
panel.hidden = !next;
|
||||
panel.style.display = next ? '' : 'none';
|
||||
const newLabel = next ? 'Collapse' : 'Expand';
|
||||
btn.textContent = newLabel;
|
||||
btn.setAttribute('aria-label', newLabel);
|
||||
}
|
||||
});
|
||||
|
||||
// field-level validation
|
||||
urlInput?.addEventListener('blur', () => {
|
||||
const raw = urlInput.value.trim();
|
||||
if (!raw) return showError(urlInput, '');
|
||||
const normalized = normalizeUrlMaybe(raw);
|
||||
showError(urlInput, normalized ? '' : 'Invalid URL');
|
||||
});
|
||||
|
||||
ipInput?.addEventListener('blur', () => {
|
||||
const v = ipInput.value.trim();
|
||||
if (!v) return showError(ipInput, '');
|
||||
const ok = isValidIP(v);
|
||||
showError(ipInput, ok ? '' : 'Invalid IP address');
|
||||
if (!ok) $('.result-box')?.setAttribute('data-state', 'empty');
|
||||
});
|
||||
|
||||
// init (preview + recent-list sync)
|
||||
(function init() {
|
||||
const serverLink = out?.value?.trim();
|
||||
if (serverLink) {
|
||||
$('.result-box')?.setAttribute('data-state', 'ready');
|
||||
openBtn?.setAttribute('aria-disabled', 'false');
|
||||
} else {
|
||||
updatePreview();
|
||||
}
|
||||
})();
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const btn = document.querySelector('[data-action="collapse"]');
|
||||
const panelId = btn?.getAttribute('aria-controls') || '';
|
||||
const panel = panelId ? document.getElementById(panelId) : null;
|
||||
if (!btn || !panel) return;
|
||||
|
||||
const expanded = btn.getAttribute('aria-expanded') === 'true';
|
||||
panel.hidden = !expanded;
|
||||
panel.style.display = expanded ? '' : 'none';
|
||||
|
||||
const label = expanded ? 'Collapse' : 'Expand';
|
||||
btn.textContent = label;
|
||||
btn.setAttribute('aria-label', label);
|
||||
});
|
||||
|
||||
form?.addEventListener('submit', (e) => e.preventDefault());
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'c') {
|
||||
const text = out?.value?.trim(); if (!text) return;
|
||||
navigator.clipboard?.writeText(text).then(() => toast('Link copied'));
|
||||
}
|
||||
});
|
||||
|
||||
const empty = $(table.id === 'recent-table' ? '#recent-empty' : `[data-empty-for="#${table.id}"]`);
|
||||
if (empty) empty.hidden = !query || visible > 0;
|
||||
}));
|
||||
})();
|
||||
|
||||
// --- theme color sync (poza IIFE) ---
|
||||
function updateThemeColor() {
|
||||
const meta = document.querySelector('meta[name="theme-color"]');
|
||||
if (!meta) return;
|
||||
const isLight = document.documentElement.getAttribute('data-theme') === 'light';
|
||||
meta.setAttribute('content', isLight ? '#f6f8fb' : '#0f1115');
|
||||
}
|
||||
|
||||
const _setTheme = setTheme;
|
||||
setTheme = function (t) { _setTheme(t); updateThemeColor(); };
|
||||
document.addEventListener('DOMContentLoaded', updateThemeColor);
|
||||
Reference in new issue
Block a user