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())

  1. 基础组件初始化:导航、返回顶部、彩蛋、音乐播放器
  2. 懒加载配置:使用LazyLoad库优化图片加载
  3. PJAX配置:实现无刷新页面切换
  4. 进度条集成:使用NProgress显示加载进度
  5. 标题处理:支持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应用,特别适合个人博客、作品展示或内容管理系统,具有良好的用户体验和开发者友好的调试功能。

评论加载中...