diff options
Diffstat (limited to 'assets/js')
| -rw-r--r-- | assets/js/jquery.pjax.min.js | 6 | ||||
| -rw-r--r-- | assets/js/main.js | 8 | ||||
| -rw-r--r-- | assets/js/pjax.js | 354 |
3 files changed, 367 insertions, 1 deletions
diff --git a/assets/js/jquery.pjax.min.js b/assets/js/jquery.pjax.min.js new file mode 100644 index 0000000..a5ecc8c --- /dev/null +++ b/assets/js/jquery.pjax.min.js @@ -0,0 +1,6 @@ +/*! + * Copyright 2012, Chris Wanstrath + * Released under the MIT License + * https://github.com/defunkt/jquery-pjax + */ +!function(t){function e(e,a,r){return r=g(a,r),this.on("click.pjax",e,(function(e){var a=r;a.container||((a=t.extend({},r)).container=t(this).attr("data-pjax")),n(e,a)}))}function n(e,n,a){a=g(n,a);var i=e.currentTarget,o=t(i);if("A"!==i.tagName.toUpperCase())throw"$.fn.pjax or $.pjax.click requires an anchor element";if(!(e.which>1||e.metaKey||e.ctrlKey||e.shiftKey||e.altKey||location.protocol!==i.protocol||location.hostname!==i.hostname||i.href.indexOf("#")>-1&&x(i)==x(location)||e.isDefaultPrevented())){var s={url:i.href,container:o.attr("data-pjax"),target:i},c=t.extend({},s,a),u=t.Event("pjax:click");o.trigger(u,[c]),u.isDefaultPrevented()||(r(c),e.preventDefault(),o.trigger("pjax:clicked",[c]))}}function a(e,n,a){a=g(n,a);var i=e.currentTarget,o=t(i);if("FORM"!==i.tagName.toUpperCase())throw"$.pjax.submit requires a form element";var s={type:(o.attr("method")||"GET").toUpperCase(),url:o.attr("action"),container:o.attr("data-pjax"),target:i};if("GET"!==s.type&&void 0!==window.FormData)s.data=new FormData(i),s.processData=!1,s.contentType=!1;else{if(o.find(":file").length)return;s.data=o.serializeArray()}r(t.extend({},s,a)),e.preventDefault()}function r(e){e=t.extend(!0,{},t.ajaxSettings,r.defaults,e),t.isFunction(e.url)&&(e.url=e.url());var n=v(e.url).hash,a=t.type(e.container);if("string"!==a)throw"expected string value for 'container' option; got "+a;var i,s=e.context=t(e.container);if(!s.length)throw"the container selector '"+e.container+"' did not match anything";function c(n,a,r){r||(r={}),r.relatedTarget=e.target;var i=t.Event(n,r);return s.trigger(i,a),!i.isDefaultPrevented()}e.data||(e.data={}),t.isArray(e.data)?e.data.push({name:"_pjax",value:e.container}):e.data._pjax=e.container,e.beforeSend=function(t,a){if("GET"!==a.type&&(a.timeout=0),t.setRequestHeader("X-PJAX","true"),t.setRequestHeader("X-PJAX-Container",e.container),!c("pjax:beforeSend",[t,a]))return!1;a.timeout>0&&(i=setTimeout((function(){c("pjax:timeout",[t,e])&&t.abort("timeout")}),a.timeout),a.timeout=0);var r=v(a.url);n&&(r.hash=n),e.requestUrl=m(r)},e.complete=function(t,n){i&&clearTimeout(i),c("pjax:complete",[t,n,e]),c("pjax:end",[t,e])},e.error=function(t,n,a){var r=w("",t,e),i=c("pjax:error",[t,n,a,e]);"GET"==e.type&&"abort"!==n&&i&&o(r.url)},e.success=function(a,i,u){var l=r.state,p="function"==typeof t.pjax.defaults.version?t.pjax.defaults.version():t.pjax.defaults.version,d=u.getResponseHeader("X-PJAX-Version"),h=w(a,u,e),m=v(h.url);if(n&&(m.hash=n,h.url=m.href),p&&d&&p!==d)o(h.url);else if(h.contents){if(r.state={id:e.id||f(),url:h.url,title:h.title,container:e.container,fragment:e.fragment,timeout:e.timeout},(e.push||e.replace)&&window.history.replaceState(r.state,h.title,h.url),t.contains(s,document.activeElement))try{document.activeElement.blur()}catch(t){}h.title&&(document.title=h.title),c("pjax:beforeReplace",[h.contents,e],{state:r.state,previousState:l}),s.html(h.contents);var x=s.find("input[autofocus], textarea[autofocus]").last()[0];x&&document.activeElement!==x&&x.focus(),function(e){if(!e)return;var n=t("script[src]");e.each((function(){var e=this.src;if(!n.filter((function(){return this.src===e})).length){var a=document.createElement("script"),r=t(this).attr("type");r&&(a.type=r),a.src=t(this).attr("src"),document.head.appendChild(a)}}))}(h.scripts);var g=e.scrollTo;if(n){var y=decodeURIComponent(n.slice(1)),j=document.getElementById(y)||document.getElementsByName(y)[0];j&&(g=t(j).offset().top)}"number"==typeof g&&t(window).scrollTop(g),c("pjax:success",[a,i,u,e])}else o(h.url)},r.state||(r.state={id:f(),url:window.location.href,title:document.title,container:e.container,fragment:e.fragment,timeout:e.timeout},window.history.replaceState(r.state,document.title)),d(r.xhr),r.options=e;var u,l,p=r.xhr=t.ajax(e);return p.readyState>0&&(e.push&&!e.replace&&(u=r.state.id,l=[e.container,h(s)],b[u]=l,E.push(u),S(T,0),S(E,r.defaults.maxCacheLength),window.history.pushState(null,"",e.requestUrl)),c("pjax:start",[p,e]),c("pjax:send",[p,e])),r.xhr}function i(e,n){var a={url:window.location.href,push:!1,replace:!0,scrollTo:!1};return r(t.extend(a,g(e,n)))}function o(t){window.history.replaceState(null,"",r.state.url),window.location.replace(t)}var s=!0,c=window.location.href,u=window.history.state;function l(e){s||d(r.xhr);var n,a=r.state,i=e.state;if(i&&i.container){if(s&&c==i.url)return;if(a){if(a.id===i.id)return;n=a.id<i.id?"forward":"back"}var u=b[i.id]||[],l=u[0]||i.container,p=t(l),f=u[1];if(p.length){a&&function(t,e,n){var a,i;b[e]=n,"forward"===t?(a=E,i=T):(a=T,i=E);a.push(e),(e=i.pop())&&delete b[e];S(a,r.defaults.maxCacheLength)}(n,a.id,[l,h(p)]);var m=t.Event("pjax:popstate",{state:i,direction:n});p.trigger(m);var v={id:i.id,url:i.url,container:l,push:!1,fragment:i.fragment,timeout:i.timeout,scrollTo:!1};if(f){p.trigger("pjax:start",[null,v]),r.state=i,i.title&&(document.title=i.title);var x=t.Event("pjax:beforeReplace",{state:i,previousState:a});p.trigger(x,[f,v]),p.html(f),p.trigger("pjax:end",[null,v])}else r(v);p[0].offsetHeight}else o(location.href)}s=!1}function p(e){var n=t.isFunction(e.url)?e.url():e.url,a=e.type?e.type.toUpperCase():"GET",r=t("<form>",{method:"GET"===a?"GET":"POST",action:n,style:"display:none"});"GET"!==a&&"POST"!==a&&r.append(t("<input>",{type:"hidden",name:"_method",value:a.toLowerCase()}));var i=e.data;if("string"==typeof i)t.each(i.split("&"),(function(e,n){var a=n.split("=");r.append(t("<input>",{type:"hidden",name:a[0],value:a[1]}))}));else if(t.isArray(i))t.each(i,(function(e,n){r.append(t("<input>",{type:"hidden",name:n.name,value:n.value}))}));else if("object"==typeof i){var o;for(o in i)r.append(t("<input>",{type:"hidden",name:o,value:i[o]}))}t(document.body).append(r),r.submit()}function d(e){e&&e.readyState<4&&(e.onreadystatechange=t.noop,e.abort())}function f(){return(new Date).getTime()}function h(e){var n=e.clone();return n.find("script").each((function(){this.src||t._data(this,"globalEval",!1)})),n.contents()}function m(t){return t.search=t.search.replace(/([?&])(_pjax|_)=[^&]*/g,"").replace(/^&/,""),t.href.replace(/\?($|#)/,"$1")}function v(t){var e=document.createElement("a");return e.href=t,e}function x(t){return t.href.replace(/#.*/,"")}function g(e,n){return e&&n?((n=t.extend({},n)).container=e,n):t.isPlainObject(e)?e:{container:e}}function y(t,e){return t.filter(e).add(t.find(e))}function j(e){return t.parseHTML(e,document,!0)}function w(e,n,a){var r,i,o={},s=/<html/i.test(e),c=n.getResponseHeader("X-PJAX-URL");if(o.url=c?m(v(c)):a.requestUrl,s){i=t(j(e.match(/<body[^>]*>([\s\S.]*)<\/body>/i)[0]));var u=e.match(/<head[^>]*>([\s\S.]*)<\/head>/i);r=null!=u?t(j(u[0])):i}else r=i=t(j(e));if(0===i.length)return o;if(o.title=y(r,"title").last().text(),a.fragment){var l=i;"body"!==a.fragment&&(l=y(l,a.fragment).first()),l.length&&(o.contents="body"===a.fragment?l:l.contents(),o.title||(o.title=l.attr("title")||l.data("title")))}else s||(o.contents=i);return o.contents&&(o.contents=o.contents.not((function(){return t(this).is("title")})),o.contents.find("title").remove(),o.scripts=y(o.contents,"script[src]").remove(),o.contents=o.contents.not(o.scripts)),o.title&&(o.title=t.trim(o.title)),o}u&&u.container&&(r.state=u),"state"in window.history&&(s=!1);var b={},T=[],E=[];function S(t,e){for(;t.length>e;)delete b[t.shift()]}function P(){return t("meta").filter((function(){var e=t(this).attr("http-equiv");return e&&"X-PJAX-VERSION"===e.toUpperCase()})).attr("content")}function C(){t.fn.pjax=e,t.pjax=r,t.pjax.enable=t.noop,t.pjax.disable=A,t.pjax.click=n,t.pjax.submit=a,t.pjax.reload=i,t.pjax.defaults={timeout:650,push:!0,replace:!1,type:"GET",dataType:"html",scrollTo:0,maxCacheLength:20,version:P},t(window).on("popstate.pjax",l)}function A(){t.fn.pjax=function(){return this},t.pjax=p,t.pjax.enable=C,t.pjax.disable=t.noop,t.pjax.click=t.noop,t.pjax.submit=t.noop,t.pjax.reload=function(){window.location.reload()},t(window).off("popstate.pjax",l)}t.event.props&&t.inArray("state",t.event.props)<0?t.event.props.push("state"):"state"in t.Event.prototype||t.event.addProp("state"),t.support.pjax=window.history&&window.history.pushState&&window.history.replaceState&&!navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]\D|WebApps\/.+CFNetwork)/),t.support.pjax?C():A()}(jQuery); diff --git a/assets/js/main.js b/assets/js/main.js index 556a150..cb11f6e 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -39,7 +39,7 @@ $(function () { "-webkit-filter": "grayscale(100%)", "filter": "progid:DXImageTransform.Microsoft.BasicImage(grayscale=1)" }) - $('body').html(function(_, oldHTML) { + $('body').html(function (_, oldHTML) { return oldHTML.replace(/Mayx/g, 'Ghost'); }); console.warn("Mayx may already be Dead"); @@ -66,4 +66,10 @@ function getSearchJSON(callback) { } else { callback(searchData); } +} +if (typeof window.go === 'undefined') { + window.go = function (url) { + window.location.href = url; + return; + } }
\ No newline at end of file diff --git a/assets/js/pjax.js b/assets/js/pjax.js new file mode 100644 index 0000000..1f663f3 --- /dev/null +++ b/assets/js/pjax.js @@ -0,0 +1,354 @@ +/** + * 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 = {}; + var _pendingScripts = []; + + /** 动态加载外部 CSS(避免重复加载) */ + function loadCSS(href) { + if ($('link[href="' + href + '"]').length) return; + $('<link rel="stylesheet" href="' + href + '" />').appendTo('head'); + } + + /** + * 动态加载外部 JS(避免重复) + * 用对象跟踪已加载的 URL,而不是检查 DOM 中的 <script> 标签 + * (pjax 替换容器内容后,惰性 <script> 标签存在但不代表已执行) + */ + 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 executeScripts(scripts) { + var idx = 0; + function runNext() { + while (idx < scripts.length) { + var s = scripts[idx]; + idx++; + if (s.src) { + loadScript(s.src, runNext); + return; // 等待 onload 回调 + } + try { + (window.execScript || function (code) { + window['eval'].call(window, code); + })(s.text); + } catch (e) { + console.warn('[pjax] inline script exec error:', e); + } + } + } + runNext(); + } + + // ========== 页面类型判断 ========== + + /** 是否为文章页(非首页/分页) */ + 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 后由 executeScripts 触发) */ + 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'); + // go() 路径:脚本在 DOM 替换前提取到了 _pendingScripts,需在此执行 + // pjax 库路径:_pendingScripts 为空,pjax 库自行处理了脚本执行 + if (_pendingScripts.length > 0) { + executeScripts(_pendingScripts); + _pendingScripts = []; + } + 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) { + // 先提取脚本(jQuery html() 会移除并可能异步处理脚本) + _pendingScripts = []; + fragment.querySelectorAll('script').forEach(function (s) { + _pendingScripts.push({ + src: s.src || null, + text: s.textContent + }); + s.remove(); + }); + $(CONTAINER).html(fragment.innerHTML); + 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); |
