139 lines
5.3 KiB
JavaScript
139 lines
5.3 KiB
JavaScript
/* EwoooC base interactions extracted from templates/ewoooc_base.html. */
|
|
(function () {
|
|
if (window.__momoLinkGuardInstalled) return;
|
|
window.__momoLinkGuardInstalled = true;
|
|
|
|
const MOMO_CODE_RE = /^[A-Za-z0-9_-]{4,}$/;
|
|
|
|
function toText(value) {
|
|
return value == null ? '' : String(value);
|
|
}
|
|
|
|
function isLikelyMomoCode(value) {
|
|
const cleaned = toText(value).trim();
|
|
if (!cleaned) return false;
|
|
const lowered = cleaned.toLowerCase();
|
|
if (['nan', 'none', 'null', 'undefined'].includes(lowered)) return false;
|
|
if (lowered.startsWith('momo_') || lowered.startsWith('manual_') || lowered.startsWith('pchome_')) return false;
|
|
return MOMO_CODE_RE.test(cleaned);
|
|
}
|
|
|
|
function extractIcode(url) {
|
|
const target = toText(url).trim();
|
|
if (!target) return '';
|
|
try {
|
|
const parsed = new URL(target, location.origin);
|
|
return toText(parsed.searchParams.get('i_code')).trim();
|
|
} catch (error) {
|
|
const match = /[?&]i_code=([^&#]+)/i.exec(target);
|
|
return match ? decodeURIComponent(match[1] || '').trim() : '';
|
|
}
|
|
}
|
|
|
|
function buildSafeMomoUrl(iCode) {
|
|
const cleaned = toText(iCode).trim();
|
|
if (!isLikelyMomoCode(cleaned)) return '';
|
|
return `https://www.momoshop.com.tw/goods/GoodsDetail.jsp?i_code=${encodeURIComponent(cleaned)}`;
|
|
}
|
|
|
|
document.addEventListener('click', function (event) {
|
|
const link = event.target.closest ? event.target.closest('a.momo-tracked-link') : null;
|
|
if (!link) return;
|
|
|
|
const href = toText(link.getAttribute('href')).trim();
|
|
const original = toText(link.dataset && link.dataset.momoOriginalUrl).trim();
|
|
const iCode = toText(link.dataset && (link.dataset.trackIcode || link.dataset.trackProductId)).trim()
|
|
|| extractIcode(original)
|
|
|| extractIcode(href);
|
|
const safeUrl = buildSafeMomoUrl(iCode);
|
|
|
|
if (safeUrl && (!href || href === '#' || /ec404/i.test(href) || /ec404/i.test(original))) {
|
|
event.preventDefault();
|
|
link.setAttribute('href', safeUrl);
|
|
window.open(safeUrl, link.getAttribute('target') || '_self', 'noopener,noreferrer');
|
|
}
|
|
}, true);
|
|
})();
|
|
|
|
(function () {
|
|
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
|
window.fetchWithCSRF = (url, options = {}) => {
|
|
options.headers = { ...options.headers, 'X-CSRFToken': csrfToken };
|
|
return fetch(url, options);
|
|
};
|
|
})();
|
|
|
|
(function () {
|
|
const shell = document.getElementById('momo-shell');
|
|
const toggle = document.querySelector('[data-momo-sidebar-toggle]');
|
|
const close = document.querySelector('[data-momo-sidebar-close]');
|
|
if (!shell || !toggle) return;
|
|
toggle.addEventListener('click', () => shell.classList.toggle('is-sidebar-open'));
|
|
if (close) close.addEventListener('click', () => shell.classList.remove('is-sidebar-open'));
|
|
})();
|
|
|
|
(function() {
|
|
const link = document.getElementById('momo-obs-link');
|
|
const badge = document.getElementById('momo-obs-badge');
|
|
if (!link || !badge) return;
|
|
|
|
const cacheKey = 'momoObsHealthIndicator:v1';
|
|
const cacheTtlMs = 60000;
|
|
|
|
function applyIndicator(d) {
|
|
if (!d || !d.ok) return;
|
|
link.title = d.tooltip || 'AI 觀測台';
|
|
if (d.alert_count > 0) {
|
|
badge.textContent = d.alert_count;
|
|
badge.hidden = false;
|
|
link.classList.add('is-alert');
|
|
} else {
|
|
badge.hidden = true;
|
|
link.classList.remove('is-alert');
|
|
}
|
|
}
|
|
|
|
function readCachedIndicator() {
|
|
try {
|
|
const cached = JSON.parse(sessionStorage.getItem(cacheKey) || 'null');
|
|
if (!cached || !cached.data || Date.now() - cached.ts > cacheTtlMs) return null;
|
|
return cached.data;
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function writeCachedIndicator(data) {
|
|
try {
|
|
sessionStorage.setItem(cacheKey, JSON.stringify({ ts: Date.now(), data }));
|
|
} catch (e) {}
|
|
}
|
|
|
|
async function refresh(useCache = true) {
|
|
if (useCache) {
|
|
const cached = readCachedIndicator();
|
|
if (cached) {
|
|
applyIndicator(cached);
|
|
return;
|
|
}
|
|
}
|
|
const controller = new AbortController();
|
|
const timer = setTimeout(() => controller.abort(), 2500);
|
|
try {
|
|
const r = await fetch('/observability/api/health_indicator', {
|
|
credentials: 'same-origin',
|
|
signal: controller.signal,
|
|
});
|
|
if (!r.ok) return;
|
|
const d = await r.json();
|
|
writeCachedIndicator(d);
|
|
applyIndicator(d);
|
|
} catch (e) {
|
|
} finally {
|
|
clearTimeout(timer);
|
|
}
|
|
}
|
|
refresh();
|
|
setInterval(() => refresh(false), 60000);
|
|
})();
|