Archived
Refactor UI assets and harden quality checks
This commit is contained in:
1 parent
23ab466c52
commit
4ecc8eb874
62 files changed
+3314
-3206
No files matched your search
@@ -0,0 +1,93 @@
|
||||
document.querySelectorAll("[data-admin-table]").forEach((tableShell) => {
|
||||
const table = tableShell.querySelector("table");
|
||||
const tbody = tableShell.querySelector("tbody");
|
||||
const search = tableShell.querySelector("[data-table-search]");
|
||||
const pageSize = tableShell.querySelector("[data-table-page-size]");
|
||||
const count = tableShell.querySelector("[data-table-count]");
|
||||
const pagination = tableShell.querySelector("[data-table-pagination]");
|
||||
if (!table || !tbody || !search || !pageSize || !count || !pagination) return;
|
||||
|
||||
const headerCount = table.querySelectorAll("thead th").length || 1;
|
||||
const originalRows = Array.from(tbody.querySelectorAll("tr"));
|
||||
const dataRows = originalRows.filter((row) => !row.classList.contains("empty-row"));
|
||||
let currentPage = 1;
|
||||
|
||||
const emptyRow = (message) => {
|
||||
const row = document.createElement("tr");
|
||||
row.className = "empty-row";
|
||||
const cell = document.createElement("td");
|
||||
cell.className = "empty";
|
||||
cell.colSpan = headerCount;
|
||||
const state = document.createElement("div");
|
||||
state.className = "empty-state";
|
||||
const title = document.createElement("div");
|
||||
title.className = "empty-state-title";
|
||||
title.textContent = message;
|
||||
state.append(title);
|
||||
cell.append(state);
|
||||
row.append(cell);
|
||||
return row;
|
||||
};
|
||||
|
||||
const visibleRows = () => {
|
||||
const query = search.value.trim().toLowerCase();
|
||||
if (!query) return dataRows;
|
||||
return dataRows.filter((row) => row.textContent.toLowerCase().includes(query));
|
||||
};
|
||||
|
||||
const renderButton = (label, page, disabled = false, active = false) => {
|
||||
const item = document.createElement("li");
|
||||
item.className = `page-item${disabled ? " disabled" : ""}${active ? " active" : ""}`;
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.className = "page-link";
|
||||
button.textContent = label;
|
||||
button.disabled = disabled;
|
||||
button.addEventListener("click", () => {
|
||||
currentPage = page;
|
||||
render();
|
||||
});
|
||||
item.append(button);
|
||||
return item;
|
||||
};
|
||||
|
||||
const renderPagination = (totalPages) => {
|
||||
pagination.replaceChildren();
|
||||
if (totalPages <= 1) return;
|
||||
pagination.append(renderButton("Previous", Math.max(1, currentPage - 1), currentPage === 1));
|
||||
for (let page = 1; page <= totalPages; page += 1) {
|
||||
if (totalPages > 7 && page !== 1 && page !== totalPages && Math.abs(page - currentPage) > 1) {
|
||||
if (page === 2 || page === totalPages - 1) pagination.append(renderButton("...", currentPage, true));
|
||||
continue;
|
||||
}
|
||||
pagination.append(renderButton(String(page), page, false, page === currentPage));
|
||||
}
|
||||
pagination.append(renderButton("Next", Math.min(totalPages, currentPage + 1), currentPage === totalPages));
|
||||
};
|
||||
|
||||
const render = () => {
|
||||
const rows = visibleRows();
|
||||
const size = Number.parseInt(pageSize.value, 10) || 25;
|
||||
const totalPages = Math.max(1, Math.ceil(rows.length / size));
|
||||
currentPage = Math.min(currentPage, totalPages);
|
||||
const start = (currentPage - 1) * size;
|
||||
const pageRows = rows.slice(start, start + size);
|
||||
tbody.replaceChildren(
|
||||
...(pageRows.length ? pageRows : [emptyRow(dataRows.length ? "No matching rows." : "No rows yet.")])
|
||||
);
|
||||
const shownStart = rows.length ? start + 1 : 0;
|
||||
const shownEnd = rows.length ? Math.min(start + size, rows.length) : 0;
|
||||
count.textContent = `${shownStart}-${shownEnd} of ${rows.length}`;
|
||||
renderPagination(totalPages);
|
||||
};
|
||||
|
||||
search.addEventListener("input", () => {
|
||||
currentPage = 1;
|
||||
render();
|
||||
});
|
||||
pageSize.addEventListener("change", () => {
|
||||
currentPage = 1;
|
||||
render();
|
||||
});
|
||||
render();
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
.admin-console{padding:32px 0 18px;background:var(--rp-canvas)} .admin-page-title{display:flex;align-items:end;justify-content:space-between;gap:20px} .admin-page-title h1{font-size:30px} .admin-total-strip,.admin-metric-strip{display:flex;flex-wrap:wrap;gap:8px} .admin-total-strip span,.admin-metric-strip span{display:inline-flex;align-items:center;gap:7px;min-height:34px;padding:6px 12px;border:1px solid var(--rp-line);border-radius:999px;background:var(--rp-surface);color:var(--rp-muted);font-size:13px;white-space:nowrap} .admin-total-strip strong,.admin-metric-strip strong{color:var(--rp-ink);font-size:14px;font-weight:600} .admin-block{padding:18px 0} .admin-block + .admin-block{border-top:1px solid var(--rp-soft-line)} .admin-section-heading{display:flex;align-items:end;justify-content:space-between;gap:20px;margin-bottom:10px} .admin-notice{margin:10px 0 0;border-radius:14px} .admin-cleanup-bar{display:grid;grid-template-columns:minmax(150px,0.8fr) minmax(0,2.3fr) auto;align-items:center;gap:16px;padding:14px 0;border-bottom:1px solid var(--rp-soft-line)} .admin-cleanup-title{display:grid;gap:2px;min-width:0} .admin-cleanup-title h2{font-size:22px} .admin-cleanup-title span{color:var(--rp-muted);font-size:13px} .admin-cleanup-times{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;margin:0} .admin-cleanup-times div{min-width:0} .admin-cleanup-times dt{margin:0;color:var(--rp-muted);font-size:12px;font-weight:600} .admin-cleanup-times dd{margin:0;overflow:hidden;color:var(--rp-ink);font-size:13px;text-overflow:ellipsis;white-space:nowrap} .admin-cleanup-bar form{justify-self:end} .admin-metric-strip{margin-top:12px} .metric-card{display:grid;gap:6px;padding:16px;border:1px solid var(--rp-line);border-radius:16px;background:var(--rp-surface)} .metric-card span{color:var(--rp-muted);font-size:13px;font-weight:500} .metric-card strong{color:var(--rp-ink);font-size:20px;font-weight:600} .admin-table-scroll{max-height:480px;overflow:auto;background:var(--rp-surface);border-color:var(--rp-line) !important;border-radius:16px !important;-webkit-overflow-scrolling:touch} .admin-table-scroll thead th{position:sticky;top:0;z-index:1;background:var(--rp-surface-alt)} .table-page-size{width:auto;min-width:76px} .admin-table-shell .form-control{max-width:360px} .status-table-shell{max-width:100%;border:1px solid var(--rp-line);border-radius:16px;background:var(--rp-surface)} .pagination{--bs-pagination-color:var(--rp-muted);--bs-pagination-bg:var(--rp-surface);--bs-pagination-border-color:var(--rp-line);--bs-pagination-hover-color:var(--rp-ink);--bs-pagination-hover-bg:var(--rp-field);--bs-pagination-hover-border-color:var(--rp-line);--bs-pagination-active-color:#ffffff;--bs-pagination-active-bg:#000000;--bs-pagination-active-border-color:#000000;--bs-pagination-disabled-color:var(--rp-mute);--bs-pagination-disabled-bg:var(--rp-surface);--bs-pagination-disabled-border-color:var(--rp-line)} table{width:100%;min-width:900px;border-collapse:collapse;font-size:13px;font-variant-numeric:tabular-nums;color:var(--rp-ink)} .table{--bs-table-bg:transparent;--bs-table-color:var(--rp-ink);--bs-table-border-color:var(--rp-soft-line);--bs-table-hover-bg:var(--rp-field);margin-bottom:0} th,td{border-bottom:1px solid var(--rp-soft-line);padding:10px 9px;text-align:left;vertical-align:middle} td{overflow-wrap:anywhere} .status-table{min-width:min(100%,420px)} .status-table th,.status-table td{white-space:normal;overflow-wrap:anywhere} .dashboard-page{background:var(--rp-canvas)} .dashboard-shell{padding:32px 0 18px} .dashboard-header{display:flex;align-items:flex-start;justify-content:space-between;gap:20px;margin-bottom:18px} .dashboard-header h1{margin-top:4px;font-size:32px} .dashboard-page .lead{margin-top:8px;font-size:15px} .dashboard-page .section-copy{margin-top:6px} .dashboard-page .alert{margin:0 0 18px;border-radius:12px} .dashboard-grid{display:grid;grid-template-columns:minmax(0,1.7fr) minmax(280px,0.75fr);gap:16px;align-items:start} .dashboard-card{min-width:0;padding:0;border:1px solid var(--ui-border);border-radius:var(--ui-radius-lg);background:var(--ui-card);box-shadow:var(--ui-shadow-sm);overflow:clip} .dashboard-card-main{display:block} .dashboard-card-compact{margin-top:16px} .dashboard-card-header{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;padding:18px 18px 0} .dashboard-card-header h2,.dashboard-section h2{margin-top:2px;font-size:22px} .dashboard-card [data-slot="card-content"]{display:grid;gap:12px;padding:14px 18px 18px} .dashboard-status-card [data-slot="card-header"]{padding:18px 18px 0} .dashboard-status-card [data-slot="card-content"]{padding-top:14px} .dashboard-copy-stack{display:grid;gap:8px} .dashboard-actions{display:flex;flex-wrap:wrap;gap:8px;padding:0 18px 18px} .dashboard-page .copy-field{gap:8px} .dashboard-page .copy-pill{min-height:48px} .dashboard-page .copy-button{min-width:96px;min-height:48px} .dashboard-page .copy-field code,.dashboarLine truncated
|
||||
@@ -0,0 +1 @@
|
||||
:root{color-scheme:light;--bs-primary:#000000;--bs-primary-rgb:0,0,0;--bs-link-color:#000000;--bs-link-hover-color:#3f3f46;--bs-border-radius:12px;--bs-border-radius-sm:10px;--bs-border-radius-lg:12px;--rp-bg:#ffffff;--rp-canvas:#ffffff;--rp-surface:#ffffff;--rp-surface-alt:#fafafa;--rp-field:#fafafa;--rp-ink:#000000;--rp-muted:#737373;--rp-charcoal:#52525b;--rp-mute:#a1a1aa;--rp-line:#e5e5e5;--rp-soft-line:#eeeeee;--rp-danger:#b42318;--rp-focus-ring:rgba(0,0,0,0.18);--rp-terminal:#ffffff;--rp-terminal-ink:#000000;--rp-success:#1d6f42;--ui-background:var(--rp-bg);--ui-foreground:var(--rp-ink);--ui-card:var(--rp-surface);--ui-card-foreground:var(--rp-ink);--ui-muted:var(--rp-field);--ui-muted-foreground:var(--rp-muted);--ui-border:var(--rp-line);--ui-ring:var(--rp-focus-ring);--ui-destructive:var(--rp-danger);--ui-success:var(--rp-success);--ui-radius-sm:8px;--ui-radius-md:10px;--ui-radius-lg:14px;--ui-shadow-sm:0 1px 1px rgba(24,24,27,0.03),0 0 0 1px rgba(24,24,27,0.02)} [data-bs-theme="dark"]{color-scheme:dark;--bs-body-bg:#0f0f0f;--bs-body-color:#f5f5f5;--bs-secondary-color:#a1a1aa;--bs-border-color:rgba(255,255,255,0.16);--bs-tertiary-bg:#171717;--rp-bg:#0f0f0f;--rp-canvas:#0f0f0f;--rp-surface:#171717;--rp-surface-alt:#1f1f1f;--rp-field:#1f1f1f;--rp-ink:#f5f5f5;--rp-muted:#a3a3a3;--rp-charcoal:#d4d4d4;--rp-mute:#737373;--rp-line:rgba(255,255,255,0.16);--rp-soft-line:rgba(255,255,255,0.1);--rp-danger:#ff8a80;--rp-focus-ring:rgba(255,255,255,0.24);--rp-terminal:#171717;--rp-terminal-ink:#f5f5f5;--rp-success:#62c383;--ui-background:var(--rp-bg);--ui-foreground:var(--rp-ink);--ui-card:var(--rp-surface);--ui-card-foreground:var(--rp-ink);--ui-muted:var(--rp-field);--ui-muted-foreground:var(--rp-muted);--ui-border:var(--rp-line);--ui-ring:var(--rp-focus-ring);--ui-destructive:var(--rp-danger);--ui-success:var(--rp-success);--ui-shadow-sm:0 1px 1px rgba(0,0,0,0.18),0 0 0 1px rgba(255,255,255,0.02)} *{box-sizing:border-box} html{scroll-behavior:smooth;overflow-x:clip} body{min-height:100vh;overflow-x:clip;background:var(--rp-bg);color:var(--rp-ink);font:400 16px/1.5 ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif} code{color:var(--rp-ink)} .justify-items-center{justify-items:center} .container{max-width:1040px} .container.narrow{max-width:760px} .skip-link{position:fixed;left:16px;top:10px;z-index:40;transform:translateY(-140%);padding:8px 20px;border-radius:999px;background:var(--rp-ink);color:var(--rp-bg);text-decoration:none;font-size:14px;transition:transform 0.16s ease} .skip-link:focus{transform:translateY(0)} .site-nav{min-height:63px;padding:14px 0;border-bottom:0;background:var(--rp-canvas)} .site-nav .container{display:flex;align-items:center;gap:12px;min-width:0} .brand{display:inline-flex;align-items:center;flex:0 0 auto;gap:8px;color:var(--rp-ink);font-size:14px;font-weight:600;letter-spacing:0;white-space:nowrap} .brand:hover{color:var(--rp-ink)} .brand img{width:26px;height:26px;display:block} .nav-label{max-width:min(38vw,320px)} .site-nav-actions{min-width:0;flex-wrap:wrap} .navbar .nav-link,.navbar-text{color:var(--rp-muted);font-size:13px;font-weight:500} .navbar .nav-link:hover{color:var(--rp-ink)} .theme-toggle{align-items:center;appearance:none;background:transparent;border:0;border-radius:999px;color:var(--rp-muted);cursor:pointer;display:inline-flex;flex:0 0 auto;height:30px;justify-content:center;padding:0;width:30px} .theme-toggle:hover{background:var(--rp-surface-alt);color:var(--rp-ink)} .theme-toggle:focus-visible{outline:2px solid var(--rp-focus-ring);outline-offset:2px} .theme-toggle svg{height:17px;width:17px} .theme-toggle:active{transform:scale(0.96)} .theme-toggle::-moz-focus-inner{border:0;padding:0} .theme-icon{display:block} .theme-icon-sun{display:none} [data-bs-theme="dark"] .theme-icon-moon{display:none} [data-bs-theme="dark"] .theme-icon-sun{display:block} .hero-band{min-height:auto;display:grid;align-items:center;border-bottom:0;background:var(--rp-canvas);text-align:center;padding:96px 0 88px} .hero-band .container{max-width:720px;min-width:0} .hero-band .container > *{max-width:100%;min-width:0} .hero-logo{width:80px;height:80px;margin-inline:auto} .hero-band.compact{min-height:auto;padding:56px 0 48px} .section{padding:88px 0;border-bottom:0;background:var(--rp-canvas)} .section-compact{padding:56px 0} .section + .section{border-top:0} .success-section{background:var(--rp-surface-alt)} .danger-section{background:var(--rp-surface);border-top:1px solid var(--rp-soft-line)} .page-heading,.admin-heading .container{max-width:760px} .admin-heading{padding:56px 0 48px} h1,h2,h3,p{margin:0} h1,h2{font-family:"SF Pro Rounded",ui-rounded,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif} h1{max-width:720px;font-size:36px;line-height:1.11;font-weight:500;letter-spacing:0;overflow-wrap:anywhere} h2{font-size:28px;line-height:1.22;font-weight:500;letter-spacing:0} h3{font-size:19px;line-height:1.35;font-weight:500} .eyebrow{color:varLine truncated
|
||||
@@ -0,0 +1,87 @@
|
||||
const legacyCopy = (value) => {
|
||||
const textarea = document.createElement("textarea");
|
||||
textarea.value = value;
|
||||
textarea.setAttribute("readonly", "");
|
||||
textarea.style.position = "fixed";
|
||||
textarea.style.top = "-9999px";
|
||||
textarea.style.left = "-9999px";
|
||||
document.body.append(textarea);
|
||||
textarea.select();
|
||||
textarea.setSelectionRange(0, textarea.value.length);
|
||||
try {
|
||||
return document.execCommand("copy");
|
||||
} finally {
|
||||
textarea.remove();
|
||||
}
|
||||
};
|
||||
|
||||
const copyText = async (value) => {
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
await navigator.clipboard.writeText(value);
|
||||
return true;
|
||||
}
|
||||
return legacyCopy(value);
|
||||
};
|
||||
|
||||
const showCopyStatus = (button, text, className, duration = 1200) => {
|
||||
const label = button.querySelector("[data-copy-label]");
|
||||
const oldLabel = label ? label.textContent : "";
|
||||
button.classList.add(className);
|
||||
if (label) {
|
||||
label.setAttribute("aria-live", "polite");
|
||||
label.textContent = text;
|
||||
}
|
||||
setTimeout(() => {
|
||||
button.classList.remove(className);
|
||||
if (label) label.textContent = oldLabel;
|
||||
}, duration);
|
||||
};
|
||||
|
||||
document.addEventListener("click", async (event) => {
|
||||
document.querySelectorAll("details.copy-menu[open]").forEach((menu) => {
|
||||
if (!menu.contains(event.target)) menu.removeAttribute("open");
|
||||
});
|
||||
|
||||
const toggle = event.target.closest("[data-secret-toggle]");
|
||||
if (toggle) {
|
||||
const group = toggle.closest(".copy-field");
|
||||
const field = group ? group.querySelector("[data-secret-input], [data-secret-value]") : null;
|
||||
if (!field) return;
|
||||
if (field.matches("[data-secret-input]")) {
|
||||
const masked = field.type === "password";
|
||||
field.type = masked ? "text" : "password";
|
||||
toggle.textContent = masked ? "Hide" : "View";
|
||||
toggle.setAttribute("aria-label", masked ? "Hide secret value" : "Show secret value");
|
||||
} else {
|
||||
const masked = field.dataset.secretMasked !== "false";
|
||||
field.dataset.secretMasked = masked ? "false" : "true";
|
||||
field.textContent = masked ? (field.dataset.secretValue || "") : "********";
|
||||
toggle.textContent = masked ? "Hide" : "View";
|
||||
toggle.setAttribute("aria-label", masked ? "Hide secret value" : "Show secret value");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const button = event.target.closest("[data-copy]");
|
||||
if (!button) return;
|
||||
const menu = button.closest("details.copy-menu");
|
||||
try {
|
||||
const copied = await copyText(button.dataset.copy || "");
|
||||
if (!copied) throw new Error("copy failed");
|
||||
showCopyStatus(button, "Copied", "is-copied");
|
||||
if (menu) {
|
||||
setTimeout(() => {
|
||||
menu.removeAttribute("open");
|
||||
}, 250);
|
||||
}
|
||||
} catch (_) {
|
||||
showCopyStatus(button, "Copy failed", "is-copy-error", 1800);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("keydown", (event) => {
|
||||
if (event.key !== "Escape") return;
|
||||
document.querySelectorAll("details.copy-menu[open]").forEach((menu) => {
|
||||
menu.removeAttribute("open");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
(() => {
|
||||
const media = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
const storageKey = "karldns-theme";
|
||||
const systemTheme = () => (media.matches ? "dark" : "light");
|
||||
const storedTheme = () => {
|
||||
try {
|
||||
return window.localStorage.getItem(storageKey);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
const setStoredTheme = (theme) => {
|
||||
try {
|
||||
window.localStorage.setItem(storageKey, theme);
|
||||
} catch (_) {}
|
||||
};
|
||||
const syncToggle = (theme) => {
|
||||
document.querySelectorAll("[data-theme-toggle]").forEach((button) => {
|
||||
const nextTheme = theme === "dark" ? "light" : "dark";
|
||||
button.dataset.themeNext = nextTheme;
|
||||
button.setAttribute("aria-label", `Switch to ${nextTheme.toLowerCase()} theme`);
|
||||
});
|
||||
};
|
||||
const applyTheme = (requestedTheme = storedTheme() || systemTheme()) => {
|
||||
const theme = requestedTheme === "dark" ? "dark" : "light";
|
||||
document.documentElement.setAttribute("data-bs-theme", theme);
|
||||
syncToggle(theme);
|
||||
return theme;
|
||||
};
|
||||
applyTheme();
|
||||
media.addEventListener("change", () => {
|
||||
if (!storedTheme()) applyTheme();
|
||||
});
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
syncToggle(document.documentElement.getAttribute("data-bs-theme") || "light");
|
||||
});
|
||||
document.addEventListener("click", (event) => {
|
||||
const toggle = event.target.closest("[data-theme-toggle]");
|
||||
if (!toggle) return;
|
||||
const currentTheme = document.documentElement.getAttribute("data-bs-theme") || systemTheme();
|
||||
const nextTheme = currentTheme === "dark" ? "light" : "dark";
|
||||
setStoredTheme(nextTheme);
|
||||
applyTheme(nextTheme);
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,99 @@
|
||||
(() => {
|
||||
const ipv4Placeholder = "203.0.113.10";
|
||||
const ipv6Placeholder = "2001:db8::10";
|
||||
const needsIpv4 = () => (
|
||||
document.body.textContent.includes(ipv4Placeholder)
|
||||
|| document.querySelector(`[data-copy*="${ipv4Placeholder}"]`)
|
||||
);
|
||||
const needsIpv6 = () => (
|
||||
document.body.textContent.includes(ipv6Placeholder)
|
||||
|| document.querySelector(`[data-copy*="${ipv6Placeholder}"]`)
|
||||
);
|
||||
|
||||
const fetchIp = async (url) => {
|
||||
try {
|
||||
const response = await fetch(url, { cache: "no-store", mode: "cors" });
|
||||
if (!response.ok) return "";
|
||||
const payload = await response.json();
|
||||
return typeof payload.ip === "string" ? payload.ip : "";
|
||||
} catch (_) {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
const replaceInTextNodes = (search, replacement) => {
|
||||
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
|
||||
const nodes = [];
|
||||
while (walker.nextNode()) {
|
||||
if (walker.currentNode.nodeValue.includes(search)) nodes.push(walker.currentNode);
|
||||
}
|
||||
nodes.forEach((node) => {
|
||||
node.nodeValue = node.nodeValue.split(search).join(replacement);
|
||||
});
|
||||
};
|
||||
|
||||
const replaceInCopyValues = (search, replacement) => {
|
||||
document.querySelectorAll("[data-copy]").forEach((element) => {
|
||||
const value = element.dataset.copy || "";
|
||||
if (value.includes(search)) element.dataset.copy = value.split(search).join(replacement);
|
||||
});
|
||||
};
|
||||
|
||||
const replaceEverywhere = (search, replacement) => {
|
||||
if (!replacement || replacement === search) return;
|
||||
replaceInTextNodes(search, replacement);
|
||||
replaceInCopyValues(search, replacement);
|
||||
};
|
||||
|
||||
const updateCopyCommand = (button, ipv6Command) => {
|
||||
const value = button.dataset.copy || "";
|
||||
if (!value || value.includes("?myipv6=")) return;
|
||||
const lines = value.split("\n");
|
||||
const output = [];
|
||||
let inserted = false;
|
||||
lines.forEach((line) => {
|
||||
output.push(line);
|
||||
if (!inserted && line.includes("/api/v1/router/update/") && line.includes("?myip=")) {
|
||||
output.push(ipv6Command);
|
||||
inserted = true;
|
||||
}
|
||||
});
|
||||
if (inserted) button.dataset.copy = output.join("\n");
|
||||
};
|
||||
|
||||
const insertIpv6Examples = (ip) => {
|
||||
if (!ip) return;
|
||||
document.querySelectorAll(".terminal-card.manual-code").forEach((card) => {
|
||||
if (card.textContent.includes("?myipv6=")) return;
|
||||
const lines = [...card.querySelectorAll("code")];
|
||||
const ipv4Line = lines.find((line) => (
|
||||
line.textContent.includes("/api/v1/router/update/")
|
||||
&& line.textContent.includes("?myip=")
|
||||
));
|
||||
if (!ipv4Line) return;
|
||||
const base = ipv4Line.textContent.split("?myip=", 1)[0].replace(/'$/, "");
|
||||
const ipv6Command = `${base}?myipv6=${ip}'`;
|
||||
const code = document.createElement("code");
|
||||
code.textContent = ipv6Command;
|
||||
ipv4Line.after(code);
|
||||
const copyButton = card.querySelector("[data-copy]");
|
||||
if (copyButton) updateCopyCommand(copyButton, ipv6Command);
|
||||
});
|
||||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
const tasks = [];
|
||||
if (needsIpv4()) {
|
||||
tasks.push(fetchIp("https://api4.ipify.org?format=json").then((ip) => {
|
||||
replaceEverywhere(ipv4Placeholder, ip);
|
||||
}));
|
||||
}
|
||||
if (needsIpv6()) {
|
||||
tasks.push(fetchIp("https://api6.ipify.org?format=json").then((ip) => {
|
||||
replaceEverywhere(ipv6Placeholder, ip);
|
||||
insertIpv6Examples(ip);
|
||||
}));
|
||||
}
|
||||
await Promise.allSettled(tasks);
|
||||
});
|
||||
})();
|
||||
Reference in new issue
Block a user