fixed 维护一下项目结构

This commit is contained in:
Vaica committed 2026-01-31 23:42:49 +08:00
1 parent c7acf03b4a
commit 6ad43b2c08
87 files changed
+6864 -11017

No files matched your search

+496
View File
@@ -0,0 +1,496 @@
// 弹幕功能封装
(function() {
let barrageInstance = null; // 单例实例引用
// 获取 Artalk 评论数据
function getArtalkComments() {
return new Promise((resolve) => {
let isResolved = false;
let interval = null;
// 安全获取评论列表的辅助函数
const safeGetComments = () => {
if (!window.artalk) return null;
if (typeof window.artalk.getComments === 'function') return window.artalk.getComments();
if (typeof window.artalk.getCommentList === 'function') return window.artalk.getCommentList();
// 尝试访问内部数据 (Artalk v2 hack)
if (window.artalk.ctx && window.artalk.ctx.get('list') && typeof window.artalk.ctx.get('list').getComments === 'function') {
return window.artalk.ctx.get('list').getComments();
}
return null;
};
const tryResolve = (data, source) => {
if (isResolved) return;
if (Array.isArray(data)) {
// 如果是事件触发(说明已加载完成),或者是轮询且有数据,则认为成功
if (source === 'event' || data.length > 0) {
console.log(`[Barrage] 数据获取成功 (${source}): ${data.length} 条`);
isResolved = true;
if (interval) clearInterval(interval);
resolve(data);
}
}
};
// 检查 Artalk 实例并尝试获取数据
const checkArtalk = () => {
if (isResolved) return;
// 如果当前页面没有评论区,不要等待 Artalk 初始化,直接返回空
// 这样可以避免在无评论页(如首页)长时间等待超时
const hasCommentSection = document.getElementById('artalk') ||
document.getElementById('Comments') ||
document.querySelector('.atk-comment-wrap');
if (!hasCommentSection) {
// 延迟一点点返回,确保逻辑统一
setTimeout(() => {
tryResolve([], 'no-comment-section');
}, 100);
return;
}
if (window.artalk) {
// 1. 尝试绑定事件 (只绑定一次)
if (!window.artalk._barrageListenerAttached && typeof window.artalk.on === 'function') {
window.artalk.on('list-loaded', (data) => {
tryResolve(data, 'event');
});
window.artalk._barrageListenerAttached = true;
console.log('[Barrage] Artalk 事件监听已绑定');
}
// 2. 尝试直接获取数据
const data = safeGetComments();
if (data) {
tryResolve(data, 'poll');
}
}
};
// 立即检查
checkArtalk();
// 轮询检查 (直到获取数据或超时)
// 增加轮询频率,以便更快响应 Artalk 初始化
interval = setInterval(checkArtalk, 200);
// 超时处理 (8秒)
setTimeout(() => {
if (!isResolved) {
if (interval) clearInterval(interval);
console.warn('[Barrage] 获取数据超时');
// 最后的尝试:DOM 兜底 (适用于静态页面或 API 失败但 DOM 已渲染的情况)
const domComments = document.querySelectorAll('.atk-comment-wrap');
if (domComments.length > 0) {
console.log(`[Barrage] 从 DOM 挽救数据: ${domComments.length} 条`);
const fallbackData = Array.from(domComments).map(el => {
const idStr = el.id || '';
const id = idStr.replace('atk-comment-', '');
const contentEl = el.querySelector('.atk-content');
const avatarEl = el.querySelector('.atk-avatar img');
if (!id || !contentEl) return null;
return {
id: id,
content: contentEl.innerText || contentEl.textContent,
avatar: avatarEl ? avatarEl.src : '',
ua: null
};
}).filter(Boolean);
if (fallbackData.length > 0) {
resolve(fallbackData);
return;
}
}
resolve([]);
}
}, 8000);
});
}
// 获取 Hitokoto 一言
async function fetchHitokoto(url) {
try {
const res = await fetch(url);
if (!res.ok) throw new Error('Network response was not ok');
const data = await res.json();
return {
id: `hitokoto-${data.id}`,
content: data.hitokoto,
from: data.from
};
} catch (e) {
console.warn('[Barrage] Hitokoto fetch failed:', e);
return null;
}
}
class BarrageEngine {
constructor(options = {}) {
this.container = typeof options.container === 'string'
? document.querySelector(options.container)
: options.container;
if (!this.container) {
console.warn('[Barrage] 错误: 找不到弹幕容器');
return;
}
this.data = [];
this.index = 0;
this.timer = null;
this.isPaused = false;
this.isDestroyed = false;
// 追踪当前屏幕上显示的弹幕 ID
this.activeIds = new Set();
// 最大同屏数量限制,防止卡顿
this.maxActiveItems = 30;
// 绑定事件委托
this.initEventDelegation();
}
initEventDelegation() {
// 鼠标进入容器暂停
this.container.addEventListener('mouseover', (e) => {
const item = e.target.closest('.barrage-item');
if (item) {
item.style.animationPlayState = 'paused';
item.style.zIndex = '9999';
}
});
// 鼠标离开恢复
this.container.addEventListener('mouseout', (e) => {
const item = e.target.closest('.barrage-item');
if (item) {
item.style.animationPlayState = 'running';
item.style.zIndex = '';
}
});
// 点击事件
this.container.addEventListener('click', (e) => {
const item = e.target.closest('.barrage-item');
if (item) {
const url = item.dataset.url;
if (url && url !== '#') {
e.stopPropagation();
e.preventDefault();
const targetId = url.split('#')[1];
const targetEl = document.getElementById(targetId);
if (targetEl) {
targetEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
targetEl.classList.add('atk-flash');
setTimeout(() => targetEl.classList.remove('atk-flash'), 2000);
} else {
window.location.href = url;
}
}
}
});
}
start(data = []) {
this.stop();
this.clearDOM();
this.isDestroyed = false;
this.updateData(data);
}
updateData(data = []) {
// 数据预处理:去重 & ID 规范化
const uniqueData = [];
const seenIds = new Set();
data.forEach(item => {
const id = String(item.id);
if (!seenIds.has(id)) {
seenIds.add(id);
uniqueData.push({ ...item, id: id });
}
});
this.data = uniqueData;
// 如果数据更新时列表变短了,重置索引以防越界
if (this.index >= this.data.length) {
this.index = 0;
}
console.log(`[Barrage] 数据更新: ${this.data.length} 条`);
if (this.data.length > 0 && !this.timer && !this.isPaused) {
this.loop();
}
}
hide() {
if (this.container) {
this.container.style.opacity = '0';
this.container.style.pointerEvents = 'none';
}
this.isPaused = true;
}
show() {
if (this.container) {
this.container.style.opacity = '1';
this.container.style.pointerEvents = 'auto';
}
this.isPaused = false;
if (!this.timer) this.loop();
}
stop() {
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
// 注意:不清除 activeIds,允许已发射的弹幕飞完
}
clearDOM() {
if (this.container) {
this.container.innerHTML = '';
}
this.activeIds.clear();
}
destroy() {
this.isDestroyed = true;
this.stop();
this.clearDOM();
}
loop() {
if (this.isDestroyed || this.isPaused) return;
// 容器检测
if (!document.body.contains(this.container)) {
this.destroy();
return;
}
if (this.data.length > 0) {
// 只有当前屏幕数量未满时才发射
if (this.activeIds.size < this.maxActiveItems) {
this.tryShoot();
}
}
// 随机间隔 2s - 4s
const interval = 2000 + Math.random() * 2000;
this.timer = setTimeout(() => this.loop(), interval);
}
tryShoot() {
if (this.data.length === 0) return;
let attempts = 0;
const maxAttempts = this.data.length;
while (attempts < maxAttempts) {
const item = this.data[this.index];
this.index = (this.index + 1) % this.data.length;
if (!this.activeIds.has(item.id)) {
this.shoot(item);
return;
}
attempts++;
}
}
shoot(itemData) {
const { text, avatar, url, id } = itemData;
this.activeIds.add(id);
const item = document.createElement('div');
item.className = 'barrage-item';
// 存储 URL 到 dataset 以供事件委托使用
if (url && url !== '#') {
item.dataset.url = url;
item.style.cursor = 'pointer';
}
let html = '';
// 优化:只有当有头像时才渲染 img 标签
// 移除 loading="lazy" 以修复弹幕头像加载问题
if (avatar) {
html += `<img src="${avatar}" class="barrage-avatar" draggable="false" onerror="this.src='https://cravatar.cn/avatar/default?d=mp'">`;
}
html += `<div class="barrage-bubble"><span class="barrage-text">${text}</span></div>`;
item.innerHTML = html;
// 样式与动画
const tracks = 10;
const trackIndex = Math.floor(Math.random() * tracks);
const top = 5 + (trackIndex * (90 / tracks));
item.style.top = `${top}%`;
const duration = 18 + Math.random() * 7;
item.style.animation = `barrage-slide-right ${duration}s linear forwards`;
// 仅绑定必要的生命周期事件
item.addEventListener('animationend', () => {
item.remove();
this.activeIds.delete(id);
});
this.container.appendChild(item);
}
}
// 辅助:判断移动端
function isMobile() {
return window.innerWidth < 768 || /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
}
// 主加载函数
async function loadBarrager() {
// 移动端禁用
if (isMobile()) {
console.log('[Barrage] 移动端禁用');
if (barrageInstance) barrageInstance.destroy();
return;
}
// 检测是否存在评论区
const hasCommentSection = document.getElementById('artalk') ||
document.getElementById('Comments') ||
document.querySelector('.atk-comment-wrap');
// 即使没有评论区,也不要隐藏弹幕,允许 Hitokoto 或上一页的数据继续播放
// if (!hasCommentSection) {
// // console.log('[Barrage] 当前页面无评论区,隐藏弹幕');
// if (barrageInstance) {
// barrageInstance.hide();
// }
// return;
// }
// 1. 获取容器,如果没有则动态创建
let container = document.querySelector('.barrager');
if (!container) {
console.warn('[Barrage] 未找到 .barrager 容器,尝试动态创建');
container = document.createElement('div');
container.className = 'barrager';
document.body.appendChild(container);
}
// 确保显示(无论是否有评论区,都允许弹幕存在,可能显示的是上一页的数据或 Hitokoto)
if (barrageInstance) {
barrageInstance.show();
}
try {
const config = window.barrageConfig || {};
const promises = [getArtalkComments()];
// 如果开启了 Hitokoto,则并发获取
if (config.enableHitokoto && config.hitokotoUrl) {
promises.push(fetchHitokoto(config.hitokotoUrl));
}
// 3. 获取数据 (支持事件监听 + 轮询 + Hitokoto)
const results = await Promise.allSettled(promises);
const barrageList = [];
// 处理 Artalk 评论
if (results[0].status === 'fulfilled' && Array.isArray(results[0].value)) {
const comments = results[0].value;
const processedComments = comments.map(c => {
const text = c.content.replace(/<[^>]+>/g, "").substring(0, 60);
if (!text.trim()) return null;
let avatar = '';
// 1. 优先尝试使用 email_encrypted 构造 Gravatar (适用于跨页面无 DOM 的情况)
if (c.email_encrypted) {
avatar = `https://cravatar.cn/avatar/${c.email_encrypted}?d=mp&s=240`;
} else if (c.email_hash) {
avatar = `https://cravatar.cn/avatar/${c.email_hash}?d=mp&s=240`;
}
// 2. 如果没有 hash,尝试从 API 对象直接获取
if (!avatar && c.avatar) {
avatar = c.avatar;
}
// 3. 尝试从 UA 获取
if (!avatar && c.ua && c.ua.avatar) {
avatar = c.ua.avatar;
}
// 4. 兜底默认头像
if (!avatar) {
avatar = 'https://cravatar.cn/avatar/default?d=mp';
}
return {
id: c.id,
text: text,
avatar: avatar,
url: `#atk-comment-${c.id}`
};
}).filter(Boolean);
barrageList.push(...processedComments);
}
// 处理 Hitokoto
if (results[1] && results[1].status === 'fulfilled' && results[1].value) {
const h = results[1].value;
barrageList.push({
id: h.id,
text: `${h.content}`,
avatar: config.hitokotoAvatar || 'https://cravatar.cn/avatar/default?d=mp',
url: '#'
});
}
if (barrageList.length === 0) {
console.log('[Barrage] 无弹幕数据');
// 即使无数据,也不要销毁实例,保持空转即可
return;
}
// 更新实例或创建新实例
if (barrageInstance) {
barrageInstance.updateData(barrageList);
} else {
console.log(`[Barrage] 初始化: ${barrageList.length} 条`);
barrageInstance = new BarrageEngine({ container });
barrageInstance.start(barrageList);
}
} catch (e) {
console.warn('[Barrage] 加载异常:', e);
}
}
// 暴露全局
window.loadBarrager = loadBarrager;
// 初始化
document.addEventListener('DOMContentLoaded', () => {
setTimeout(loadBarrager, 500);
});
// PJAX 适配
document.addEventListener('pjax:complete', () => {
setTimeout(loadBarrager, 500);
});
})();
+143
View File
@@ -0,0 +1,143 @@
// 定义pjax 预设pjax
(function() {
if (typeof Pjax === 'undefined') {
console.error('Pjax library is not loaded.');
return;
}
var pjax = new Pjax({
// 这里填写页面中改变的部分(和 CSS 选择器用法一样)
selectors: ["title", "#pjax-container"],
cacheBust: false,
elements: "a", // 拦截所有链接
switches: {
"#pjax-container": Pjax.switches.default
}
});
// 将 pjax 实例挂载到 window 以便调试或外部访问
window.pjax = pjax;
console.log("Pjax initialized:", pjax);
})();
// pjax预先加载代码
function pjax_reload() {
console.log("Pjax reload triggered");
// 给pjax容器加入css过渡动画
// var pjaxContainer = document.getElementById("pjax-container");
// if (pjaxContainer) pjaxContainer.classList.add("uk-animation-slide-bottom-small");
// 评论区过渡动画
var selectors = [
".comment-meta",
".comment-author vcard",
".comment-metadata",
".comment-content",
".reply"
];
selectors.forEach(function(selector) {
document.querySelectorAll(selector).forEach(function(el) {
el.setAttribute("uk-scrollspy", "cls: uk-animation-fade; delay: 100");
});
});
// 说说页面过渡动画
var shuoshuoBody = document.getElementById("shuoshuo_body");
if (shuoshuoBody) {
shuoshuoBody.setAttribute("uk-scrollspy", "target: > .shuoshuo; cls: uk-animation-fade; delay: 250");
}
}
// pjax发送时期
document.addEventListener("pjax:send", function () {
// pjax动画减少圆圈动画
NProgress.configure({
showSpinner: false
});
// pjax执行动画开始
NProgress.start(); //加载动画效果开始
});
// Pjax 成功后,重新加载下面的函数
document.addEventListener("pjax:success", function () {
// 重载图片懒加载插件的js
if (typeof lazyLoadInit === 'function') {
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
});
}
// 激活文章图片灯箱
if (window.ViewImage) {
ViewImage.init('.post-content img');
}
});
// pjax加载完成
document.addEventListener("pjax:complete", function () {
// pjax完成动画
if (typeof NProgress !== 'undefined') {
NProgress.done();
}
// 重载一些js样式调整
pjax_reload();
// 初始化代码复制
if (typeof initCodeCopy === 'function') initCodeCopy();
// 初始化代码折叠
if (typeof initCodeFold === 'function') initCodeFold();
// 初始化段落评论
if (typeof window.initParagraphComments === 'function') {
try {
window.initParagraphComments();
} catch (e) {
console.error('initParagraphComments error:', e);
}
}
// 初始化无限加载
if (typeof window.initInfiniteScroll === 'function' && window.enableInfiniteScroll) {
window.initInfiniteScroll();
}
// 初始化友链状态
if (typeof window.initLinkStatus === 'function') {
try {
window.initLinkStatus();
} catch(e) { console.error('initLinkStatus error:', e); }
}
// 重载评论
if (typeof window.initArtalk === 'function') {
try {
window.initArtalk();
} catch(e) { console.error('initArtalk error:', e); }
}
// 重载不蒜子统计
if (typeof window.bsz_fetch === 'function') {
try {
window.bsz_fetch();
} catch(e) { console.error('bsz_fetch error:', e); }
}
// 重载 Search 和 Floating Tools (Moved from main.js)
if (typeof window.initFloatingTools === 'function') {
try {
window.initFloatingTools();
} catch(e) { console.error('initFloatingTools error:', e); }
}
if (typeof window.initSearch === 'function') {
try {
window.initSearch();
} catch(e) { console.error('initSearch error:', e); }
}
});
@@ -0,0 +1,35 @@
function checkParent(element, classNames) {
while (element) {
if (element.classList && classNames.some(cn => element.classList.contains(cn))) {
return true;
}
element = element.parentElement;
}
return false;
}
var excludedClasses = ['talks_comments','tiaozhuan-button', 'floating-tools']; // 添加需要排除的a标签类名class
window.addEventListener('load', (event) => {
document.body.addEventListener('click', function(e) {
let target = e.target;
while (target && target.nodeName !== 'A') {
target = target.parentNode;
}
if (target && target.nodeName === 'A' &&
!checkParent(target, excludedClasses) &&
!target.href.startsWith('javascript:') &&
!target.href.includes('usj.cc') &&
!target.href.includes('douban.com') &&
!target.href.includes('bilibili.com') &&
!target.href.includes('github.com') &&
!target.href.includes('zql.im') &&
!target.href.includes('5b2.cn') &&
!target.href.includes('https://beian.miit.gov.cn') &&
!target.href.includes('https://www.upyun.com') &&
target.hostname !== window.location.hostname) {
e.preventDefault();
let encodedUrl = btoa(target.href); // Base64 encode the URL
let url = '/tiaozhuan.html?target=' + encodedUrl;
window.open(url, '_blank');
}
});
});