/** * AKSRT Yiyan — 一言轮播组件 * * Fetches random quotes from hitokoto.cn API and auto-rotates. * Public v1 returns one sentence per request; multi-quote carousels * issue parallel fetches. Category `c` must be a letter a–l (or omitted). * * @package AKSRT */ (function () { 'use strict'; var API_BASE = 'https://v1.hitokoto.cn/'; var TYPE_RE = /^[a-l]$/i; var MIN_COUNT = 1; var MAX_COUNT = 10; var DEFAULT_COUNT = 5; var DEFAULT_INTERVAL_SEC = 5; function normalizeType(raw) { if (raw == null || raw === '') { return ''; } var t = String(raw).trim().toLowerCase(); return TYPE_RE.test(t) ? t : ''; } function normalizeCount(raw) { var n = parseInt(raw, 10); if (isNaN(n) || n < MIN_COUNT) { return DEFAULT_COUNT; } return Math.min(MAX_COUNT, n); } function normalizeIntervalMs(raw) { var sec = parseInt(raw, 10); if (isNaN(sec) || sec < 3) { sec = DEFAULT_INTERVAL_SEC; } return Math.min(60, sec) * 1000; } function buildApiUrl(type) { var params = new URLSearchParams(); params.set('encode', 'json'); if (type) { params.set('c', type); } return API_BASE + '?' + params.toString(); } function mapQuote(data) { if (!data || typeof data !== 'object') { return null; } var text = data.hitokoto || data.text || ''; if (!text) { return null; } return { text: String(text), from: data.from ? String(data.from) : '', }; } function ensureStructure(el) { var textEl = el.querySelector('.aksrt-yiyan__text'); if (textEl) { return { textEl: textEl, fromEl: el.querySelector('.aksrt-yiyan__from'), dotsEl: el.querySelector('.aksrt-yiyan__dots'), }; } var content = document.createElement('div'); content.className = 'aksrt-yiyan__content'; textEl = document.createElement('p'); textEl.className = 'aksrt-yiyan__text'; textEl.textContent = '加载中…'; content.appendChild(textEl); var fromEl = document.createElement('p'); fromEl.className = 'aksrt-yiyan__from'; content.appendChild(fromEl); el.appendChild(content); var dotsEl = document.createElement('div'); dotsEl.className = 'aksrt-yiyan__dots'; dotsEl.setAttribute('aria-hidden', 'true'); el.appendChild(dotsEl); return { textEl: textEl, fromEl: fromEl, dotsEl: dotsEl }; } function fetchOneQuote(type) { return fetch(buildApiUrl(type), { credentials: 'omit', mode: 'cors', }).then(function (r) { return r .json() .catch(function () { return null; }) .then(function (data) { if (!r.ok) { var msg = data && data.message ? String(data.message) : 'HTTP ' + r.status; throw new Error(msg); } var quote = mapQuote(data); if (!quote) { throw new Error('empty'); } return quote; }); }); } function fetchQuotes(type, count) { var jobs = []; var i; for (i = 0; i < count; i += 1) { jobs.push( fetchOneQuote(type).catch(function () { return null; }) ); } return Promise.all(jobs).then(function (results) { var quotes = []; var seen = Object.create(null); results.forEach(function (q) { if (!q || !q.text) { return; } // Dedupe identical text from parallel random draws. if (seen[q.text]) { return; } seen[q.text] = 1; quotes.push(q); }); return quotes; }); } function initYiyan(el) { if (el.dataset.yiyanBound) { return; } el.dataset.yiyanBound = '1'; var count = normalizeCount(el.dataset.count); var interval = normalizeIntervalMs(el.dataset.interval); var type = normalizeType(el.dataset.type); var parts = ensureStructure(el); var textEl = parts.textEl; var fromEl = parts.fromEl; var dotsEl = parts.dotsEl; var quotes = []; var currentIndex = 0; var timer = null; function showQuote(index) { if (!quotes.length || !textEl) { return; } var q = quotes[index % quotes.length]; textEl.style.opacity = '0'; setTimeout(function () { textEl.textContent = '"' + q.text + '"'; textEl.style.opacity = '1'; if (fromEl) { fromEl.textContent = q.from ? '—— ' + q.from : ''; } }, 200); if (dotsEl) { dotsEl.querySelectorAll('.aksrt-yiyan__dot').forEach(function (dot, i) { dot.classList.toggle('is-active', i === index % quotes.length); }); } } function nextQuote() { if (quotes.length < 2) { return; } currentIndex = (currentIndex + 1) % quotes.length; showQuote(currentIndex); } function startTimer() { if (timer) { clearInterval(timer); timer = null; } if (quotes.length > 1) { timer = setInterval(nextQuote, interval); } } function buildDots() { if (!dotsEl || quotes.length < 2) { if (dotsEl) { dotsEl.innerHTML = ''; } return; } dotsEl.innerHTML = ''; quotes.forEach(function (_, i) { var dot = document.createElement('span'); dot.className = 'aksrt-yiyan__dot' + (i === 0 ? ' is-active' : ''); dotsEl.appendChild(dot); }); } el.addEventListener('mouseenter', function () { if (timer) { clearInterval(timer); timer = null; } }); el.addEventListener('mouseleave', function () { startTimer(); }); fetchQuotes(type, count) .then(function (list) { quotes = list; if (!quotes.length) { textEl.textContent = '暂无内容'; return; } buildDots(); showQuote(0); startTimer(); }) .catch(function () { textEl.textContent = '加载失败,请稍后再试'; }); } document.addEventListener('DOMContentLoaded', function () { document.querySelectorAll('[data-aksrt-yiyan]').forEach(initYiyan); }); })();