2026-01-22 21:39:19 +08:00
|
|
|
// 判断是否为移动端
|
|
|
|
|
function isMobile() {
|
|
|
|
|
if (window.navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i)) {
|
|
|
|
|
return true; // 移动端
|
|
|
|
|
} else {
|
|
|
|
|
return false; // PC端
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 文章图片灯箱
|
|
|
|
|
window.ViewImage && ViewImage.init('.post-content img');
|
|
|
|
|
|
|
|
|
|
// Pure JavaScript Back to Top (High Performance)
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
|
|
|
var backToTopButton = document.getElementById('back-to-top');
|
|
|
|
|
if (backToTopButton) {
|
|
|
|
|
var isVisible = false;
|
|
|
|
|
var ticking = false;
|
|
|
|
|
|
|
|
|
|
function update() {
|
|
|
|
|
if (window.scrollY > 300) {
|
|
|
|
|
if (!isVisible) {
|
|
|
|
|
backToTopButton.classList.add('show');
|
|
|
|
|
isVisible = true;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (isVisible) {
|
|
|
|
|
backToTopButton.classList.remove('show');
|
|
|
|
|
isVisible = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ticking = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
window.addEventListener('scroll', function() {
|
|
|
|
|
if (!ticking) {
|
|
|
|
|
window.requestAnimationFrame(update);
|
|
|
|
|
ticking = true;
|
|
|
|
|
}
|
|
|
|
|
}, { passive: true });
|
|
|
|
|
|
|
|
|
|
backToTopButton.addEventListener('click', function(e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
window.scrollTo({
|
|
|
|
|
top: 0,
|
|
|
|
|
behavior: 'smooth'
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// loader定时器
|
|
|
|
|
$(document).ready(function () {
|
|
|
|
|
setTimeout(function () {
|
|
|
|
|
$("#loader").addClass("hide");
|
|
|
|
|
}, 30000);
|
|
|
|
|
});
|
|
|
|
|
// 点击进入按钮
|
|
|
|
|
$("#closeloader").click(function () {
|
|
|
|
|
$("#loader").addClass("hide");
|
|
|
|
|
});
|
|
|
|
|
// nav栏样式调整
|
|
|
|
|
$('#libabys').first().attr("style", "margin-left: -5px");
|
|
|
|
|
|
|
|
|
|
$('#libabys').eq(1).attr("style", "margin-left: -5px");
|
|
|
|
|
// 友链列表版样式调整
|
|
|
|
|
$('.itp-index:odd').attr("style", "float: right;width:48%;");
|
|
|
|
|
|
|
|
|
|
//文章图片懒加载
|
|
|
|
|
lazyLoadInit({
|
|
|
|
|
// coverColor: "white",
|
|
|
|
|
// coverDiv: "<div style='display: flex; justify-content: center; align-items: center; height: 200px; color: #000000;'><span>优世界·一个记录生活点滴的小站</span></div>",
|
|
|
|
|
offsetBottom: 800,
|
|
|
|
|
offsetTopm: 800,
|
|
|
|
|
showTime: 1100
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
async function loadBarrager() {
|
|
|
|
|
try {
|
|
|
|
|
const commentsData = await initArtalkAndGetComments();
|
|
|
|
|
if (commentsData) {
|
|
|
|
|
await createBarragerFromArtalk(commentsData);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('加载弹幕时发生错误:', error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 异步初始化 Artalk 并获取评论数据
|
|
|
|
|
function initArtalkAndGetComments() {
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
Artalk.use((ctx) => {
|
|
|
|
|
ctx.on('list-loaded', () => {
|
|
|
|
|
console.log('评论列表加载完毕');
|
|
|
|
|
const commentsData = ctx.getComments();
|
|
|
|
|
if (commentsData) {
|
|
|
|
|
resolve(commentsData);
|
|
|
|
|
// console.log(commentsData);
|
|
|
|
|
} else {
|
|
|
|
|
reject('获取评论数据失败');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Artalk.init({
|
|
|
|
|
el: '#Comments', // 绑定元素的 Selector
|
2026-01-23 22:02:11 +08:00
|
|
|
server: window.siteConfig.artalk.server, // 后端地址
|
|
|
|
|
site: window.siteConfig.artalk.site, // 你的站点名
|
2026-01-22 21:39:19 +08:00
|
|
|
flatMode: true,
|
2026-01-23 22:02:11 +08:00
|
|
|
emoticons: window.siteConfig.artalk.emoticons
|
2026-01-22 21:39:19 +08:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 异步创建弹幕
|
|
|
|
|
async function createBarragerFromArtalk(commentsData) {
|
|
|
|
|
const looperTime = 1000; // 每条弹幕的发送间隔,单位毫秒
|
2026-01-23 22:02:11 +08:00
|
|
|
|
|
|
|
|
// 1. 处理评论数据(扁平化、乱序)
|
2026-01-22 21:39:19 +08:00
|
|
|
const flattenedComments = flattenComments(commentsData);
|
2026-01-23 22:02:11 +08:00
|
|
|
shuffleArray(flattenedComments); // 随机打乱评论顺序
|
|
|
|
|
const selectedComments = flattenedComments; // 使用所有评论
|
|
|
|
|
|
|
|
|
|
// 2. 构建弹幕列表
|
|
|
|
|
let barrageList = selectedComments.map(comment => ({
|
|
|
|
|
type: 'comment',
|
|
|
|
|
content: stripHTML(comment.content_marked) || 'No comment',
|
|
|
|
|
// 修复头像模糊:请求 200px 的图片 (s=200)
|
|
|
|
|
headImg: window.siteConfig.artalk.gravatar + comment.email_encrypted + "?s=200" || 'static/default-avatar.png',
|
|
|
|
|
href: comment.link || '#',
|
|
|
|
|
speed: 35
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// 3. 尝试获取在线一言并插入
|
|
|
|
|
if (window.siteConfig.barrage && window.siteConfig.barrage.enableHitokoto) {
|
|
|
|
|
try {
|
|
|
|
|
const hitokoto = await fetchHitokoto();
|
|
|
|
|
if (hitokoto) {
|
|
|
|
|
const insertIndex = Math.floor(Math.random() * (barrageList.length + 1));
|
|
|
|
|
barrageList.splice(insertIndex, 0, {
|
|
|
|
|
type: 'hitokoto',
|
|
|
|
|
content: `『一言』${hitokoto}`,
|
|
|
|
|
headImg: window.siteConfig.author.avatar,
|
|
|
|
|
href: 'https://hitokoto.cn',
|
|
|
|
|
speed: 35
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error('获取一言失败:', e);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-22 21:39:19 +08:00
|
|
|
|
|
|
|
|
// 初始化弹幕插件
|
|
|
|
|
let barrage = new Barrage.default({
|
|
|
|
|
el: 'body',
|
|
|
|
|
backColor: "#000000b5",
|
2026-01-23 22:02:11 +08:00
|
|
|
fontSize: "13px", // 字体稍微改小一点点以适配小头像
|
2026-01-22 21:39:19 +08:00
|
|
|
isLeft: true,
|
|
|
|
|
speed: 0,
|
2026-01-23 22:02:11 +08:00
|
|
|
height: "30px" // 整体高度稍微减小
|
2026-01-22 21:39:19 +08:00
|
|
|
});
|
|
|
|
|
|
2026-01-23 22:02:11 +08:00
|
|
|
const totalItems = barrageList.length;
|
|
|
|
|
let currentIndex = 0;
|
2026-01-22 21:39:19 +08:00
|
|
|
|
2026-01-23 22:02:11 +08:00
|
|
|
if (totalItems !== 0) {
|
|
|
|
|
while (currentIndex < totalItems) {
|
|
|
|
|
const item = barrageList[currentIndex];
|
|
|
|
|
|
2026-01-22 21:39:19 +08:00
|
|
|
// 发送弹幕
|
2026-01-23 22:02:11 +08:00
|
|
|
barrage.start(item.content, {
|
|
|
|
|
speed: item.speed,
|
|
|
|
|
headImg: item.headImg,
|
|
|
|
|
href: item.href
|
2026-01-22 21:39:19 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
currentIndex++;
|
|
|
|
|
await delay(looperTime); // 等待指定的时间再发送下一条弹幕
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-23 22:02:11 +08:00
|
|
|
// 数组乱序算法 (Fisher-Yates Shuffle)
|
|
|
|
|
function shuffleArray(array) {
|
|
|
|
|
for (let i = array.length - 1; i > 0; i--) {
|
|
|
|
|
const j = Math.floor(Math.random() * (i + 1));
|
|
|
|
|
[array[i], array[j]] = [array[j], array[i]];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取一言
|
|
|
|
|
async function fetchHitokoto() {
|
|
|
|
|
const url = window.siteConfig.barrage.hitokotoUrl || 'https://v1.hitokoto.cn?c=a&c=b&c=c&c=d&c=i';
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(url);
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
return data.hitokoto;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-22 21:39:19 +08:00
|
|
|
// 延迟函数,返回一个 Promise,用于等待指定的毫秒数
|
|
|
|
|
function delay(ms) {
|
|
|
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 去除 HTML 标签的函数
|
|
|
|
|
function stripHTML(html) {
|
|
|
|
|
return html.replace(/<\/?[^>]+(>|$)/g, "");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 展平评论数据结构的函数
|
|
|
|
|
function flattenComments(comments) {
|
|
|
|
|
let result = [];
|
|
|
|
|
comments.forEach(function (comment) {
|
|
|
|
|
result.push(comment);
|
|
|
|
|
if (comment.children && comment.children.length > 0) {
|
|
|
|
|
result = result.concat(flattenComments(comment.children));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 调用异步的弹幕加载函数
|
|
|
|
|
if (document.querySelector("#Comments") && !isMobile()) {
|
|
|
|
|
loadBarrager()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getCurrentTheme() {
|
|
|
|
|
var theme = document.documentElement.getAttribute('data-theme');
|
|
|
|
|
if (!theme) {
|
|
|
|
|
theme = 'light';
|
|
|
|
|
}
|
|
|
|
|
return theme;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function applyTheme(theme) {
|
|
|
|
|
document.documentElement.setAttribute('data-theme', theme);
|
|
|
|
|
try {
|
|
|
|
|
localStorage.setItem('theme', theme);
|
|
|
|
|
} catch (e) {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function updateThemeToggle(theme) {
|
|
|
|
|
var button = document.getElementById('theme-toggle');
|
|
|
|
|
if (theme === 'dark') {
|
|
|
|
|
if (button) {
|
|
|
|
|
button.textContent = '日间';
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (button) {
|
|
|
|
|
button.textContent = '夜间';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function toggleTheme() {
|
|
|
|
|
var current = getCurrentTheme();
|
|
|
|
|
var next = current === 'dark' ? 'light' : 'dark';
|
|
|
|
|
applyTheme(next);
|
|
|
|
|
updateThemeToggle(next);
|
|
|
|
|
if (window.Artalk && window.Artalk.get) {
|
|
|
|
|
try {
|
|
|
|
|
var ctx = Artalk.get('#Comments');
|
|
|
|
|
if (ctx && ctx.setDarkMode) {
|
|
|
|
|
ctx.setDarkMode(next === 'dark');
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
|
|
|
var theme = getCurrentTheme();
|
|
|
|
|
updateThemeToggle(theme);
|
|
|
|
|
var button = document.getElementById('theme-toggle');
|
|
|
|
|
if (button) {
|
|
|
|
|
button.addEventListener('click', toggleTheme);
|
|
|
|
|
}
|
|
|
|
|
var avatarWrapper = document.querySelector('.head_author_img');
|
|
|
|
|
var avatarTip = document.querySelector('.avatar-theme-tip');
|
|
|
|
|
if (avatarWrapper) {
|
|
|
|
|
avatarWrapper.style.cursor = 'pointer';
|
|
|
|
|
avatarWrapper.addEventListener('click', toggleTheme);
|
|
|
|
|
}
|
|
|
|
|
if (avatarTip) {
|
|
|
|
|
avatarTip.classList.add('is-visible');
|
|
|
|
|
var hideOnce = function () {
|
|
|
|
|
avatarTip.classList.add('is-hidden');
|
|
|
|
|
};
|
|
|
|
|
setTimeout(hideOnce, 9000);
|
|
|
|
|
if (avatarWrapper) {
|
|
|
|
|
avatarWrapper.addEventListener('click', hideOnce);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 代码复制功能
|
|
|
|
|
function initCodeCopy() {
|
|
|
|
|
var codeBlocks = document.querySelectorAll('.highlight');
|
|
|
|
|
codeBlocks.forEach(function(block) {
|
|
|
|
|
if (block.querySelector('.copy-code-btn')) return;
|
|
|
|
|
|
|
|
|
|
var btn = document.createElement('button');
|
|
|
|
|
btn.className = 'copy-code-btn';
|
|
|
|
|
btn.innerHTML = '<i class="ri-file-copy-line"></i>';
|
|
|
|
|
block.appendChild(btn);
|
|
|
|
|
|
|
|
|
|
btn.addEventListener('click', function() {
|
|
|
|
|
var code = block.querySelector('code').innerText;
|
|
|
|
|
navigator.clipboard.writeText(code).then(function() {
|
|
|
|
|
btn.innerHTML = '<i class="ri-check-line"></i>';
|
|
|
|
|
setTimeout(function() {
|
|
|
|
|
btn.innerHTML = '<i class="ri-file-copy-line"></i>';
|
|
|
|
|
}, 2000);
|
|
|
|
|
}).catch(function(err) {
|
|
|
|
|
console.error('Failed to copy: ', err);
|
|
|
|
|
btn.innerText = 'Error';
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 页面加载完成后初始化
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
|
|
|
initCodeCopy();
|
2026-01-23 22:02:11 +08:00
|
|
|
initDescriptionHitokoto();
|
2026-01-22 21:39:19 +08:00
|
|
|
});
|
2026-01-23 22:02:11 +08:00
|
|
|
|
|
|
|
|
// 初始化个人描述一言切换
|
|
|
|
|
function initDescriptionHitokoto() {
|
|
|
|
|
const descEl = document.querySelector('.yiyandes');
|
|
|
|
|
if (!descEl) return;
|
|
|
|
|
|
|
|
|
|
descEl.title = '点击切换一言';
|
|
|
|
|
|
|
|
|
|
descEl.addEventListener('click', async () => {
|
|
|
|
|
if (descEl.dataset.loading === 'true') return;
|
|
|
|
|
|
|
|
|
|
descEl.dataset.loading = 'true';
|
|
|
|
|
const originalText = descEl.innerText;
|
|
|
|
|
descEl.innerText = '正在获取灵感...';
|
|
|
|
|
|
|
|
|
|
const hitokoto = await fetchHitokoto();
|
|
|
|
|
|
|
|
|
|
if (hitokoto) {
|
|
|
|
|
typeWriter(descEl, hitokoto);
|
|
|
|
|
} else {
|
|
|
|
|
descEl.innerText = originalText;
|
|
|
|
|
descEl.dataset.loading = 'false';
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 打字机效果
|
|
|
|
|
function typeWriter(element, text, speed = 100) {
|
|
|
|
|
element.innerText = '';
|
|
|
|
|
element.classList.add('typing');
|
|
|
|
|
let i = 0;
|
|
|
|
|
|
|
|
|
|
function type() {
|
|
|
|
|
if (i < text.length) {
|
|
|
|
|
element.innerText += text.charAt(i);
|
|
|
|
|
i++;
|
|
|
|
|
setTimeout(type, speed);
|
|
|
|
|
} else {
|
|
|
|
|
element.dataset.loading = 'false';
|
|
|
|
|
element.classList.remove('typing');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type();
|
|
|
|
|
}
|