|
|
- /**
- * PJAX 初始化与页面切换重绑定脚本
- * 依赖:jQuery, jquery.pjax.min.js
- * 加载顺序:在 jquery.pjax.min.js 之后,body 末尾
- */
-
- (function ($) {
- // ========== 常量 ==========
- var CONTAINER = '#pjax-container';
- var PJAX_OPTS = {
- container: CONTAINER,
- fragment: CONTAINER,
- timeout: 8000,
- scrollTo: false
- };
-
- // ========== 工具函数 ==========
-
- var _loadedScripts = {};
-
- /** 动态加载外部 CSS(避免重复加载) */
- function loadCSS(href) {
- if ($('link[href="' + href + '"]').length) return;
- $('<link rel="stylesheet" href="' + href + '" />').appendTo('head');
- }
-
- /** 动态加载外部 JS(去重) */
- function loadScript(src, callback) {
- if (_loadedScripts[src]) {
- if (typeof callback === 'function') callback();
- return;
- }
- _loadedScripts[src] = true;
- var s = document.createElement('script');
- s.src = src;
- s.onload = callback || null;
- document.body.appendChild(s);
- }
-
- // ========== 页面类型判断 ==========
-
- /** 是否为文章页(非首页/分页) */
- function isPostPage(pathname) {
- return !/^(\/(index\.html)?|\/page\d+(\/index\.html)?)$/.test(pathname || window.location.pathname);
- }
-
- /** 是否为真正的文章页(用 DOM 特征判断,仅 post 布局才有这些元素) */
- function isRealPostPage() {
- return $(CONTAINER + ' #gitalk-container').length > 0;
- }
-
- // ========== 欢迎语生成 ==========
-
- /**
- * 根据当前时间和页面生成 Live2D 欢迎语
- * 此函数暴露到 window._live2d.getWelcomeText,供 message.js 首次加载时复用
- * @param {string} [pathname] - 页面路径,默认当前路径
- * @param {string} [title] - 页面标题,默认从 document.title 提取
- * @returns {string} 欢迎语 HTML
- */
- function getWelcomeText(pathname, title) {
- pathname = pathname || window.location.pathname;
- title = title || document.title.split(' | ')[0];
-
- if (pathname === '/' || pathname === '/index.html') {
- var now = (new Date()).getHours();
- if (now > 23 || now <= 5) return '你是夜猫子呀?这么晚还不睡觉,明天起的来嘛?';
- if (now > 5 && now <= 7) return '早上好!一日之计在于晨,美好的一天就要开始了!';
- if (now > 7 && now <= 11) return '上午好!工作顺利嘛,不要久坐,多起来走动走动哦!';
- if (now > 11 && now <= 14) return '中午了,工作了一个上午,现在是午餐时间!';
- if (now > 14 && now <= 17) return '午后很容易犯困呢,今天的运动目标完成了吗?';
- if (now > 17 && now <= 19) return '傍晚了!窗外夕阳的景色很美丽呢,最美不过夕阳红~~';
- if (now > 19 && now <= 21) return '晚上好,今天过得怎么样?';
- if (now > 21 && now <= 23) return '已经这么晚了呀,早点休息吧,晚安~~';
- return '嗨~ 快来逗我玩吧!';
- }
- return '欢迎阅读<span style="color:#0099cc;">「 ' + title + ' 」</span>';
- }
-
- // ========== 各组件重初始化 ==========
-
- /** 访问量统计 */
- function reinitVisitors() {
- if (typeof BlogAPI === 'undefined') return;
- var apiBase = BlogAPI;
- if ($('.visitors').length === 1) {
- var $visitor = $('.visitors:first');
- $.get(apiBase + '/count_click_add?id=' + $visitor.attr('id'), function (data) {
- $visitor.text(Number(data));
- });
- } else if ($('.visitors-index').length > 0) {
- $('.visitors-index').each(function () {
- var $elem = $(this);
- $.get(apiBase + '/count_click?id=' + $elem.attr('id'), function (data) {
- $elem.text(Number(data));
- });
- });
- }
- }
-
- /** AI 摘要(post.html 内联脚本,pjax 后触发) */
- function reinitAISummary() {
- if (typeof ai_gen === 'function' && $('#ai-output').length) {
- try { ai_gen(); } catch (e) { /* ignore */ }
- }
- }
-
- /** 代码块复制按钮 */
- function reinitCopyButtons() {
- $('.copy').remove();
- $('div.highlight').each(function () {
- var $block = $(this);
- var $btn = $('<button>', { class: 'copy', type: 'button', text: '📋' });
- $block.append($btn);
- $btn.on('click', function () {
- var code = $btn.siblings('pre').find('code').text().trim();
- navigator.clipboard.writeText(code)
- .then(function () { $btn.text('✅'); })
- .catch(function () { $btn.text('❌'); })
- .finally(function () { setTimeout(function () { $btn.text('📋'); }, 1500); });
- });
- });
- }
-
- /** Gitalk 评论(post 页面专属) */
- function reinitGitalk() {
- if ($(CONTAINER + ' #gitalk-container').length === 0) return;
- loadCSS('/assets/css/gitalk.css');
-
- function doInitGitalk() {
- if (typeof Gitalk === 'undefined') {
- loadScript('/assets/js/gitalk.min.js', doInitGitalk);
- return;
- }
- var pageId = $(CONTAINER + ' #gitalk-container').data('page-id') || window.location.pathname;
- try {
- new Gitalk(Object.assign({ id: pageId }, window.GitalkConfig))
- .render('gitalk-container');
- } catch (e) {
- console.warn('[pjax] Gitalk init error:', e);
- }
- }
- $('#gitalk-container').empty();
- doInitGitalk();
- }
-
- /** 关键词高亮 */
- function reinitHighlight() {
- var keyword = new URLSearchParams(window.location.search).get('kw');
- if (!keyword) return;
- keyword = keyword.trim();
- if (!keyword) return;
-
- var escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
- var regex = new RegExp('(' + escaped + ')', 'gi');
- var escapeHTML = function (str) {
- return str.replace(/[&<>"']/g, function (t) {
- return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[t] || t;
- });
- };
- function walk(node) {
- $(node).contents().each(function () {
- if (this.nodeType === Node.TEXT_NODE) {
- var $t = $(this);
- var text = escapeHTML($t.text());
- if (regex.test(text)) $t.replaceWith(text.replace(regex, '<mark>$1</mark>'));
- } else if (this.nodeType === Node.ELEMENT_NODE && !$(this).is('script, style, noscript, textarea')) {
- walk(this);
- }
- });
- }
- $('section').each(function () { walk(this); });
- }
-
- /** Google Analytics 页面浏览事件 */
- function trackPageView() {
- if (typeof gtag === 'function') {
- gtag('config', window._gaId || '', { page_path: window.location.pathname });
- }
- }
-
- /** Live2D 重初始化 */
- var _live2dSelectors = ['.post-link', '#search-input'];
- var _live2dDelegateBound = false;
-
- function reinitLive2d() {
- if (!window._live2d) return;
- var pathname = window.location.pathname;
-
- // 更新"想问这篇文章"相关状态(仅真正的文章页显示)
- $('#post_id').val(pathname);
- if (isRealPostPage()) {
- $('.live_talk_input_name_body').show();
- } else {
- $('.live_talk_input_name_body').hide();
- $('#load_this').prop('checked', false);
- }
-
- // 音乐按钮:根据当前页面是否有 BGM 输入来显示/隐藏
- if (typeof window._live2d.initBGM === 'function') {
- window._live2d.initBGM();
- }
-
- // 事件委托绑定(只执行一次)
- if (!_live2dDelegateBound && typeof String.prototype.renderTip === 'function') {
- var selector = CONTAINER + ' ' + _live2dSelectors.join(', ' + CONTAINER + ' ');
- $(document).on('mouseover._live2d_pjax', selector, function (e) {
- var $el = $(e.currentTarget || e.target);
- if ($el.is('.post-link')) {
- window._live2d.showMessage('要看看 ' + $el.text() + ' 么?', 3000);
- } else if ($el.is('#search-input')) {
- window._live2d.showMessage('在找什么东西呢,需要帮忙吗?', 3000);
- }
- });
- $(document).on('mouseout._live2d_pjax', selector, function () {
- if (window._live2d.showHitokoto) window._live2d.showHitokoto();
- });
- _live2dDelegateBound = true;
- }
-
- // 欢迎语
- if (typeof window._live2d.showMessage === 'function') {
- window._live2d.showMessage(getWelcomeText(pathname), 6000);
- }
- }
-
- // ========== PJAX 导航 ==========
-
- /** PJAX 完成后的统一处理 */
- function doPjaxComplete() {
- $('body').removeClass('pjax-loading');
- // 清理可能残留的浮层(如推荐文章 tooltip,hover 后点击跳转时 mouseleave 来不及触发)
- $('.content-tooltip').hide();
- onPjaxComplete();
- }
-
- /** 暴露给模板内 onclick/onchange 调用的导航函数 */
- window.go = function (url) {
- if (!url || url === '#') return;
- if (/^(https?:)?\/\//.test(url) || url.startsWith('mailto:')) {
- window.location.href = url;
- return;
- }
- $('body').addClass('pjax-loading');
- $.ajax({
- url: url,
- beforeSend: function (xhr) {
- xhr.setRequestHeader('X-PJAX', 'true');
- xhr.setRequestHeader('X-PJAX-Container', CONTAINER);
- },
- success: function (html) {
- try {
- var doc = (new DOMParser()).parseFromString(html, 'text/html');
- var fragment = doc.querySelector(CONTAINER);
- if (fragment) {
- // 用 adoptNode 搬运所有子节点(包括 script 元素),让浏览器自行处理脚本执行
- // 这能正确支持 type="module"、顶层 await 等,避免手动提取重建的坑
- $(CONTAINER).empty(); // jQuery 清理旧元素的事件和数据,避免内存泄漏
- var container = document.querySelector(CONTAINER);
- while (fragment.firstChild) {
- container.appendChild(document.adoptNode(fragment.firstChild));
- }
- document.title = doc.title;
- history.pushState({ url: url }, document.title, url);
- doPjaxComplete();
- } else {
- window.location.href = url;
- }
- } catch (e) {
- console.warn('[go] parse error, fallback:', e);
- window.location.href = url;
- }
- },
- error: function () { window.location.href = url; },
- timeout: PJAX_OPTS.timeout
- });
- };
-
- /** 暴露 getWelcomeText 供 message.js 首次加载时复用,避免欢迎语逻辑重复 */
- window._pjaxGetWelcomeText = getWelcomeText;
-
- // ========== 初始化 ==========
-
- /** 每次 pjax 完成后执行所有重初始化 */
- function onPjaxComplete() {
- reinitVisitors();
- reinitCopyButtons();
- reinitHighlight();
- reinitGitalk();
- reinitAISummary();
- reinitLive2d();
- trackPageView();
- window.scrollTo(0, 0);
- }
-
- $(document).ready(function () {
- // 排除列表:外链、锚点、静态资源、Live2D 目录
- var exclude = ':not([target="_blank"]):not([href^="http"]):not([href^="//"])' +
- ':not([href^="mailto"]):not([href^="#"])' +
- ':not([href$=".xml"]):not([href$=".json"]):not([href$=".tgz"]):not([href$=".zip"])' +
- ':not([href^="/Live2dHistoire"])';
- $(document).pjax('a' + exclude, PJAX_OPTS.container, PJAX_OPTS);
-
- $(document).on('pjax:send', function () {
- $('body').addClass('pjax-loading');
- });
- $(document).on('pjax:complete', doPjaxComplete);
- $(document).on('pjax:error', function (xhr, textStatus, error) {
- console.warn('[pjax] error, fallback:', error);
- });
-
- // 首次加载初始化
- reinitCopyButtons();
- });
-
- })(jQuery);
|