Files
wordpress-theme-aksrt/assets/js/yiyan.js
T
kate522 6bd7676b73 fix(yiyan): stop invalid Hitokoto category requests
Public hitokoto v1 rejects c=hitokoto (and ignores batch count). Whitelist
categories a–l, multi-fetch for carousel, and share full markup via
aksrt_render_yiyan_block so single-post shells actually render quotes.
2026-07-17 10:33:00 +08:00

256 lines
6.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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);
});
})();