Spaces:
Running
Running
File size: 9,834 Bytes
71993f0 f781626 71993f0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | /* UTM Builder UI. © 2026 CyberMax. All rights reserved. */
(function () {
"use strict";
var U = window.UtmCore;
var el = function (id) { return document.getElementById(id); };
var KEY = "cybermax-utm:v1:";
function load(k, d) { try { var v = localStorage.getItem(KEY + k); return v ? JSON.parse(v) : d; } catch (e) { return d; } }
function save(k, v) { try { localStorage.setItem(KEY + k, JSON.stringify(v)); } catch (e) { /* ignore */ } }
function esc(s) { return String(s).replace(/[&<>"]/g, function (c) { return { "&": "&", "<": "<", ">": ">", '"': """ }[c]; }); }
var toastT;
function toast(m) { var t = el("toast"); t.textContent = m; t.classList.add("show"); clearTimeout(toastT); toastT = setTimeout(function () { t.classList.remove("show"); }, 1600); }
function copy(text, msg) {
var done = function () { toast(msg || "Copied"); };
if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(text).then(done, function () { fallback(text); done(); });
else { fallback(text); done(); }
}
function fallback(text) { var ta = document.createElement("textarea"); ta.value = text; document.body.appendChild(ta); ta.select(); try { document.execCommand("copy"); } catch (e) { /* ignore */ } ta.remove(); }
function download(name, text, type) {
var a = document.createElement("a");
a.href = URL.createObjectURL(new Blob([text], { type: type }));
a.download = name; document.body.appendChild(a); a.click(); a.remove();
setTimeout(function () { URL.revokeObjectURL(a.href); }, 1000);
}
// ---------- tabs ----------
function showTab(name) {
document.querySelectorAll(".tab").forEach(function (t) { t.setAttribute("aria-selected", String(t.dataset.tab === name)); });
document.querySelectorAll(".panel").forEach(function (p) { p.hidden = p.id !== "panel-" + name; });
if (name === "history") renderHistory();
}
document.querySelectorAll(".tab").forEach(function (t) { t.onclick = function () { showTab(t.dataset.tab); }; });
// ---------- builder ----------
var fields = ["url"].concat(U.KEYS);
function params() { var p = {}; U.KEYS.forEach(function (k) { p[k] = el("f-" + k).value; }); return p; }
function opts() { return { lower: el("o-lower").checked, space: el("o-space").value }; }
var current = "";
var presets = el("presets");
Object.keys(U.PRESETS).forEach(function (name) {
var b = document.createElement("button");
b.type = "button"; b.className = "chip"; b.textContent = name;
b.onclick = function () {
var p = U.PRESETS[name];
for (var k in p) el("f-" + k).value = p[k];
presets.querySelectorAll(".chip").forEach(function (c) { c.classList.toggle("on", c === b); });
update();
};
presets.appendChild(b);
});
function update() {
var r = U.build(el("f-url").value, params(), opts());
var out = el("out");
if (r.error || !r.tagged) {
current = "";
out.textContent = el("f-url").value.trim() ? (r.error || "Add a source, medium and campaign.") : "Fill in the URL, source, medium and campaign.";
} else {
current = r.url;
out.textContent = r.url;
}
el("warn").innerHTML = (r.warnings || []).filter(function () { return !!current; }).map(function (w) { return "⚠ " + esc(w); }).join("<br>");
el("btn-copy").disabled = !current;
el("btn-qr").disabled = !current;
var open = el("btn-open");
if (current) { open.href = current; open.removeAttribute("aria-disabled"); } else { open.removeAttribute("href"); open.setAttribute("aria-disabled", "true"); }
drawQr(current);
var state = {}; fields.forEach(function (f) { state[f] = el("f-" + f).value; });
state.lower = el("o-lower").checked; state.space = el("o-space").value;
save("form", state);
}
function drawQr(text) {
var c = el("qr"), ctx = c.getContext("2d");
ctx.fillStyle = "#fff"; ctx.fillRect(0, 0, c.width, c.height);
if (!text) {
ctx.fillStyle = "#9aa4b6"; ctx.font = "14px system-ui, sans-serif"; ctx.textAlign = "center";
ctx.fillText("QR code appears here", c.width / 2, c.height / 2);
return;
}
try {
var qr = window.qrcode(0, "M");
qr.addData(text); qr.make();
var n = qr.getModuleCount(), quiet = 4, size = Math.floor(c.width / (n + quiet * 2));
var off = Math.floor((c.width - size * n) / 2);
ctx.fillStyle = "#000";
for (var r = 0; r < n; r++) for (var col = 0; col < n; col++) if (qr.isDark(r, col)) ctx.fillRect(off + col * size, off + r * size, size, size);
} catch (e) {
ctx.fillStyle = "#b25e00"; ctx.font = "13px system-ui, sans-serif"; ctx.textAlign = "center";
ctx.fillText("Link too long for a QR code", c.width / 2, c.height / 2);
}
}
function qrPng() {
// Re-render at 1024px for print quality.
var qr = window.qrcode(0, "M"); qr.addData(current); qr.make();
var n = qr.getModuleCount(), cell = Math.floor(1024 / (n + 8)), size = cell * (n + 8);
var c = document.createElement("canvas"); c.width = c.height = size;
var ctx = c.getContext("2d"); ctx.fillStyle = "#fff"; ctx.fillRect(0, 0, size, size); ctx.fillStyle = "#000";
for (var r = 0; r < n; r++) for (var col = 0; col < n; col++) if (qr.isDark(r, col)) ctx.fillRect((col + 4) * cell, (r + 4) * cell, cell, cell);
var a = document.createElement("a"); a.href = c.toDataURL("image/png");
var camp = (el("f-utm_campaign").value || "link").toLowerCase().replace(/[^a-z0-9_-]+/g, "-");
a.download = "qr-" + camp + ".png"; document.body.appendChild(a); a.click(); a.remove();
}
fields.forEach(function (f) { el("f-" + f).addEventListener("input", update); });
el("o-lower").addEventListener("change", update);
el("o-space").addEventListener("change", update);
el("btn-copy").onclick = function () { if (!current) return; copy(current, "Link copied"); addHistory(current); };
el("btn-qr").onclick = function () { if (current) qrPng(); };
el("btn-reset").onclick = function () { fields.forEach(function (f) { el("f-" + f).value = ""; }); presets.querySelectorAll(".chip").forEach(function (c) { c.classList.remove("on"); }); update(); el("f-url").focus(); };
// ---------- bulk ----------
var bulkRows = [];
el("btn-bulk").onclick = function () {
bulkRows = U.bulk(el("bulk-in").value, params(), opts());
var tb = el("bulk-table").querySelector("tbody");
tb.innerHTML = bulkRows.map(function (r, i) {
return "<tr><td class='num'>" + (i + 1) + "</td><td style='word-break:break-all'>" + esc(r.url || r.input) + "</td><td>" +
(r.error ? "<span style='color:var(--warn)'>" + esc(r.error) + "</span>" : esc(r.warnings.filter(function (w) { return !/^Added https/.test(w); }).slice(0, 2).join(" "))) + "</td></tr>";
}).join("");
el("bulk-table").hidden = !bulkRows.length;
var okN = bulkRows.filter(function (r) { return !r.error; }).length;
el("btn-bulk-copy").disabled = el("btn-bulk-csv").disabled = !okN;
toast(bulkRows.length ? okN + " of " + bulkRows.length + " links built" : "Paste some URLs first");
};
el("btn-bulk-copy").onclick = function () { copy(bulkRows.filter(function (r) { return !r.error; }).map(function (r) { return r.url; }).join("\n"), "All links copied"); };
el("btn-bulk-csv").onclick = function () { download("utm-links.csv", U.toCsv(bulkRows), "text/csv"); };
// ---------- decode ----------
var decoded = null;
el("dec-in").addEventListener("input", function () {
var v = el("dec-in").value.trim(), tb = el("dec-table").querySelector("tbody");
decoded = v ? U.parse(v) : null;
if (!decoded || decoded.error) { tb.innerHTML = v ? "<tr><td>" + esc(decoded.error) + "</td></tr>" : ""; el("dec-clean").textContent = ""; return; }
var keys = Object.keys(decoded.utm);
tb.innerHTML = keys.length ? keys.map(function (k) { return "<tr><th>" + esc(k) + "</th><td>" + esc(decoded.utm[k]) + "</td></tr>"; }).join("")
: "<tr><td>No UTM parameters in this link.</td></tr>";
el("dec-clean").textContent = decoded.clean;
});
el("btn-dec-edit").onclick = function () {
if (!decoded || decoded.error) return;
el("f-url").value = decoded.clean;
U.KEYS.forEach(function (k) { el("f-" + k).value = decoded.utm[k] || ""; });
showTab("single"); update();
};
// ---------- history ----------
function addHistory(url) {
var h = load("history", []).filter(function (x) { return x.url !== url; });
h.unshift({ url: url, at: new Date().toISOString().slice(0, 16).replace("T", " ") });
save("history", h.slice(0, (window.CMXPro && CMXPro.isPro()) ? 500 : 25)); // Pro (pro.js): 500
}
function renderHistory() {
var h = load("history", []), tb = el("hist-table").querySelector("tbody");
tb.innerHTML = h.length ? h.map(function (x, i) {
return "<tr><td style='white-space:nowrap;color:var(--muted)'>" + esc(x.at) + "</td><td style='word-break:break-all'>" + esc(x.url) + "</td><td><button class='chip' data-i='" + i + "'>Copy</button></td></tr>";
}).join("") : "<tr><td>Links you copy will appear here.</td></tr>";
tb.querySelectorAll("button").forEach(function (b) { b.onclick = function () { copy(h[+b.dataset.i].url, "Link copied"); }; });
}
el("btn-hist-clear").onclick = function () { save("history", []); renderHistory(); };
// ---------- restore ----------
var st = load("form", null);
if (st) {
fields.forEach(function (f) { if (st[f]) el("f-" + f).value = st[f]; });
el("o-lower").checked = st.lower !== false;
if (st.space) el("o-space").value = st.space;
}
update();
window.UtmApp = { update: update, current: function () { return current; } };
if ("serviceWorker" in navigator && location.protocol === "https:") navigator.serviceWorker.register("sw.js").catch(function () {});
})();
|