import {CommentSystem} from './modules/comment/index.js';
import {Nav} from './modules/nav.js';
import {Home} from './modules/home/index.js';
import {ArticleRead} from './modules/article-read.js';
import {Gallery} from './modules/gallery.js';
import {Steam} from './modules/steam.js';
import {BackToTop} from "./modules/back-to-top.js";
import {MumbleApp} from "./modules/mumble.js";
import {MusicPlayer} from "./modules/music-player/index.js";
import {BookmarkManager} from "./modules/bookmark.js?_version=2_3";
import {CodeExplorer} from "./modules/code-explorer.js";
import {Friends} from "./modules/friends.js";
import {Timeline} from "./modules/timeline.js";
import {Albums} from "./modules/albums.js?_version=2_2";
import {Video} from "./modules/video.js";
import {dialog} from './modules/dialog.js';
import {initSystemNotifications} from './modules/signalr-notification.js';
import {CodeArea} from "./modules/code-area.js";
import {groupChat} from './modules/group-chat/index.js';
import {EasterEgg} from "./modules/easter-egg.js";
import {updateTimeTags} from './modules/util.js';
import {watermelonGame} from './modules/watermelon-game.js';
class App {
constructor() {
this.init().then(() => console.log("App init"));
}
async init() {
this.nav = new Nav();
new BackToTop();
new EasterEgg();
void watermelonGame;
new MusicPlayer();
const lazyLoadInstance = new LazyLoad({
elements_selector: ".lazy"
});
this.initComponents();
this.initTooltips();
updateTimeTags();
// PJAX Configuration
$.pjax.defaults.timeout = 5000;
$(document).pjax('a[data-pjax],#pager:not(#article-pager #pager) a', '#pjax-container');
$(document).pjax('#article-pager #pager a', '#article-list')
$(document).on('submit', 'form[data-pjax]', function (event) {
$.pjax.submit(event, '#pjax-container')
});
$(document).on('pjax:send', function () {
if (typeof NProgress !== 'undefined') NProgress.start();
dialog.hideTooltip();
});
const that = this;
$(document).on('pjax:complete', async function (event, xhr) {
const titleValue = xhr.getResponseHeader("title")
if (titleValue === "" || titleValue === null) {
document.title = "(;´д`)ゞ标题不见啦 - 叫我阿胖";
} else {
try {
// 先用 atob 解码 Base64
const binaryString = atob(titleValue);
// 转换为字节数组
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
// 使用 UTF-8 解码
const decoder = new TextDecoder('utf-8');
document.title = decoder.decode(bytes);
} catch (e) {
document.title = titleValue;
}
}
});
$(document).on('pjax:end', async function () {
if (typeof NProgress !== 'undefined') NProgress.done();
// Close mobile menu on pjax complete
if (that.nav) {
that.nav.closeMobileMenu();
}
// Re-init fetch content on PJAX load
await that.initFetchContent();
that.initComponents();
updateTimeTags();
lazyLoadInstance.update();
that.setUnSelect();
});
await this.initFetchContent();
// 初始化 SignalR 通知
await initSystemNotifications();
// 初始化调试控制台加载器
this._initDebugConsole();
this.setUnSelect();
// 初始化群组聊天(彩蛋功能,通过控制台开启)
// 已在 group-chat.js 中自动初始化
// Mobile Menu Toggle
$('.mobile-menu-toggle').on('click', function () {
$('.mobile-nav').addClass('is-open');
$('.mobile-nav-overlay').addClass('is-open');
});
// Close mobile menu on overlay click or link click
$('.mobile-nav-overlay, .mobile-nav a').on('click', function () {
$('.mobile-nav').removeClass('is-open');
$('.mobile-nav-overlay').removeClass('is-open');
});
}
initComponents() {
new Home();
new ArticleRead();
new Steam();
new MumbleApp();
new BookmarkManager();
new CodeExplorer();
new Friends();
new Timeline();
new Albums();
new Video();
new CodeArea();
// Initialize Gallery for article images and mumble gallery images
const articleImages = document.querySelectorAll('.article-card__cover img,.markdown-body img,.mumble-gallery__image');
if (articleImages.length > 0) {
new Gallery(articleImages);
}
}
initTooltips() {
// Global handler to hide tooltips on interaction
const hideGlobal = () => dialog.hideTooltip();
document.addEventListener('click', hideGlobal, true);
let isTouch = false;
document.addEventListener('touchstart', () => {
isTouch = true;
hideGlobal();
setTimeout(() => isTouch = false, 1000);
}, {passive: true, capture: true});
// 阻止触摸设备上带有 title 元素的默认右键菜单(长按菜单)
document.addEventListener('contextmenu', (e) => {
if (!isTouch) return;
const target = e.target.closest('[title], [data-original-title]');
if (target) {
e.preventDefault();
}
});
// Handle title attribute tooltips
document.body.addEventListener('mouseenter', (e) => {
const target = e.target;
// Handle element with title
if (target && target.hasAttribute && target.hasAttribute('title')) {
const title = target.getAttribute('title');
if (!title) return;
target.setAttribute('data-original-title', title);
target.removeAttribute('title');
dialog.showTooltip(target, title);
}
}, true);
document.body.addEventListener('mouseleave', (e) => {
const target = e.target;
if (target && target.hasAttribute && target.hasAttribute('data-original-title')) {
const title = target.getAttribute('data-original-title');
target.setAttribute('title', title);
target.removeAttribute('data-original-title');
dialog.hideTooltip();
}
}, true);
// Handle time tag interactions (Touch & Mouse)
const handleTimeTooltip = (e) => {
const time = e.target.closest('time');
if (time) {
const datetime = time.getAttribute('datetime');
// Priority: Use title if exists (handled by above mouseenter), otherwise use datetime
// But for touch/click we want explicit datetime if title mechanism didn't trigger
if (datetime) {
dialog.showTooltip(time, datetime);
}
}
};
const handleTimeTooltipHide = (e) => {
const time = e.target.closest('time');
if (time) {
// For touch end, we delay hide
if (e.type === 'touchend') {
setTimeout(() => dialog.hideTooltip(), 1500);
} else {
dialog.hideTooltip();
}
}
};
// Touch events
document.body.addEventListener('touchstart', handleTimeTooltip, {passive: true});
document.body.addEventListener('touchend', handleTimeTooltipHide, {passive: true});
}
/**
* Auto fetch content for [data-request] elements
* */
async initFetchContent() {
let fetchContents = document.querySelectorAll("[data-request]");
for (let item of fetchContents) {
let loaded = item.dataset["status"];
if (loaded === "loaded") {
continue;
}
try {
let response = await fetch(item.dataset.request, {
method: 'GET'
});
if (response.ok) {
item.innerHTML = await response.text();
item.dataset["status"] = "loaded";
const comment = item.querySelector('.comment-section');
if (comment) {
new CommentSystem(comment);
}
// Optional: Re-init plugins if needed inside loaded content
// if($ && $.fn.relativeTime) $("time.timeago").relativeTime();
} else {
console.error("Failed to fetch content for", item.dataset.request);
item.innerHTML = '<div class="error-message">加载失败</div>';
}
} catch (error) {
console.error("Error fetching content:", error);
}
}
}
help() {
console.group('🥚 彩蛋指南');
console.log('1. 连续点击页脚版本号 5 次:触发破碎特效');
console.log('2. 连续点击页脚版本号 10 次:开启群组聊天');
console.log('3. 控制台输入 "openGroupChat()":直接开启群组聊天');
console.log('4. 控制台输入 "openWatermelonGame()":开启合成大西瓜');
console.log('5. 连续点击页脚版权信息 5 次:开启移动端调试工具');
console.groupEnd();
}
/**
* 初始化调试控制台加载器
* 连续点击页脚版权信息5次后初始化 Eruda 调试工具
*/
_initDebugConsole() {
let clickCount = 0;
let lastClickTime = 0;
// 2秒内连续点击有效,超过则重置计数
const resetTimeout = 2000;
const copyrightElement = document.querySelector('.site-footer .container > p:last-child');
if (!copyrightElement) return;
copyrightElement.style.cursor = 'pointer';
copyrightElement.style.userSelect = 'none';
copyrightElement.addEventListener('click', () => {
const now = Date.now();
// 如果距离上次点击超过2秒,重置计数
if (now - lastClickTime > resetTimeout) {
clickCount = 0;
}
clickCount++;
lastClickTime = now;
if (clickCount >= 5) {
clickCount = 0; // 重置计数
this._initEruda();
}
});
}
async _initEruda() {
if (window.eruda) {
if (!window.eruda._isInit) {
eruda.init();
}
return;
}
}
setUnSelect(){
document.querySelectorAll('time[datetime],[title]').forEach(item => {
if(item.style.userSelect !== 'none'){
item.style.userSelect = 'none';
}
});
}
}
document.addEventListener('DOMContentLoaded', () => {
const app = new App();
window.help = app.help;
});
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
这是一个现代化的单页应用(SPA)主入口文件,使用模块化架构构建的个人博客或内容管理系统。让我详细解释一下代码的结构和功能:
整体架构
1. 模块导入
代码开头导入了多个功能模块:
- 评论系统 (
CommentSystem) - 导航栏 (
Nav) - 首页 (
Home) - 文章阅读 (
ArticleRead) - 图片画廊 (
Gallery) - Steam集成 (
Steam) - 音乐播放器 (
MusicPlayer) - 书签管理 (
BookmarkManager) - 代码浏览器 (
CodeExplorer) - 时间轴 (
Timeline) - 相册 (
Albums) - 视频播放 (
Video) - 对话框 (
dialog) - 实时通知 (
initSystemNotifications) - 群组聊天 (
groupChat) - 彩蛋功能 (
EasterEgg) - 工具函数 (
updateTimeTags) - 小游戏 (
watermelonGame)
2. App类的核心功能
初始化流程 (init())
- 基础组件初始化:导航、返回顶部、彩蛋、音乐播放器
- 懒加载配置:使用LazyLoad库优化图片加载
- PJAX配置:实现无刷新页面切换
- 进度条集成:使用NProgress显示加载进度
- 标题处理:支持Base64编码的页面标题解码
PJAX集成
// PJAX事件监听
$(document).on('pjax:send', function () {
if (typeof NProgress !== 'undefined') NProgress.start();
dialog.hideTooltip();
});
$(document).on('pjax:end', async function () {
// 重新初始化组件
await that.initFetchContent();
that.initComponents();
updateTimeTags();
lazyLoadInstance.update();
});
动态内容加载 (initFetchContent())
- 自动查找带有
data-request属性的元素 - 异步获取内容并插入到页面
- 支持评论系统的动态初始化
- 包含错误处理机制
工具提示系统 (initTooltips())
- 处理
title属性的鼠标悬停提示 - 支持触摸设备的交互
- 特殊处理时间标签的提示显示
- 全局事件管理,避免提示重叠
3. 彩蛋功能
调试控制台加载器
_initDebugConsole() {
// 连续点击页脚版权信息5次启动Eruda调试工具
// 适用于移动端调试
}
帮助系统
help() {
console.group('🥚 彩蛋指南');
console.log('1. 连续点击页脚版本号 5 次:触发破碎特效');
console.log('2. 连续点击页脚版本号 10 次:开启群组聊天');
// ... 更多彩蛋说明
}
4. 移动端适配
- 移动菜单切换逻辑
- 触摸事件优化
- 响应式交互处理
5. 技术特点
优点:
- 模块化架构,代码组织清晰
- 支持无刷新页面切换(PJAX)
- 懒加载优化性能
- 完善的错误处理
- 移动端友好
- 丰富的交互功能
使用的技术栈:
- ES6+ 模块系统
- jQuery + PJAX
- LazyLoad图片懒加载
- NProgress进度条
- SignalR实时通信
- Eruda移动端调试
这是一个功能丰富的现代化Web应用,特别适合个人博客、作品展示或内容管理系统,具有良好的用户体验和开发者友好的调试功能。
评论加载中...