更新 renovate.json
··[CST 2026-04-01 Wednesday 21:27:38]
@@ -0,0 +1 @@
|
||||
C5EC3CAE4FD14AD69AECF56219B47A0A
|
||||
@@ -0,0 +1,441 @@
|
||||
<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0,viewport-fit=cover"><title>版权协议 | Bi's Blog</title><meta name="author" content="biss"><meta name="copyright" content="biss"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="为了保持文章质量,并保持互联网的开放共享精神,保持页面流量的稳定,综合考虑下本站的所有原创文章均采用cc协议中比较严格的署名-非商业性使用-禁止演绎 4.0 国际标准。这篇文章主要想能够更加清楚明白的介绍本站的协议标准和要求。方便你合理的使用本站的文章 本站无广告嵌入和商业行为。违反协议的行为不仅会损害原作者的创作热情,而且会影响整个版权环境。强烈呼吁你能够在转载时遵守协议。遵守协议的行为几乎">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="版权协议">
|
||||
<meta property="og:url" content="https://blog.biss.click/cc/index.html">
|
||||
<meta property="og:site_name" content="Bi's Blog">
|
||||
<meta property="og:description" content="为了保持文章质量,并保持互联网的开放共享精神,保持页面流量的稳定,综合考虑下本站的所有原创文章均采用cc协议中比较严格的署名-非商业性使用-禁止演绎 4.0 国际标准。这篇文章主要想能够更加清楚明白的介绍本站的协议标准和要求。方便你合理的使用本站的文章 本站无广告嵌入和商业行为。违反协议的行为不仅会损害原作者的创作热情,而且会影响整个版权环境。强烈呼吁你能够在转载时遵守协议。遵守协议的行为几乎">
|
||||
<meta property="og:locale" content="zh_CN">
|
||||
<meta property="og:image" content="https://free.picui.cn/free/2025/08/10/689845496a283.png">
|
||||
<meta property="article:published_time" content="2025-08-10T05:27:27.000Z">
|
||||
<meta property="article:modified_time" content="2026-04-01T13:26:48.910Z">
|
||||
<meta property="article:author" content="biss">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://free.picui.cn/free/2025/08/10/689845496a283.png"><script type="application/ld+json"></script><link rel="shortcut icon" href="/images/Bi.ico"><link rel="canonical" href="https://blog.biss.click/cc/index.html"><link rel="preconnect" href="//unpkg.com"/><link rel="preconnect" href="//busuanzi.ibruce.info"/><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://unpkg.com/@fortawesome/fontawesome-free/css/all.min.css"><link rel="stylesheet" href="https://unpkg.com/node-snackbar/dist/snackbar.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://unpkg.com/@fancyapps/ui/dist/fancybox/fancybox.css" media="print" onload="this.media='all'"><script>
|
||||
(() => {
|
||||
|
||||
const saveToLocal = {
|
||||
set: (key, value, ttl) => {
|
||||
if (!ttl) return
|
||||
const expiry = Date.now() + ttl * 86400000
|
||||
localStorage.setItem(key, JSON.stringify({ value, expiry }))
|
||||
},
|
||||
get: key => {
|
||||
const itemStr = localStorage.getItem(key)
|
||||
if (!itemStr) return undefined
|
||||
const { value, expiry } = JSON.parse(itemStr)
|
||||
if (Date.now() > expiry) {
|
||||
localStorage.removeItem(key)
|
||||
return undefined
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
window.btf = {
|
||||
saveToLocal,
|
||||
getScript: (url, attr = {}) => new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script')
|
||||
script.src = url
|
||||
script.async = true
|
||||
Object.entries(attr).forEach(([key, val]) => script.setAttribute(key, val))
|
||||
script.onload = script.onreadystatechange = () => {
|
||||
if (!script.readyState || /loaded|complete/.test(script.readyState)) resolve()
|
||||
}
|
||||
script.onerror = reject
|
||||
document.head.appendChild(script)
|
||||
}),
|
||||
getCSS: (url, id) => new Promise((resolve, reject) => {
|
||||
const link = document.createElement('link')
|
||||
link.rel = 'stylesheet'
|
||||
link.href = url
|
||||
if (id) link.id = id
|
||||
link.onload = link.onreadystatechange = () => {
|
||||
if (!link.readyState || /loaded|complete/.test(link.readyState)) resolve()
|
||||
}
|
||||
link.onerror = reject
|
||||
document.head.appendChild(link)
|
||||
}),
|
||||
addGlobalFn: (key, fn, name = false, parent = window) => {
|
||||
if (!true && key.startsWith('pjax')) return
|
||||
const globalFn = parent.globalFn || {}
|
||||
globalFn[key] = globalFn[key] || {}
|
||||
globalFn[key][name || Object.keys(globalFn[key]).length] = fn
|
||||
parent.globalFn = globalFn
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const activateDarkMode = () => {
|
||||
document.documentElement.setAttribute('data-theme', 'dark')
|
||||
if (document.querySelector('meta[name="theme-color"]') !== null) {
|
||||
document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
|
||||
}
|
||||
}
|
||||
const activateLightMode = () => {
|
||||
document.documentElement.setAttribute('data-theme', 'light')
|
||||
if (document.querySelector('meta[name="theme-color"]') !== null) {
|
||||
document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
|
||||
}
|
||||
}
|
||||
|
||||
btf.activateDarkMode = activateDarkMode
|
||||
btf.activateLightMode = activateLightMode
|
||||
|
||||
const theme = saveToLocal.get('theme')
|
||||
|
||||
theme === 'dark' ? activateDarkMode() : theme === 'light' ? activateLightMode() : null
|
||||
|
||||
|
||||
const asideStatus = saveToLocal.get('aside-status')
|
||||
if (asideStatus !== undefined) {
|
||||
document.documentElement.classList.toggle('hide-aside', asideStatus === 'hide')
|
||||
}
|
||||
|
||||
|
||||
const detectApple = () => {
|
||||
if (/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)) {
|
||||
document.documentElement.classList.add('apple')
|
||||
}
|
||||
}
|
||||
detectApple()
|
||||
|
||||
})()
|
||||
</script><script>const GLOBAL_CONFIG = {
|
||||
root: '/',
|
||||
algolia: undefined,
|
||||
localSearch: undefined,
|
||||
translate: {"defaultEncoding":2,"translateDelay":0,"msgToTraditionalChinese":"繁","msgToSimplifiedChinese":"簡"},
|
||||
highlight: {"plugin":"highlight.js","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":200,"highlightFullpage":true,"highlightMacStyle":false},
|
||||
copy: {
|
||||
success: '复制成功',
|
||||
error: '复制失败',
|
||||
noSupport: '浏览器不支持'
|
||||
},
|
||||
relativeDate: {
|
||||
homepage: false,
|
||||
post: false
|
||||
},
|
||||
runtime: '',
|
||||
dateSuffix: {
|
||||
just: '刚刚',
|
||||
min: '分钟前',
|
||||
hour: '小时前',
|
||||
day: '天前',
|
||||
month: '个月前'
|
||||
},
|
||||
copyright: undefined,
|
||||
lightbox: 'fancybox',
|
||||
Snackbar: {"chs_to_cht":"已切换为繁体中文","cht_to_chs":"已切换为简体中文","day_to_night":"已切换为深色模式","night_to_day":"已切换为浅色模式","bgLight":"#49b1f5","bgDark":"#1f1f1f","position":"bottom-left"},
|
||||
infinitegrid: {
|
||||
js: 'https://unpkg.com/@egjs/infinitegrid/dist/infinitegrid.min.js',
|
||||
buttonText: '加载更多'
|
||||
},
|
||||
isPhotoFigcaption: false,
|
||||
islazyloadPlugin: false,
|
||||
isAnchor: false,
|
||||
percent: {
|
||||
toc: true,
|
||||
rightside: false,
|
||||
},
|
||||
autoDarkmode: false
|
||||
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
|
||||
title: '版权协议',
|
||||
isHighlightShrink: false,
|
||||
isToc: false,
|
||||
pageType: 'page'
|
||||
}</script><link rel="stylesheet" href="/css/shuoshuo.css"><link rel="stylesheet" href="/css/shuoshuoshouye.css"><link rel="stylesheet" href="/css/nav.css"><link rel="stylesheet" href="/css/style.css"><link rel="stylesheet" href="/css/poem.css"><link rel="stylesheet" href="/css/swiper.css"><link rel="stylesheet" href="https://cdn.jsdmirror.com/npm/instantsearch.css/themes/reset-min.css"><link rel="stylesheet" href="https://cdn.jsdmirror.com/gh/bishshi/welcomemessage/welcome.css"><link rel="stylesheet" href="https://cdn.jsdmirror.com/gh/bishshi/sidecalendar/calendar.css"><link rel="stylesheet" href="https://cdn.jsdmirror.com/gh/bishshi/rightmenu/rightmenu.css"><link rel="stylesheet" href="https://cdn.jsdmirror.com/gh/bishshi/webfont/font.css"><link rel="stylesheet" href="https://cdn.jsdmirror.com/npm/aplayer/dist/APlayer.min.css" media="all" onload="this.media="all""><!-- hexo injector head_end start --><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/5.4.5/css/swiper.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn1.tianli0.top/npm/hexo-butterfly-swiper/lib/swiperstyle.css" media="print" onload="this.media='all'"><!-- hexo injector head_end end --><style type="text/css">
|
||||
.spoiler {
|
||||
display: inline;
|
||||
}
|
||||
p.spoiler {
|
||||
display: flex;
|
||||
}
|
||||
.spoiler a {
|
||||
pointer-events: none;
|
||||
}
|
||||
.spoiler-blur, .spoiler-blur > * {
|
||||
transition: text-shadow .5s ease;
|
||||
}
|
||||
.spoiler .spoiler-blur, .spoiler .spoiler-blur > * {
|
||||
color: rgba(0, 0, 0, 0);
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
text-shadow: 0 0 10px grey;
|
||||
cursor: pointer;
|
||||
}
|
||||
.spoiler .spoiler-blur:hover, .spoiler .spoiler-blur:hover > * {
|
||||
text-shadow: 0 0 5px grey;
|
||||
}
|
||||
.spoiler-box, .spoiler-box > * {
|
||||
transition: color .5s ease,
|
||||
background-color .5s ease;
|
||||
}
|
||||
.spoiler .spoiler-box, .spoiler .spoiler-box > * {
|
||||
color: black;
|
||||
background-color: black;
|
||||
text-shadow: none;
|
||||
}</style><meta name="generator" content="Hexo 8.1.1"><link rel="alternate" href="/atom.xml" title="Bi's Blog" type="application/atom+xml">
|
||||
</head><body><div class="bg-animation" id="web_bg" style="background-image: url(/images/background.png);"></div><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="avatar-img text-center"><img src="https://free.picui.cn/free/2025/08/10/689845496a283.png" onerror="this.onerror=null;this.src='/img/friend_404.gif'" alt="avatar"/></div><div class="site-data text-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">35</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">11</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">5</div></a></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 存档</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></div><div class="menus_item"><a class="site-page" href="/shuoshuo/"><i class="fa-fw fa-regular fa-comment"></i><span> 说说</span></a></div><div class="menus_item"><a class="site-page" href="/link/"><i class="fa-fw fas fa-link"></i><span> 友链</span></a></div><div class="menus_item"><a class="site-page" href="/about/"><i class="fa-fw fas fa-heart"></i><span> 关于</span></a></div></div></div></div><div class="page" id="body-wrap"><header class="not-top-img fixed" id="page-header"><nav id="nav"><span id="blog-info"><div id="ls-menu-container"><i class="fas fa-fingerprint"></i><div id="ls-menu-panel"><div class="ls-section"><div class="ls-title">😀 个人网站</div><div class="ls-grid"><a href="/"><i class="fas fa-rss"></i> 个人博客</a><a target="_blank" rel="noopener" href="https://github.com/bishshi"><i class="fab fa-github"></i> Github</a></div></div><div class="ls-section"><div class="ls-title">😎 常用服务</div><div class="ls-grid"><a target="_blank" rel="noopener" href="https://git.biss.click/biss"><i class="fas fa-code"></i> 代码仓库</a><a target="_blank" rel="noopener" href="https://mm.biss.click"><i class="fas fa-pen-nib"></i> 日常yy</a><a target="_blank" rel="noopener" href="https://statistic.biss.click/"><i class="fas fa-users"></i> 访客统计</a><a target="_blank" rel="noopener" href="https://pic.biss.click"><i class="fas fa-image"></i> 图床</a><a target="_blank" rel="noopener" href="https://git.biss.click"><i class="fas fa-code-branch"></i> 代码仓库</a></div></div><div class="ls-section"><div class="ls-title">🛸 实用工具</div><div class="ls-grid"><a target="_blank" rel="noopener" href="https://cover.biss.click"><i class="fas fa-palette"></i> 封面设计</a><a target="_blank" rel="noopener" href="https://doc.biss.click"><i class="fas fa-file"></i> 文档服务</a><a target="_blank" rel="noopener" href="https://doc.biss.click"><i class="fas fa-server"></i>服务监测</a><a target="_blank" rel="noopener" href="https://typesense.biss.click"><i class="fas fa-magnifying-glass"></i> 搜索后端</a></div></div></div></div><a class="nav-site-title" href="/"><span class="site-name">Bi's Blog</span></a></span><div id="nav-right"><div id="menus"><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 存档</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></div><div class="menus_item"><a class="site-page" href="/shuoshuo/"><i class="fa-fw fa-regular fa-comment"></i><span> 说说</span></a></div><div class="menus_item"><a class="site-page" href="/link/"><i class="fa-fw fas fa-link"></i><span> 友链</span></a></div><div class="menus_item"><a class="site-page" href="/about/"><i class="fa-fw fas fa-heart"></i><span> 关于</span></a></div></div></div><div id="random-post-button"><a class="site-page social-icon" id="random-post-link" href="javascript:void(0);" onclick="randomPost()"><i class="fas fa-solid fa-shuffle"></i></a></div><div id="search-button"><a class="site-page social-icon search-typesense-trigger"><i class="fas fa-search fa-fw"></i></a></div><div id="toggle-menu"><span class="site-page"><i class="fas fa-bars fa-fw"></i></span></div></div></nav><h1 class="title-seo">版权协议</h1></header><main class="layout hide-aside" id="content-inner"><div id="page"><div class="page-title">版权协议</div><div class="container" id="article-container"><!-- 页面内容 -->
|
||||
|
||||
<p>为了保持文章质量,并保持互联网的开放共享精神,保持页面流量的稳定,综合考虑下本站的所有原创文章均采用cc协议中比较严格的<a target="_blank" rel="noopener" href="https://creativecommons.org/licenses/by-nc-nd/4.0/deed.zh">署名-非商业性使用-禁止演绎 4.0 国际</a>标准。这篇文章主要想能够更加清楚明白的介绍本站的协议标准和要求。方便你合理的使用本站的文章</p>
|
||||
<p>本站无广告嵌入和商业行为。违反协议的行为不仅会损害原作者的创作热情,而且会影响整个版权环境。强烈呼吁你能够在转载时遵守协议。遵守协议的行为几乎不会对你的目标产生负面影响,鼓励创作环境是每个创作者的期望</p>
|
||||
<h1 id="哪些文章适于本协议?"><a href="#哪些文章适于本协议?" class="headerlink" title="哪些文章适于本协议?"></a>哪些文章适于本协议?</h1><p>所有原创内容均在文章标题顶部,以及文章结尾的版权说明部分展示</p>
|
||||
<p>原创内容的非商用转载必须为完整转载且标注出处的 <code>带有完整url链接</code> 或 <code>访问原文</code> 之类字样的超链接</p>
|
||||
<p>作为参考资料的情况可以无需完整转载,摘录所需要的部分内容即可,但需标注出处</p>
|
||||
<h1 id="你可以做什么?"><a href="#你可以做什么?" class="headerlink" title="你可以做什么?"></a>你可以做什么?</h1><p>只要你遵守本页的许可,你可以自由地共享文章的内容 — 在任何媒介以任何形式复制、发行本作品。并且无需通知作者</p>
|
||||
<h1 id="你需要遵守什么样的许可?"><a href="#你需要遵守什么样的许可?" class="headerlink" title="你需要遵守什么样的许可?"></a>你需要遵守什么样的许可?</h1><h3 id="署名"><a href="#署名" class="headerlink" title="署名"></a>署名</h3><p>你必须标注内容的来源,你需要在文章开头部分(或者明显位置)标注原文章链接(建议使用超链接提升阅读体验)</p>
|
||||
<h3 id="禁止商用"><a href="#禁止商用" class="headerlink" title="禁止商用"></a>禁止商用</h3><p>本站内容免费向互联网所有用户提供,分享本站文章时禁止商业性使用、禁止在转载页面中插入广告(例如谷歌广告、百度广告)、禁止阅读的拦截行为(例如关注公众号、下载App后观看文章)</p>
|
||||
<h3 id="禁止演绎"><a href="#禁止演绎" class="headerlink" title="禁止演绎"></a>禁止演绎</h3><ul>
|
||||
<li>分享全部内容(无修改)<br>你需要在文章开头部分(或者明显位置)标注原文章链接(建议使用超链接)</li>
|
||||
<li>分享部分截取内容或者衍生创作<br>目前本站全部原创文章的衍生品禁止公开分享和分发。如有更好的修改建议,可以在对应文章下留言。如有衍生创作需求,可以在评论中联系。</li>
|
||||
<li>作为参考资料截取部分内容<br>作为参考资料的情况可以无需完整转载,摘录所需要的部分内容即可,但需标注出处。</li>
|
||||
</ul>
|
||||
<h1 id="什么内容会被版权保护"><a href="#什么内容会被版权保护" class="headerlink" title="什么内容会被版权保护"></a>什么内容会被版权保护</h1><p><strong>包括但不限于:</strong></p>
|
||||
<ul>
|
||||
<li>文章封面图片</li>
|
||||
<li>文章标题和正文</li>
|
||||
<li>站点图片素材(不含主题自带素材)</li>
|
||||
</ul>
|
||||
<h1 id="例外情况"><a href="#例外情况" class="headerlink" title="例外情况"></a>例外情况</h1><p>本着友好互相进步的原则,被本站友链收录的博客允许博客文章内容的衍生品的分享和分发,但仍需标注出处</p>
|
||||
<p>本着互联网开放精神,你可以在博客文章下方留言要求授权博文的衍生品的分享和分发,标注你的网站地址</p>
|
||||
<p>关于主题样式的版权信息,可以详见<a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly/">Butterfly</a>主题说明,本站已经历多次版本迭代</p>
|
||||
<h1 id="网站源代码协议"><a href="#网站源代码协议" class="headerlink" title="网站源代码协议"></a>网站源代码协议</h1><p>网站源代码(仅包含css、js)的代码部分采用GPL协议</p>
|
||||
</div></div></main><footer id="footer"><div class="footer-other"><div class="footer-copyright"><span class="copyright">© 2025 - 2026 By biss</span><span class="framework-info"><span>框架 </span><a target="_blank" rel="noopener" href="https://hexo.io">Hexo</a><span class="footer-separator">|</span><span>主题 </span><a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a></span></div><div class="footer_custom_text"><p> <a style="margin-inline:5px"target="_blank" href="https://hexo.io/zh-cn/"><img src="https://img.shields.io/badge/Frame-Hexo-blue?style=flat&logo=hexo" title="hexo 8.1"></a> <a style="margin-inline:5px"target="_blank" href="https://butterfly.js.org"><img src="https://img.shields.io/badge/Theme-Butterfly-pink?style=flat" title="butterfly主题"></a> <a style="margin-inline:5px"target="_blank" href="https://creativecommons.org/licenses/by-nc-sa/4.0/"><img src="https://img.shields.io/badge/Copyright-BY--NC--SA-red?style=flat&logo=alchemy" title="CC BY-NC-SA 4.0"></a> <a href="https://www.trustssl.cc/ipv6.php?domain=blog.biss.click" title="本站已支持IPv6访问" target="_blank"><img src="https://static.coolcdn.cn/images/ipv6.svg"/></a> </p></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="translateLink" type="button" title="简繁转换">繁</button><button id="darkmode" type="button" title="日间和夜间模式切换"><i class="fas fa-adjust"></i></button></div><div id="rightside-config-show"><button id="rightside-config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button id="go-up" type="button" title="回到顶部"><span class="scroll-percent"></span><i class="fas fa-arrow-up"></i></button></div></div><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><script src="/js/tw_cn.js"></script><script src="https://unpkg.com/@fancyapps/ui/dist/fancybox/fancybox.umd.js"></script><script src="https://unpkg.com/node-snackbar/dist/snackbar.min.js"></script><div class="js-pjax"><script>(() => {
|
||||
const loadMathjax = () => {
|
||||
if (!window.MathJax) {
|
||||
window.MathJax = {
|
||||
loader: {
|
||||
load: [
|
||||
// Four font extension packages (optional)
|
||||
//- '[tex]/bbm',
|
||||
//- '[tex]/bboldx',
|
||||
//- '[tex]/dsfont',
|
||||
'[tex]/mhchem'
|
||||
],
|
||||
paths: {
|
||||
'mathjax-newcm': '[mathjax]/../@mathjax/mathjax-newcm-font',
|
||||
|
||||
//- // Four font extension packages (optional)
|
||||
//- 'mathjax-bbm-extension': '[mathjax]/../@mathjax/mathjax-bbm-font-extension',
|
||||
//- 'mathjax-bboldx-extension': '[mathjax]/../@mathjax/mathjax-bboldx-font-extension',
|
||||
//- 'mathjax-dsfont-extension': '[mathjax]/../@mathjax/mathjax-dsfont-font-extension',
|
||||
'mathjax-mhchem-extension': '[mathjax]/../@mathjax/mathjax-mhchem-font-extension'
|
||||
}
|
||||
},
|
||||
output: {
|
||||
font: 'mathjax-newcm',
|
||||
},
|
||||
tex: {
|
||||
inlineMath: [['$', '$'], ['\\(', '\\)']],
|
||||
tags: 'none',
|
||||
packages: {
|
||||
'[+]': [
|
||||
'mhchem'
|
||||
]
|
||||
}
|
||||
},
|
||||
chtml: {
|
||||
scale: 1.1
|
||||
},
|
||||
options: {
|
||||
enableMenu: true,
|
||||
menuOptions: {
|
||||
settings: {
|
||||
enrich: false // Turn off Braille and voice narration text automatic generation
|
||||
}
|
||||
},
|
||||
renderActions: {
|
||||
findScript: [10, doc => {
|
||||
for (const node of document.querySelectorAll('script[type^="math/tex"]')) {
|
||||
const display = !!node.type.match(/; *mode=display/)
|
||||
const math = new doc.options.MathItem(node.textContent, doc.inputJax[0], display)
|
||||
const text = document.createTextNode('')
|
||||
node.parentNode.replaceChild(text, node)
|
||||
math.start = {node: text, delim: '', n: 0}
|
||||
math.end = {node: text, delim: '', n: 0}
|
||||
doc.math.push(math)
|
||||
}
|
||||
}, '']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const script = document.createElement('script')
|
||||
script.src = 'https://unpkg.com/mathjax/tex-mml-chtml.js'
|
||||
script.id = 'MathJax-script'
|
||||
script.async = true
|
||||
document.head.appendChild(script)
|
||||
} else {
|
||||
MathJax.startup.document.state(0)
|
||||
MathJax.texReset()
|
||||
MathJax.typesetPromise()
|
||||
}
|
||||
}
|
||||
|
||||
btf.addGlobalFn('encrypt', loadMathjax, 'mathjax')
|
||||
window.pjax ? loadMathjax() : window.addEventListener('load', loadMathjax)
|
||||
})()</script><script>(() => {
|
||||
const applyThemeDefaultsConfig = theme => {
|
||||
if (theme === 'dark-mode') {
|
||||
Chart.defaults.color = "rgba(255, 255, 255, 0.8)"
|
||||
Chart.defaults.borderColor = "rgba(255, 255, 255, 0.2)"
|
||||
Chart.defaults.scale.ticks.backdropColor = "transparent"
|
||||
} else {
|
||||
Chart.defaults.color = "rgba(0, 0, 0, 0.8)"
|
||||
Chart.defaults.borderColor = "rgba(0, 0, 0, 0.1)"
|
||||
Chart.defaults.scale.ticks.backdropColor = "transparent"
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively traverse the config object and automatically apply theme-specific color schemes
|
||||
const applyThemeConfig = (obj, theme) => {
|
||||
if (typeof obj !== 'object' || obj === null) return
|
||||
|
||||
Object.keys(obj).forEach(key => {
|
||||
const value = obj[key]
|
||||
// If the property is an object and has theme-specific options, apply them
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (value[theme]) {
|
||||
obj[key] = value[theme] // Apply the value for the current theme
|
||||
} else {
|
||||
// Recursively process child objects
|
||||
applyThemeConfig(value, theme)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const runChartJS = ele => {
|
||||
window.loadChartJS = true
|
||||
|
||||
Array.from(ele).forEach((item, index) => {
|
||||
const chartSrc = item.firstElementChild
|
||||
const chartID = item.getAttribute('data-chartjs-id') || ('chartjs-' + index) // Use custom ID or default ID
|
||||
const width = item.getAttribute('data-width')
|
||||
const existingCanvas = document.getElementById(chartID)
|
||||
|
||||
// If a canvas already exists, remove it to avoid rendering duplicates
|
||||
if (existingCanvas) {
|
||||
existingCanvas.parentNode.remove()
|
||||
}
|
||||
|
||||
const chartDefinition = chartSrc.textContent
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.id = chartID
|
||||
|
||||
const div = document.createElement('div')
|
||||
div.className = 'chartjs-wrap'
|
||||
|
||||
if (width) {
|
||||
div.style.width = width
|
||||
}
|
||||
|
||||
div.appendChild(canvas)
|
||||
chartSrc.insertAdjacentElement('afterend', div)
|
||||
|
||||
const ctx = document.getElementById(chartID).getContext('2d')
|
||||
|
||||
const config = JSON.parse(chartDefinition)
|
||||
|
||||
const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark-mode' : 'light-mode'
|
||||
|
||||
// Set default styles (initial setup)
|
||||
applyThemeDefaultsConfig(theme)
|
||||
|
||||
// Automatically traverse the config and apply dual-mode color schemes
|
||||
applyThemeConfig(config, theme)
|
||||
|
||||
new Chart(ctx, config)
|
||||
})
|
||||
}
|
||||
|
||||
const loadChartJS = () => {
|
||||
const chartJSEle = document.querySelectorAll('#article-container .chartjs-container')
|
||||
if (chartJSEle.length === 0) return
|
||||
|
||||
window.loadChartJS ? runChartJS(chartJSEle) : btf.getScript('https://unpkg.com/chart.js/dist/chart.umd.js').then(() => runChartJS(chartJSEle))
|
||||
}
|
||||
|
||||
// Listen for theme change events
|
||||
btf.addGlobalFn('themeChange', loadChartJS, 'chartjs')
|
||||
btf.addGlobalFn('encrypt', loadChartJS, 'chartjs')
|
||||
|
||||
window.pjax ? loadChartJS() : document.addEventListener('DOMContentLoaded', loadChartJS)
|
||||
})()</script></div><script src="/js/random.js"></script><script src="/js/shuoshuoshouye.js"></script><script src="/js/ai-summary.js"></script><script src="/js/typesense-search.js"></script><script src="/js/statistic.js"></script><script src="/js/footer.js"<script src="https://code.jquery.com/jquery-4.0.0.js"></script><script src="https://cdn.jsdmirror.com/npm/echarts@4.9.0/dist/echarts.min.js"></script><script src="https://cdn.jsdmirror.com/npm/aplayer/dist/APlayer.min.js"></script><script src="https://cdn.jsdmirror.com/npm/meting/dist/Meting.min.js"></script><script src="https://cdn.jsdmirror.com/gh/bishshi/welcomemessage/txmap.js"></script><script src="https://cdn.jsdmirror.com/gh/bishshi/rightmenu@1.2/rightmenu.js"></script><script src="https://cdn.jsdmirror.com/gh/bishshi/sidecalendar@latest/calendar.js"></script><script src="https://cdn.jsdmirror.com/npm/chinese-lunar@0.1.4/lib/chinese-lunar.js"></script><script src="https://cdn.jsdmirror.com/npm/instantsearch.js@4.56.0"></script><script src="https://cdn.jsdmirror.com/npm/typesense-instantsearch-adapter@2.7.0/dist/typesense-instantsearch-adapter.min.js"></script><script src="https://unpkg.com/pjax/pjax.min.js" defer="defer"></script><script>document.addEventListener('DOMContentLoaded', () => {
|
||||
const pjaxSelectors = ["head > title","#config-diff","#body-wrap","#rightside-config-hide","#rightside-config-show",".js-pjax"]
|
||||
|
||||
window.pjax = new Pjax({
|
||||
elements: 'a:not([target="_blank"])',
|
||||
selectors: pjaxSelectors,
|
||||
cacheBust: false,
|
||||
analytics: false,
|
||||
scrollRestoration: false
|
||||
})
|
||||
|
||||
const triggerPjaxFn = (val) => {
|
||||
if (!val) return
|
||||
Object.values(val).forEach(fn => {
|
||||
try {
|
||||
fn()
|
||||
} catch (err) {
|
||||
console.debug('Pjax callback failed:', err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
document.addEventListener('pjax:send', () => {
|
||||
// removeEventListener
|
||||
btf.removeGlobalFnEvent('pjaxSendOnce')
|
||||
btf.removeGlobalFnEvent('themeChange')
|
||||
|
||||
// reset readmode
|
||||
const $bodyClassList = document.body.classList
|
||||
if ($bodyClassList.contains('read-mode')) $bodyClassList.remove('read-mode')
|
||||
|
||||
triggerPjaxFn(window.globalFn.pjaxSend)
|
||||
})
|
||||
|
||||
document.addEventListener('pjax:complete', () => {
|
||||
btf.removeGlobalFnEvent('pjaxCompleteOnce')
|
||||
document.querySelectorAll('script[data-pjax]').forEach(item => {
|
||||
const newScript = document.createElement('script')
|
||||
const content = item.text || item.textContent || item.innerHTML || ""
|
||||
Array.from(item.attributes).forEach(attr => newScript.setAttribute(attr.name, attr.value))
|
||||
newScript.appendChild(document.createTextNode(content))
|
||||
item.parentNode.replaceChild(newScript, item)
|
||||
})
|
||||
|
||||
triggerPjaxFn(window.globalFn.pjaxComplete)
|
||||
})
|
||||
|
||||
document.addEventListener('pjax:error', e => {
|
||||
if (e.request.status === 404) {
|
||||
true
|
||||
? pjax.loadUrl('/404.html')
|
||||
: window.location.href = e.request.responseURL
|
||||
}
|
||||
})
|
||||
})</script><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script></div><div class="js-pjax" id="rightMenu"><div class="rightMenu-group rightMenu-small"><a class="rightMenu-item" href="javascript:window.history.back();"><i class="fa fa-arrow-left"></i></a><a class="rightMenu-item" href="javascript:window.history.forward();"><i class="fa fa-arrow-right"></i></a><a class="rightMenu-item" href="javascript:window.location.reload();"><i class="fa fa-refresh"></i></a><a class="rightMenu-item" href="javascript:window.scrollTo(0, 0);"><i class="fa fa-arrow-up"></i></a></div><div class="rightMenu-group rightMenu-line hide" id="menu-text"><a class="rightMenu-item" href="javascript:rmf.copySelect();"><i class="fa fa-copy"></i><span>复制</span></a><a class="rightMenu-item" href="javascript:rmf.searchinThisPage();"><i class="fas fa-search"></i><span>站内搜索</span></a></div><div class="rightMenu-group rightMenu-line hide" id="menu-too"><a class="rightMenu-item" href="javascript:window.open(window.getSelection().toString());window.location.reload();"><i class="fa fa-link"></i><span>转到链接</span></a></div><div class="rightMenu-group rightMenu-line hide" id="menu-paste"><a class="rightMenu-item" href="javascript:rmf.paste()"><i class="fa fa-copy"></i><span>粘贴</span></a></div><div class="rightMenu-group rightMenu-line hide" id="menu-post"><a class="rightMenu-item" href="javascript:rmf.copyWordsLink()"><i class="fa fa-link"></i><span>复制本文地址</span></a></div><div class="rightMenu-group rightMenu-line hide" id="menu-to"><a class="rightMenu-item" href="javascript:rmf.openWithNewTab()"><i class="fa fa-window-restore"></i><span>新窗口打开</span></a><a class="rightMenu-item" href="javascript:rmf.open()"><i class="fa fa-link"></i><span>转到链接</span></a><a class="rightMenu-item" href="javascript:rmf.copyLink()"><i class="fa fa-copy"></i><span>复制链接</span></a></div><div class="rightMenu-group rightMenu-line hide" id="menu-img"><a class="rightMenu-item" href="javascript:rmf.saveAs()"><i class="fa fa-download"></i><span>保存图片</span></a><a class="rightMenu-item" href="javascript:rmf.openWithNewTab()"><i class="fa fa-window-restore"></i><span>在新窗口打开</span></a><a class="rightMenu-item" href="javascript:rmf.click()"><i class="fa fa-arrows-alt"></i><span>全屏显示</span></a><a class="rightMenu-item" href="javascript:rmf.copyLink()"><i class="fa fa-copy"></i><span>复制图片链接</span></a></div><div class="rightMenu-group rightMenu-line"><a class="rightMenu-item" href="javascript:randomPost()"><i class="fa fa-paper-plane"></i><span>随便逛逛</span></a><a class="rightMenu-item" href="javascript:rmf.switchDarkMode();"><i class="fa fa-moon"></i><span>昼夜切换</span></a><a class="rightMenu-item" href="javascript:rmf.translate();"><i class="iconfont icon-fanti"></i><span>繁简转换</span></a><a class="rightMenu-item" href="javascript:rmf.switchReadMode();"><i class="fa fa-book"></i><span>阅读模式</span></a><a class="rightMenu-item" href="javascript:pjax.loadUrl("/privacy/");"><i class="fa fa-info-circle"></i><span>隐私声明</span></a><a class="rightMenu-item" href="javascript:pjax.loadUrl("/cookie/");"><i class="fa fa-info-circle"></i><span>Cookie协议</span></a><a class="rightMenu-item" href="javascript:pjax.loadUrl("/cc/");"><i class="fa fa-info-circle"></i><span>版权声明</span></a></div></div><!-- hexo injector body_end start --><script data-pjax>
|
||||
function butterfly_swiper_injector_config(){
|
||||
var parent_div_git = document.getElementById('recent-posts');
|
||||
var item_html = '<div class="recent-post-item" style="height: auto;width: 100%"><div class="blog-slider swiper-container-fade swiper-container-horizontal" id="swiper_container"><div class="blog-slider__wrp swiper-wrapper" style="transition-duration: 0ms;"><div class="blog-slider__item swiper-slide" style="background:url(https://pic.biss.click/image/fca16741-64fa-495b-aa5e-a2ef077461ef.webp);border-radius:12px;opacity: 1; transform: translate3d(0px, 0px, 0px); transition-duration: 0ms;"><div class="blog-slider__content"><span class="blog-slider__code">2026-02-23</span><a class="blog-slider__title" onclick="pjax.loadUrl("posts/56f57c0b/");" href="javascript:void(0);" alt="">自建renovate-bot</a><div class="blog-slider__text">还不知道怎么描述哦</div></div></div><div class="blog-slider__item swiper-slide" style="background:url(https://pic.biss.click/image/63a5c345-cb40-4e92-bc7b-7dc4daaf5b74.webp);border-radius:12px;opacity: 1; transform: translate3d(0px, 0px, 0px); transition-duration: 0ms;"><div class="blog-slider__content"><span class="blog-slider__code">2025-09-28</span><a class="blog-slider__title" onclick="pjax.loadUrl("posts/b57500e9/");" href="javascript:void(0);" alt="">在Openwrt上安装AdguardHome</a><div class="blog-slider__text">还不知道怎么描述哦</div></div></div><div class="blog-slider__item swiper-slide" style="background:url(https://pic.biss.click/image/961bc881-cb0a-4ab7-ace5-9990e71c30a0.webp);border-radius:12px;opacity: 1; transform: translate3d(0px, 0px, 0px); transition-duration: 0ms;"><div class="blog-slider__content"><span class="blog-slider__code">2026-02-07</span><a class="blog-slider__title" onclick="pjax.loadUrl("posts/34725d47/");" href="javascript:void(0);" alt="">安装gitea</a><div class="blog-slider__text">还不知道怎么描述哦</div></div></div></div><div class="blog-slider__pagination swiper-pagination-clickable swiper-pagination-bullets"></div><div class="swiper-button-prev"></div><div class="swiper-button-next"></div></div></div>';
|
||||
console.log('已挂载butterfly_swiper')
|
||||
parent_div_git.insertAdjacentHTML("afterbegin",item_html)
|
||||
}
|
||||
var elist = 'undefined'.split(',');
|
||||
var cpage = location.pathname;
|
||||
var epage = '/';
|
||||
var flag = 0;
|
||||
|
||||
for (var i=0;i<elist.length;i++){
|
||||
if (cpage.includes(elist[i])){
|
||||
flag++;
|
||||
}
|
||||
}
|
||||
|
||||
if ((epage ==='all')&&(flag == 0)){
|
||||
butterfly_swiper_injector_config();
|
||||
}
|
||||
else if (epage === cpage){
|
||||
butterfly_swiper_injector_config();
|
||||
}
|
||||
</script><script defer src="https://cdnjs.cloudflare.com/ajax/libs/Swiper/5.4.5/js/swiper.min.js"></script><script defer data-pjax src="https://npm.elemecdn.com/hexo-butterfly-swiper-lyx/lib/swiper_init.js"></script><!-- hexo injector body_end end --></body></html>
|
||||
@@ -0,0 +1,495 @@
|
||||
<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0,viewport-fit=cover"><title>Cookies | Bi's Blog</title><meta name="author" content="biss"><meta name="copyright" content="biss"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="我使用Cookies来保持我的网站和我开发的软件的可靠性,安全性和个性化。当你接受Cookies时,这有助于通过我识别你的身份、记住你的偏好、或提供个性化用户体验来帮助我改善网站 本政策应与我的 隐私政策 一起阅读,该隐私政策解释了我如何使用个人信息 如果你对我使用你的个人信息或Cookies的方式有任何疑问,请通过 bishsh2006@outlook.com 与我联系 如果你想管理你的Coo">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="Cookies">
|
||||
<meta property="og:url" content="https://blog.biss.click/cookie/index.html">
|
||||
<meta property="og:site_name" content="Bi's Blog">
|
||||
<meta property="og:description" content="我使用Cookies来保持我的网站和我开发的软件的可靠性,安全性和个性化。当你接受Cookies时,这有助于通过我识别你的身份、记住你的偏好、或提供个性化用户体验来帮助我改善网站 本政策应与我的 隐私政策 一起阅读,该隐私政策解释了我如何使用个人信息 如果你对我使用你的个人信息或Cookies的方式有任何疑问,请通过 bishsh2006@outlook.com 与我联系 如果你想管理你的Coo">
|
||||
<meta property="og:locale" content="zh_CN">
|
||||
<meta property="og:image" content="https://free.picui.cn/free/2025/08/10/689845496a283.png">
|
||||
<meta property="article:published_time" content="2023-03-14T05:07:26.000Z">
|
||||
<meta property="article:modified_time" content="2026-04-01T13:26:48.910Z">
|
||||
<meta property="article:author" content="biss">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://free.picui.cn/free/2025/08/10/689845496a283.png"><script type="application/ld+json"></script><link rel="shortcut icon" href="/images/Bi.ico"><link rel="canonical" href="https://blog.biss.click/cookie/index.html"><link rel="preconnect" href="//unpkg.com"/><link rel="preconnect" href="//busuanzi.ibruce.info"/><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://unpkg.com/@fortawesome/fontawesome-free/css/all.min.css"><link rel="stylesheet" href="https://unpkg.com/node-snackbar/dist/snackbar.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://unpkg.com/@fancyapps/ui/dist/fancybox/fancybox.css" media="print" onload="this.media='all'"><script>
|
||||
(() => {
|
||||
|
||||
const saveToLocal = {
|
||||
set: (key, value, ttl) => {
|
||||
if (!ttl) return
|
||||
const expiry = Date.now() + ttl * 86400000
|
||||
localStorage.setItem(key, JSON.stringify({ value, expiry }))
|
||||
},
|
||||
get: key => {
|
||||
const itemStr = localStorage.getItem(key)
|
||||
if (!itemStr) return undefined
|
||||
const { value, expiry } = JSON.parse(itemStr)
|
||||
if (Date.now() > expiry) {
|
||||
localStorage.removeItem(key)
|
||||
return undefined
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
window.btf = {
|
||||
saveToLocal,
|
||||
getScript: (url, attr = {}) => new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script')
|
||||
script.src = url
|
||||
script.async = true
|
||||
Object.entries(attr).forEach(([key, val]) => script.setAttribute(key, val))
|
||||
script.onload = script.onreadystatechange = () => {
|
||||
if (!script.readyState || /loaded|complete/.test(script.readyState)) resolve()
|
||||
}
|
||||
script.onerror = reject
|
||||
document.head.appendChild(script)
|
||||
}),
|
||||
getCSS: (url, id) => new Promise((resolve, reject) => {
|
||||
const link = document.createElement('link')
|
||||
link.rel = 'stylesheet'
|
||||
link.href = url
|
||||
if (id) link.id = id
|
||||
link.onload = link.onreadystatechange = () => {
|
||||
if (!link.readyState || /loaded|complete/.test(link.readyState)) resolve()
|
||||
}
|
||||
link.onerror = reject
|
||||
document.head.appendChild(link)
|
||||
}),
|
||||
addGlobalFn: (key, fn, name = false, parent = window) => {
|
||||
if (!true && key.startsWith('pjax')) return
|
||||
const globalFn = parent.globalFn || {}
|
||||
globalFn[key] = globalFn[key] || {}
|
||||
globalFn[key][name || Object.keys(globalFn[key]).length] = fn
|
||||
parent.globalFn = globalFn
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const activateDarkMode = () => {
|
||||
document.documentElement.setAttribute('data-theme', 'dark')
|
||||
if (document.querySelector('meta[name="theme-color"]') !== null) {
|
||||
document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
|
||||
}
|
||||
}
|
||||
const activateLightMode = () => {
|
||||
document.documentElement.setAttribute('data-theme', 'light')
|
||||
if (document.querySelector('meta[name="theme-color"]') !== null) {
|
||||
document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
|
||||
}
|
||||
}
|
||||
|
||||
btf.activateDarkMode = activateDarkMode
|
||||
btf.activateLightMode = activateLightMode
|
||||
|
||||
const theme = saveToLocal.get('theme')
|
||||
|
||||
theme === 'dark' ? activateDarkMode() : theme === 'light' ? activateLightMode() : null
|
||||
|
||||
|
||||
const asideStatus = saveToLocal.get('aside-status')
|
||||
if (asideStatus !== undefined) {
|
||||
document.documentElement.classList.toggle('hide-aside', asideStatus === 'hide')
|
||||
}
|
||||
|
||||
|
||||
const detectApple = () => {
|
||||
if (/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)) {
|
||||
document.documentElement.classList.add('apple')
|
||||
}
|
||||
}
|
||||
detectApple()
|
||||
|
||||
})()
|
||||
</script><script>const GLOBAL_CONFIG = {
|
||||
root: '/',
|
||||
algolia: undefined,
|
||||
localSearch: undefined,
|
||||
translate: {"defaultEncoding":2,"translateDelay":0,"msgToTraditionalChinese":"繁","msgToSimplifiedChinese":"簡"},
|
||||
highlight: {"plugin":"highlight.js","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":200,"highlightFullpage":true,"highlightMacStyle":false},
|
||||
copy: {
|
||||
success: '复制成功',
|
||||
error: '复制失败',
|
||||
noSupport: '浏览器不支持'
|
||||
},
|
||||
relativeDate: {
|
||||
homepage: false,
|
||||
post: false
|
||||
},
|
||||
runtime: '',
|
||||
dateSuffix: {
|
||||
just: '刚刚',
|
||||
min: '分钟前',
|
||||
hour: '小时前',
|
||||
day: '天前',
|
||||
month: '个月前'
|
||||
},
|
||||
copyright: undefined,
|
||||
lightbox: 'fancybox',
|
||||
Snackbar: {"chs_to_cht":"已切换为繁体中文","cht_to_chs":"已切换为简体中文","day_to_night":"已切换为深色模式","night_to_day":"已切换为浅色模式","bgLight":"#49b1f5","bgDark":"#1f1f1f","position":"bottom-left"},
|
||||
infinitegrid: {
|
||||
js: 'https://unpkg.com/@egjs/infinitegrid/dist/infinitegrid.min.js',
|
||||
buttonText: '加载更多'
|
||||
},
|
||||
isPhotoFigcaption: false,
|
||||
islazyloadPlugin: false,
|
||||
isAnchor: false,
|
||||
percent: {
|
||||
toc: true,
|
||||
rightside: false,
|
||||
},
|
||||
autoDarkmode: false
|
||||
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
|
||||
title: 'Cookies',
|
||||
isHighlightShrink: false,
|
||||
isToc: false,
|
||||
pageType: 'page'
|
||||
}</script><link rel="stylesheet" href="/css/shuoshuo.css"><link rel="stylesheet" href="/css/shuoshuoshouye.css"><link rel="stylesheet" href="/css/nav.css"><link rel="stylesheet" href="/css/style.css"><link rel="stylesheet" href="/css/poem.css"><link rel="stylesheet" href="/css/swiper.css"><link rel="stylesheet" href="https://cdn.jsdmirror.com/npm/instantsearch.css/themes/reset-min.css"><link rel="stylesheet" href="https://cdn.jsdmirror.com/gh/bishshi/welcomemessage/welcome.css"><link rel="stylesheet" href="https://cdn.jsdmirror.com/gh/bishshi/sidecalendar/calendar.css"><link rel="stylesheet" href="https://cdn.jsdmirror.com/gh/bishshi/rightmenu/rightmenu.css"><link rel="stylesheet" href="https://cdn.jsdmirror.com/gh/bishshi/webfont/font.css"><link rel="stylesheet" href="https://cdn.jsdmirror.com/npm/aplayer/dist/APlayer.min.css" media="all" onload="this.media="all""><!-- hexo injector head_end start --><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/5.4.5/css/swiper.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn1.tianli0.top/npm/hexo-butterfly-swiper/lib/swiperstyle.css" media="print" onload="this.media='all'"><!-- hexo injector head_end end --><style type="text/css">
|
||||
.spoiler {
|
||||
display: inline;
|
||||
}
|
||||
p.spoiler {
|
||||
display: flex;
|
||||
}
|
||||
.spoiler a {
|
||||
pointer-events: none;
|
||||
}
|
||||
.spoiler-blur, .spoiler-blur > * {
|
||||
transition: text-shadow .5s ease;
|
||||
}
|
||||
.spoiler .spoiler-blur, .spoiler .spoiler-blur > * {
|
||||
color: rgba(0, 0, 0, 0);
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
text-shadow: 0 0 10px grey;
|
||||
cursor: pointer;
|
||||
}
|
||||
.spoiler .spoiler-blur:hover, .spoiler .spoiler-blur:hover > * {
|
||||
text-shadow: 0 0 5px grey;
|
||||
}
|
||||
.spoiler-box, .spoiler-box > * {
|
||||
transition: color .5s ease,
|
||||
background-color .5s ease;
|
||||
}
|
||||
.spoiler .spoiler-box, .spoiler .spoiler-box > * {
|
||||
color: black;
|
||||
background-color: black;
|
||||
text-shadow: none;
|
||||
}</style><meta name="generator" content="Hexo 8.1.1"><link rel="alternate" href="/atom.xml" title="Bi's Blog" type="application/atom+xml">
|
||||
</head><body><div class="bg-animation" id="web_bg" style="background-image: url(/images/background.png);"></div><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="avatar-img text-center"><img src="https://free.picui.cn/free/2025/08/10/689845496a283.png" onerror="this.onerror=null;this.src='/img/friend_404.gif'" alt="avatar"/></div><div class="site-data text-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">35</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">11</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">5</div></a></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 存档</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></div><div class="menus_item"><a class="site-page" href="/shuoshuo/"><i class="fa-fw fa-regular fa-comment"></i><span> 说说</span></a></div><div class="menus_item"><a class="site-page" href="/link/"><i class="fa-fw fas fa-link"></i><span> 友链</span></a></div><div class="menus_item"><a class="site-page" href="/about/"><i class="fa-fw fas fa-heart"></i><span> 关于</span></a></div></div></div></div><div class="page" id="body-wrap"><header class="not-top-img fixed" id="page-header"><nav id="nav"><span id="blog-info"><div id="ls-menu-container"><i class="fas fa-fingerprint"></i><div id="ls-menu-panel"><div class="ls-section"><div class="ls-title">😀 个人网站</div><div class="ls-grid"><a href="/"><i class="fas fa-rss"></i> 个人博客</a><a target="_blank" rel="noopener" href="https://github.com/bishshi"><i class="fab fa-github"></i> Github</a></div></div><div class="ls-section"><div class="ls-title">😎 常用服务</div><div class="ls-grid"><a target="_blank" rel="noopener" href="https://git.biss.click/biss"><i class="fas fa-code"></i> 代码仓库</a><a target="_blank" rel="noopener" href="https://mm.biss.click"><i class="fas fa-pen-nib"></i> 日常yy</a><a target="_blank" rel="noopener" href="https://statistic.biss.click/"><i class="fas fa-users"></i> 访客统计</a><a target="_blank" rel="noopener" href="https://pic.biss.click"><i class="fas fa-image"></i> 图床</a><a target="_blank" rel="noopener" href="https://git.biss.click"><i class="fas fa-code-branch"></i> 代码仓库</a></div></div><div class="ls-section"><div class="ls-title">🛸 实用工具</div><div class="ls-grid"><a target="_blank" rel="noopener" href="https://cover.biss.click"><i class="fas fa-palette"></i> 封面设计</a><a target="_blank" rel="noopener" href="https://doc.biss.click"><i class="fas fa-file"></i> 文档服务</a><a target="_blank" rel="noopener" href="https://doc.biss.click"><i class="fas fa-server"></i>服务监测</a><a target="_blank" rel="noopener" href="https://typesense.biss.click"><i class="fas fa-magnifying-glass"></i> 搜索后端</a></div></div></div></div><a class="nav-site-title" href="/"><span class="site-name">Bi's Blog</span></a></span><div id="nav-right"><div id="menus"><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 存档</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></div><div class="menus_item"><a class="site-page" href="/shuoshuo/"><i class="fa-fw fa-regular fa-comment"></i><span> 说说</span></a></div><div class="menus_item"><a class="site-page" href="/link/"><i class="fa-fw fas fa-link"></i><span> 友链</span></a></div><div class="menus_item"><a class="site-page" href="/about/"><i class="fa-fw fas fa-heart"></i><span> 关于</span></a></div></div></div><div id="random-post-button"><a class="site-page social-icon" id="random-post-link" href="javascript:void(0);" onclick="randomPost()"><i class="fas fa-solid fa-shuffle"></i></a></div><div id="search-button"><a class="site-page social-icon search-typesense-trigger"><i class="fas fa-search fa-fw"></i></a></div><div id="toggle-menu"><span class="site-page"><i class="fas fa-bars fa-fw"></i></span></div></div></nav><h1 class="title-seo">Cookies</h1></header><main class="layout hide-aside" id="content-inner"><div id="page"><div class="page-title">Cookies</div><div class="container" id="article-container"><!-- 页面内容 -->
|
||||
<p>我使用Cookies来保持我的网站和我开发的软件的可靠性,安全性和个性化。当你接受Cookies时,这有助于通过我识别你的身份、记住你的偏好、或提供个性化用户体验来帮助我改善网站</p>
|
||||
<p>本政策应与我的 <a href="/privacy/"><strong>隐私政策</strong></a> 一起阅读,该隐私政策解释了我如何使用个人信息</p>
|
||||
<p>如果你对我使用你的个人信息或Cookies的方式有任何疑问,请通过 <code>bishsh2006@outlook.com</code> 与我联系</p>
|
||||
<p>如果你想管理你的Cookies,请按照下面“如何管理Cookies”部分中的说明进行操作。</p>
|
||||
<h1 id="什么是Cookies?"><a href="#什么是Cookies?" class="headerlink" title="什么是Cookies?"></a>什么是Cookies?</h1><p>Cookies是一种小型文本文件,当你访问网站时,网站可能会将这些文件放在你的计算机或设备上。Cookies会帮助网站或其他网站在你下次访问时识别你的设备。网站信标、像素或其他类似文件也可以做同样的事情。我在此政策中使用术语“Cookies”来指代以这种方式收集信息的所有文件</p>
|
||||
<p>Cookies提供许多功能。例如,他们可以帮助我记住你喜欢深色模式还是浅色模式,分析我网站的效果</p>
|
||||
<p>大多数网站使用Cookies来收集和保留有关其访问者的个人信息。大多数Cookies收集一般信息,例如访问者如何到达和使用我的网站,他们使用的设备,他们的互联网协议地址(IP地址),他们正在查看的页面及其大致位置(例如,我将能够认识到你正在从深圳访问我的网站)</p>
|
||||
<h1 id="Cookies的目的"><a href="#Cookies的目的" class="headerlink" title="Cookies的目的"></a>Cookies的目的</h1><p>我将Cookies分为以下类别:</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th align="center">用途</th>
|
||||
<th align="center">说明</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td align="center">授权</td>
|
||||
<td align="center">你访问我的网站时,我可通过 Cookie 提供正确信息,为你打造个性化的体验。例如:Cookie会告知你通过搜索引擎搜索的具体内容来改善文章的标题优化关键词、或者创建更符合你搜索需求的文章内容</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">安全措施</td>
|
||||
<td align="center">我通过 Cookie 启用及支持安全功能,监控和防止可疑活动、欺诈性流量和违反版权协议的行为</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">偏好、功能和服务</td>
|
||||
<td align="center">我使用功能性Cookies来让我记住你的偏好,或保存你向我提供的有关你的喜好或其他信息</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">个性化广告</td>
|
||||
<td align="center">本站及所属旗下产品、IP没有个性化广告</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">网站性能、分析和研究</td>
|
||||
<td align="center">我使用这些cookie来监控网站性能。这使我能够通过快速识别和解决出现的任何问题来提供高质量的体验</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<h1 id="我的网站上的第三方Cookies"><a href="#我的网站上的第三方Cookies" class="headerlink" title="我的网站上的第三方Cookies"></a>我的网站上的第三方Cookies</h1><p>我还在我的网站上使用属于上述类别的第三方Cookies,用于以下目的:</p>
|
||||
<ul>
|
||||
<li>帮助我监控网站上的流量;</li>
|
||||
<li>识别欺诈或非人为性流量;</li>
|
||||
<li>协助市场调研;</li>
|
||||
<li>改善网站功能;</li>
|
||||
<li>监督我的版权协议和隐私政策的遵守情况</li>
|
||||
</ul>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th align="center">第三方服务商</th>
|
||||
<th align="center">用途</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td align="center">51.la</td>
|
||||
<td align="center">用于统计站内访问情况,进行针对性优化异常</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">灵雀应用监控</td>
|
||||
<td align="center">监控系统将网站所发生的程序异常、资源加载异常、网络请求异常上报汇总,同时对页面和资源进行性能监控和卡顿监测,分析不同维度下的耗时情况,快速解决遇到的问题</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">Twikoo</td>
|
||||
<td align="center">Twikoo评论服务,自建存储私有,用于本站的评论服务</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<h1 id="如何管理Cookies?"><a href="#如何管理Cookies?" class="headerlink" title="如何管理Cookies?"></a>如何管理Cookies?</h1><p>在将Cookie放置在你的计算机或设备上之前,系统会显示一个弹出窗口,要求你同意设置这些Cookie。通过同意放置Cookies,你可以让我为你提供最佳的体验和服务。如果你愿意,你可以通过浏览器设置关闭本站的Cookie来拒绝同意放置Cookies;但是,我网站的部分功能可能无法完全或按预期运行。你有机会允许和/或拒绝使用Cookie。你可以通过访问浏览器设置随时返回到你的Cookie偏好设置以查看和/或删除它们</p>
|
||||
<p>除了我提供的控件之外,你还可以选择在Internet浏览器中启用或禁用Cookie。大多数互联网浏览器还允许你选择是要禁用所有Cookie还是仅禁用第三方Cookie。默认情况下,大多数互联网浏览器都接受Cookie,但这可以更改。有关详细信息,请参阅Internet浏览器中的帮助菜单或设备随附的文档</p>
|
||||
<p>以下链接提供了有关如何在所有主流浏览器中控制Cookie的说明:</p>
|
||||
<ul>
|
||||
<li><a target="_blank" rel="noopener" href="https://support.google.com/chrome/answer/95647?hl=en"><strong>Google Chrome</strong></a></li>
|
||||
<li><a target="_blank" rel="noopener" href="https://support.microsoft.com/en-us/help/260971/description-of-cookies"><strong>IE</strong></a></li>
|
||||
<li><a target="_blank" rel="noopener" href="https://support.apple.com/guide/safari/manage-cookies-and-website-data-sfri11471/mac"><strong>Safari(桌面版)</strong></a></li>
|
||||
<li><a target="_blank" rel="noopener" href="https://support.apple.com/en-us/HT201265"><strong>Safari(移动版)</strong></a></li>
|
||||
<li><a target="_blank" rel="noopener" href="https://support.mozilla.org/en-US/kb/Cookies-information-websites-store-on-your-computer"><strong>火狐浏览器</strong></a></li>
|
||||
<li><a target="_blank" rel="noopener" href="http://support.google.com/ics/nexus/bin/answer.py?hl=en&answer=2425067"><strong>Android浏览器</strong></a></li>
|
||||
</ul>
|
||||
<p>如你使用其他浏览器,请参阅浏览器制造商提供的文档。<br>有关Cookies以及如何管理Cookies的更多信息,请访问:</p>
|
||||
<p><a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/Cookie"><strong>wikipedia.org</strong></a> 、 <a target="_blank" rel="noopener" href="https://www.allaboutcookies.org/"><strong>allaboutCookies.org</strong></a> 或 <a target="_blank" rel="noopener" href="https://www.aboutcookies.org/"><strong>aboutCookies.org</strong></a></p>
|
||||
<h1 id="更多信息"><a href="#更多信息" class="headerlink" title="更多信息"></a>更多信息</h1><p>有关我数据处理的更多信息,请参阅我的隐私政策。如果你对此Cookie政策有任何疑问,请通过 <code>bishsh2006@outlook.com</code> 与我联系</p>
|
||||
</div></div></main><footer id="footer"><div class="footer-other"><div class="footer-copyright"><span class="copyright">© 2025 - 2026 By biss</span><span class="framework-info"><span>框架 </span><a target="_blank" rel="noopener" href="https://hexo.io">Hexo</a><span class="footer-separator">|</span><span>主题 </span><a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a></span></div><div class="footer_custom_text"><p> <a style="margin-inline:5px"target="_blank" href="https://hexo.io/zh-cn/"><img src="https://img.shields.io/badge/Frame-Hexo-blue?style=flat&logo=hexo" title="hexo 8.1"></a> <a style="margin-inline:5px"target="_blank" href="https://butterfly.js.org"><img src="https://img.shields.io/badge/Theme-Butterfly-pink?style=flat" title="butterfly主题"></a> <a style="margin-inline:5px"target="_blank" href="https://creativecommons.org/licenses/by-nc-sa/4.0/"><img src="https://img.shields.io/badge/Copyright-BY--NC--SA-red?style=flat&logo=alchemy" title="CC BY-NC-SA 4.0"></a> <a href="https://www.trustssl.cc/ipv6.php?domain=blog.biss.click" title="本站已支持IPv6访问" target="_blank"><img src="https://static.coolcdn.cn/images/ipv6.svg"/></a> </p></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="translateLink" type="button" title="简繁转换">繁</button><button id="darkmode" type="button" title="日间和夜间模式切换"><i class="fas fa-adjust"></i></button></div><div id="rightside-config-show"><button id="rightside-config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button id="go-up" type="button" title="回到顶部"><span class="scroll-percent"></span><i class="fas fa-arrow-up"></i></button></div></div><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><script src="/js/tw_cn.js"></script><script src="https://unpkg.com/@fancyapps/ui/dist/fancybox/fancybox.umd.js"></script><script src="https://unpkg.com/node-snackbar/dist/snackbar.min.js"></script><div class="js-pjax"><script>(() => {
|
||||
const loadMathjax = () => {
|
||||
if (!window.MathJax) {
|
||||
window.MathJax = {
|
||||
loader: {
|
||||
load: [
|
||||
// Four font extension packages (optional)
|
||||
//- '[tex]/bbm',
|
||||
//- '[tex]/bboldx',
|
||||
//- '[tex]/dsfont',
|
||||
'[tex]/mhchem'
|
||||
],
|
||||
paths: {
|
||||
'mathjax-newcm': '[mathjax]/../@mathjax/mathjax-newcm-font',
|
||||
|
||||
//- // Four font extension packages (optional)
|
||||
//- 'mathjax-bbm-extension': '[mathjax]/../@mathjax/mathjax-bbm-font-extension',
|
||||
//- 'mathjax-bboldx-extension': '[mathjax]/../@mathjax/mathjax-bboldx-font-extension',
|
||||
//- 'mathjax-dsfont-extension': '[mathjax]/../@mathjax/mathjax-dsfont-font-extension',
|
||||
'mathjax-mhchem-extension': '[mathjax]/../@mathjax/mathjax-mhchem-font-extension'
|
||||
}
|
||||
},
|
||||
output: {
|
||||
font: 'mathjax-newcm',
|
||||
},
|
||||
tex: {
|
||||
inlineMath: [['$', '$'], ['\\(', '\\)']],
|
||||
tags: 'none',
|
||||
packages: {
|
||||
'[+]': [
|
||||
'mhchem'
|
||||
]
|
||||
}
|
||||
},
|
||||
chtml: {
|
||||
scale: 1.1
|
||||
},
|
||||
options: {
|
||||
enableMenu: true,
|
||||
menuOptions: {
|
||||
settings: {
|
||||
enrich: false // Turn off Braille and voice narration text automatic generation
|
||||
}
|
||||
},
|
||||
renderActions: {
|
||||
findScript: [10, doc => {
|
||||
for (const node of document.querySelectorAll('script[type^="math/tex"]')) {
|
||||
const display = !!node.type.match(/; *mode=display/)
|
||||
const math = new doc.options.MathItem(node.textContent, doc.inputJax[0], display)
|
||||
const text = document.createTextNode('')
|
||||
node.parentNode.replaceChild(text, node)
|
||||
math.start = {node: text, delim: '', n: 0}
|
||||
math.end = {node: text, delim: '', n: 0}
|
||||
doc.math.push(math)
|
||||
}
|
||||
}, '']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const script = document.createElement('script')
|
||||
script.src = 'https://unpkg.com/mathjax/tex-mml-chtml.js'
|
||||
script.id = 'MathJax-script'
|
||||
script.async = true
|
||||
document.head.appendChild(script)
|
||||
} else {
|
||||
MathJax.startup.document.state(0)
|
||||
MathJax.texReset()
|
||||
MathJax.typesetPromise()
|
||||
}
|
||||
}
|
||||
|
||||
btf.addGlobalFn('encrypt', loadMathjax, 'mathjax')
|
||||
window.pjax ? loadMathjax() : window.addEventListener('load', loadMathjax)
|
||||
})()</script><script>(() => {
|
||||
const applyThemeDefaultsConfig = theme => {
|
||||
if (theme === 'dark-mode') {
|
||||
Chart.defaults.color = "rgba(255, 255, 255, 0.8)"
|
||||
Chart.defaults.borderColor = "rgba(255, 255, 255, 0.2)"
|
||||
Chart.defaults.scale.ticks.backdropColor = "transparent"
|
||||
} else {
|
||||
Chart.defaults.color = "rgba(0, 0, 0, 0.8)"
|
||||
Chart.defaults.borderColor = "rgba(0, 0, 0, 0.1)"
|
||||
Chart.defaults.scale.ticks.backdropColor = "transparent"
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively traverse the config object and automatically apply theme-specific color schemes
|
||||
const applyThemeConfig = (obj, theme) => {
|
||||
if (typeof obj !== 'object' || obj === null) return
|
||||
|
||||
Object.keys(obj).forEach(key => {
|
||||
const value = obj[key]
|
||||
// If the property is an object and has theme-specific options, apply them
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (value[theme]) {
|
||||
obj[key] = value[theme] // Apply the value for the current theme
|
||||
} else {
|
||||
// Recursively process child objects
|
||||
applyThemeConfig(value, theme)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const runChartJS = ele => {
|
||||
window.loadChartJS = true
|
||||
|
||||
Array.from(ele).forEach((item, index) => {
|
||||
const chartSrc = item.firstElementChild
|
||||
const chartID = item.getAttribute('data-chartjs-id') || ('chartjs-' + index) // Use custom ID or default ID
|
||||
const width = item.getAttribute('data-width')
|
||||
const existingCanvas = document.getElementById(chartID)
|
||||
|
||||
// If a canvas already exists, remove it to avoid rendering duplicates
|
||||
if (existingCanvas) {
|
||||
existingCanvas.parentNode.remove()
|
||||
}
|
||||
|
||||
const chartDefinition = chartSrc.textContent
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.id = chartID
|
||||
|
||||
const div = document.createElement('div')
|
||||
div.className = 'chartjs-wrap'
|
||||
|
||||
if (width) {
|
||||
div.style.width = width
|
||||
}
|
||||
|
||||
div.appendChild(canvas)
|
||||
chartSrc.insertAdjacentElement('afterend', div)
|
||||
|
||||
const ctx = document.getElementById(chartID).getContext('2d')
|
||||
|
||||
const config = JSON.parse(chartDefinition)
|
||||
|
||||
const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark-mode' : 'light-mode'
|
||||
|
||||
// Set default styles (initial setup)
|
||||
applyThemeDefaultsConfig(theme)
|
||||
|
||||
// Automatically traverse the config and apply dual-mode color schemes
|
||||
applyThemeConfig(config, theme)
|
||||
|
||||
new Chart(ctx, config)
|
||||
})
|
||||
}
|
||||
|
||||
const loadChartJS = () => {
|
||||
const chartJSEle = document.querySelectorAll('#article-container .chartjs-container')
|
||||
if (chartJSEle.length === 0) return
|
||||
|
||||
window.loadChartJS ? runChartJS(chartJSEle) : btf.getScript('https://unpkg.com/chart.js/dist/chart.umd.js').then(() => runChartJS(chartJSEle))
|
||||
}
|
||||
|
||||
// Listen for theme change events
|
||||
btf.addGlobalFn('themeChange', loadChartJS, 'chartjs')
|
||||
btf.addGlobalFn('encrypt', loadChartJS, 'chartjs')
|
||||
|
||||
window.pjax ? loadChartJS() : document.addEventListener('DOMContentLoaded', loadChartJS)
|
||||
})()</script></div><script src="/js/random.js"></script><script src="/js/shuoshuoshouye.js"></script><script src="/js/ai-summary.js"></script><script src="/js/typesense-search.js"></script><script src="/js/statistic.js"></script><script src="/js/footer.js"<script src="https://code.jquery.com/jquery-4.0.0.js"></script><script src="https://cdn.jsdmirror.com/npm/echarts@4.9.0/dist/echarts.min.js"></script><script src="https://cdn.jsdmirror.com/npm/aplayer/dist/APlayer.min.js"></script><script src="https://cdn.jsdmirror.com/npm/meting/dist/Meting.min.js"></script><script src="https://cdn.jsdmirror.com/gh/bishshi/welcomemessage/txmap.js"></script><script src="https://cdn.jsdmirror.com/gh/bishshi/rightmenu@1.2/rightmenu.js"></script><script src="https://cdn.jsdmirror.com/gh/bishshi/sidecalendar@latest/calendar.js"></script><script src="https://cdn.jsdmirror.com/npm/chinese-lunar@0.1.4/lib/chinese-lunar.js"></script><script src="https://cdn.jsdmirror.com/npm/instantsearch.js@4.56.0"></script><script src="https://cdn.jsdmirror.com/npm/typesense-instantsearch-adapter@2.7.0/dist/typesense-instantsearch-adapter.min.js"></script><script src="https://unpkg.com/pjax/pjax.min.js" defer="defer"></script><script>document.addEventListener('DOMContentLoaded', () => {
|
||||
const pjaxSelectors = ["head > title","#config-diff","#body-wrap","#rightside-config-hide","#rightside-config-show",".js-pjax"]
|
||||
|
||||
window.pjax = new Pjax({
|
||||
elements: 'a:not([target="_blank"])',
|
||||
selectors: pjaxSelectors,
|
||||
cacheBust: false,
|
||||
analytics: false,
|
||||
scrollRestoration: false
|
||||
})
|
||||
|
||||
const triggerPjaxFn = (val) => {
|
||||
if (!val) return
|
||||
Object.values(val).forEach(fn => {
|
||||
try {
|
||||
fn()
|
||||
} catch (err) {
|
||||
console.debug('Pjax callback failed:', err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
document.addEventListener('pjax:send', () => {
|
||||
// removeEventListener
|
||||
btf.removeGlobalFnEvent('pjaxSendOnce')
|
||||
btf.removeGlobalFnEvent('themeChange')
|
||||
|
||||
// reset readmode
|
||||
const $bodyClassList = document.body.classList
|
||||
if ($bodyClassList.contains('read-mode')) $bodyClassList.remove('read-mode')
|
||||
|
||||
triggerPjaxFn(window.globalFn.pjaxSend)
|
||||
})
|
||||
|
||||
document.addEventListener('pjax:complete', () => {
|
||||
btf.removeGlobalFnEvent('pjaxCompleteOnce')
|
||||
document.querySelectorAll('script[data-pjax]').forEach(item => {
|
||||
const newScript = document.createElement('script')
|
||||
const content = item.text || item.textContent || item.innerHTML || ""
|
||||
Array.from(item.attributes).forEach(attr => newScript.setAttribute(attr.name, attr.value))
|
||||
newScript.appendChild(document.createTextNode(content))
|
||||
item.parentNode.replaceChild(newScript, item)
|
||||
})
|
||||
|
||||
triggerPjaxFn(window.globalFn.pjaxComplete)
|
||||
})
|
||||
|
||||
document.addEventListener('pjax:error', e => {
|
||||
if (e.request.status === 404) {
|
||||
true
|
||||
? pjax.loadUrl('/404.html')
|
||||
: window.location.href = e.request.responseURL
|
||||
}
|
||||
})
|
||||
})</script><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script></div><div class="js-pjax" id="rightMenu"><div class="rightMenu-group rightMenu-small"><a class="rightMenu-item" href="javascript:window.history.back();"><i class="fa fa-arrow-left"></i></a><a class="rightMenu-item" href="javascript:window.history.forward();"><i class="fa fa-arrow-right"></i></a><a class="rightMenu-item" href="javascript:window.location.reload();"><i class="fa fa-refresh"></i></a><a class="rightMenu-item" href="javascript:window.scrollTo(0, 0);"><i class="fa fa-arrow-up"></i></a></div><div class="rightMenu-group rightMenu-line hide" id="menu-text"><a class="rightMenu-item" href="javascript:rmf.copySelect();"><i class="fa fa-copy"></i><span>复制</span></a><a class="rightMenu-item" href="javascript:rmf.searchinThisPage();"><i class="fas fa-search"></i><span>站内搜索</span></a></div><div class="rightMenu-group rightMenu-line hide" id="menu-too"><a class="rightMenu-item" href="javascript:window.open(window.getSelection().toString());window.location.reload();"><i class="fa fa-link"></i><span>转到链接</span></a></div><div class="rightMenu-group rightMenu-line hide" id="menu-paste"><a class="rightMenu-item" href="javascript:rmf.paste()"><i class="fa fa-copy"></i><span>粘贴</span></a></div><div class="rightMenu-group rightMenu-line hide" id="menu-post"><a class="rightMenu-item" href="javascript:rmf.copyWordsLink()"><i class="fa fa-link"></i><span>复制本文地址</span></a></div><div class="rightMenu-group rightMenu-line hide" id="menu-to"><a class="rightMenu-item" href="javascript:rmf.openWithNewTab()"><i class="fa fa-window-restore"></i><span>新窗口打开</span></a><a class="rightMenu-item" href="javascript:rmf.open()"><i class="fa fa-link"></i><span>转到链接</span></a><a class="rightMenu-item" href="javascript:rmf.copyLink()"><i class="fa fa-copy"></i><span>复制链接</span></a></div><div class="rightMenu-group rightMenu-line hide" id="menu-img"><a class="rightMenu-item" href="javascript:rmf.saveAs()"><i class="fa fa-download"></i><span>保存图片</span></a><a class="rightMenu-item" href="javascript:rmf.openWithNewTab()"><i class="fa fa-window-restore"></i><span>在新窗口打开</span></a><a class="rightMenu-item" href="javascript:rmf.click()"><i class="fa fa-arrows-alt"></i><span>全屏显示</span></a><a class="rightMenu-item" href="javascript:rmf.copyLink()"><i class="fa fa-copy"></i><span>复制图片链接</span></a></div><div class="rightMenu-group rightMenu-line"><a class="rightMenu-item" href="javascript:randomPost()"><i class="fa fa-paper-plane"></i><span>随便逛逛</span></a><a class="rightMenu-item" href="javascript:rmf.switchDarkMode();"><i class="fa fa-moon"></i><span>昼夜切换</span></a><a class="rightMenu-item" href="javascript:rmf.translate();"><i class="iconfont icon-fanti"></i><span>繁简转换</span></a><a class="rightMenu-item" href="javascript:rmf.switchReadMode();"><i class="fa fa-book"></i><span>阅读模式</span></a><a class="rightMenu-item" href="javascript:pjax.loadUrl("/privacy/");"><i class="fa fa-info-circle"></i><span>隐私声明</span></a><a class="rightMenu-item" href="javascript:pjax.loadUrl("/cookie/");"><i class="fa fa-info-circle"></i><span>Cookie协议</span></a><a class="rightMenu-item" href="javascript:pjax.loadUrl("/cc/");"><i class="fa fa-info-circle"></i><span>版权声明</span></a></div></div><!-- hexo injector body_end start --><script data-pjax>
|
||||
function butterfly_swiper_injector_config(){
|
||||
var parent_div_git = document.getElementById('recent-posts');
|
||||
var item_html = '<div class="recent-post-item" style="height: auto;width: 100%"><div class="blog-slider swiper-container-fade swiper-container-horizontal" id="swiper_container"><div class="blog-slider__wrp swiper-wrapper" style="transition-duration: 0ms;"><div class="blog-slider__item swiper-slide" style="background:url(https://pic.biss.click/image/fca16741-64fa-495b-aa5e-a2ef077461ef.webp);border-radius:12px;opacity: 1; transform: translate3d(0px, 0px, 0px); transition-duration: 0ms;"><div class="blog-slider__content"><span class="blog-slider__code">2026-02-23</span><a class="blog-slider__title" onclick="pjax.loadUrl("posts/56f57c0b/");" href="javascript:void(0);" alt="">自建renovate-bot</a><div class="blog-slider__text">还不知道怎么描述哦</div></div></div><div class="blog-slider__item swiper-slide" style="background:url(https://pic.biss.click/image/63a5c345-cb40-4e92-bc7b-7dc4daaf5b74.webp);border-radius:12px;opacity: 1; transform: translate3d(0px, 0px, 0px); transition-duration: 0ms;"><div class="blog-slider__content"><span class="blog-slider__code">2025-09-28</span><a class="blog-slider__title" onclick="pjax.loadUrl("posts/b57500e9/");" href="javascript:void(0);" alt="">在Openwrt上安装AdguardHome</a><div class="blog-slider__text">还不知道怎么描述哦</div></div></div><div class="blog-slider__item swiper-slide" style="background:url(https://pic.biss.click/image/961bc881-cb0a-4ab7-ace5-9990e71c30a0.webp);border-radius:12px;opacity: 1; transform: translate3d(0px, 0px, 0px); transition-duration: 0ms;"><div class="blog-slider__content"><span class="blog-slider__code">2026-02-07</span><a class="blog-slider__title" onclick="pjax.loadUrl("posts/34725d47/");" href="javascript:void(0);" alt="">安装gitea</a><div class="blog-slider__text">还不知道怎么描述哦</div></div></div></div><div class="blog-slider__pagination swiper-pagination-clickable swiper-pagination-bullets"></div><div class="swiper-button-prev"></div><div class="swiper-button-next"></div></div></div>';
|
||||
console.log('已挂载butterfly_swiper')
|
||||
parent_div_git.insertAdjacentHTML("afterbegin",item_html)
|
||||
}
|
||||
var elist = 'undefined'.split(',');
|
||||
var cpage = location.pathname;
|
||||
var epage = '/';
|
||||
var flag = 0;
|
||||
|
||||
for (var i=0;i<elist.length;i++){
|
||||
if (cpage.includes(elist[i])){
|
||||
flag++;
|
||||
}
|
||||
}
|
||||
|
||||
if ((epage ==='all')&&(flag == 0)){
|
||||
butterfly_swiper_injector_config();
|
||||
}
|
||||
else if (epage === cpage){
|
||||
butterfly_swiper_injector_config();
|
||||
}
|
||||
</script><script defer src="https://cdnjs.cloudflare.com/ajax/libs/Swiper/5.4.5/js/swiper.min.js"></script><script defer data-pjax src="https://npm.elemecdn.com/hexo-butterfly-swiper-lyx/lib/swiper_init.js"></script><!-- hexo injector body_end end --></body></html>
|
||||
@@ -0,0 +1,222 @@
|
||||
/* 浅色主题变量覆盖 -------------------------------- */
|
||||
:root {
|
||||
--main: #a0d2eb; /* 主强调色:柔和天空蓝 */
|
||||
--main-op: rgba(160, 210, 235, 0.6);
|
||||
--main-op-deep: rgba(160, 210, 235, 0.4);
|
||||
--main-op-light: rgba(160, 210, 235, 0.2);
|
||||
|
||||
--card-bg: #fdfdfd; /* 卡片背景:几乎白 */
|
||||
--fontcolor: #444; /* 主文本:深灰 */
|
||||
--secondtext: #999; /* 次级文本:浅灰 */
|
||||
}
|
||||
.card-widget {
|
||||
padding: 10px!important;
|
||||
max-height: calc(100vh - 100px);
|
||||
}
|
||||
.card-times a, .card-times div {
|
||||
color: var(--fontcolor);
|
||||
}
|
||||
|
||||
#card-widget-calendar .item-content {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#calendar-area-left {
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
#calendar-area-right {
|
||||
width: 55%;
|
||||
}
|
||||
|
||||
#calendar-area-left, #calendar-area-right {
|
||||
height: 100%;
|
||||
padding: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
#calendar-main {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#calendar-week {
|
||||
height: 1.2rem;
|
||||
font-size: 14px;
|
||||
letter-spacing: 1px;
|
||||
font-weight: 700;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#calendar-date {
|
||||
height: 3rem;
|
||||
line-height: 1.3;
|
||||
font-size: 64px;
|
||||
letter-spacing: 3px;
|
||||
color: var(--main);
|
||||
font-weight: 700;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
position: relative;
|
||||
top: calc(50% - 2.1rem);
|
||||
}
|
||||
|
||||
#calendar-lunar, #calendar-solar {
|
||||
height: 1rem;
|
||||
font-size: 12px;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
#calendar-solar {
|
||||
bottom: 2.1rem;
|
||||
}
|
||||
|
||||
#calendar-lunar {
|
||||
bottom: 1rem;
|
||||
color: var(--secondtext);
|
||||
}
|
||||
|
||||
#calendar-main a {
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
border-radius: 50%;
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#calendar-main a.now {
|
||||
background: var(--main);
|
||||
color: var(--card-bg);
|
||||
}
|
||||
|
||||
#calendar-main .calendar-rh a {
|
||||
color: var(--secondtext);
|
||||
}
|
||||
|
||||
.calendar-r0, .calendar-r1, .calendar-r2, .calendar-r3, .calendar-r4, .calendar-r5, .calendar-rh {
|
||||
height: 1.2rem;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.calendar-d0, .calendar-d1, .calendar-d2, .calendar-d3, .calendar-d4, .calendar-d5, .calendar-d6 {
|
||||
width: calc(100% / 7);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#card-widget-schedule .item-content {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#schedule-area-left, #schedule-area-right {
|
||||
height: 100px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#schedule-area-left {
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
#schedule-area-right {
|
||||
width: 70%;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.schedule-r0, .schedule-r1, .schedule-r2 {
|
||||
height: 2rem;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.schedule-d0 {
|
||||
width: 30px;
|
||||
margin-right: 5px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.schedule-d1 {
|
||||
width: calc(100% - 35px);
|
||||
height: 1.5rem;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
progress::-webkit-progress-bar {
|
||||
background: linear-gradient(to right, var(--main-op-deep), var(--main-op), var(--main-op-light));
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
progress::-webkit-progress-value {
|
||||
background: var(--main);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.aside-span1, .aside-span2 {
|
||||
height: 1rem;
|
||||
font-size: 12px;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.aside-span1 {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.aside-span2 {
|
||||
right: 20px;
|
||||
color: var(--secondtext);
|
||||
}
|
||||
|
||||
.aside-span2 a {
|
||||
margin: 0 3px;
|
||||
}
|
||||
|
||||
#pBar_month, #pBar_week, #pBar_year {
|
||||
width: 100%;
|
||||
border-radius: 5px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#schedule-date, #schedule-days, #schedule-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#schedule-title {
|
||||
height: 25px;
|
||||
line-height: 1;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
#schedule-days {
|
||||
height: 40px;
|
||||
line-height: 1;
|
||||
font-size: 30px;
|
||||
font-weight: 900;
|
||||
color: var(--main);
|
||||
}
|
||||
|
||||
#schedule-date {
|
||||
height: 20px;
|
||||
line-height: 1;
|
||||
font-size: 12px;
|
||||
color: var(--secondtext);
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
#nav-right{
|
||||
flex:1 1 auto;
|
||||
justify-content: flex-end;
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
flex-wrap:nowrap;
|
||||
}
|
||||
|
||||
/* 导航栏居中 */
|
||||
|
||||
#sidebar #sidebar-menus .menus_items .menus_item {
|
||||
margin: 10px 0;
|
||||
}
|
||||
#sidebar #sidebar-menus .menus_items a.site-page {
|
||||
padding-left: 0;
|
||||
}
|
||||
#sidebar #sidebar-menus .menus_items .site-page {
|
||||
position: relative;
|
||||
display: block;
|
||||
padding: 6px 30px 6px 22px;
|
||||
color: var(--font-color);
|
||||
font-size: 1.15em;
|
||||
border: var(--style-border-always);
|
||||
background: var(--icat-card-bg);
|
||||
font-size: 14px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
#sidebar #sidebar-menus .menus_items .site-page i:first-child {
|
||||
text-align: left;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
#nav #menus {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
margin: 0;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
#nav #blog-info {
|
||||
flex-wrap: nowrap;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
z-index: 102;
|
||||
max-width: fit-content;
|
||||
}
|
||||
@media screen and (max-width: 900px) {
|
||||
#nav {
|
||||
padding: 0 15px;
|
||||
}
|
||||
#nav-group {
|
||||
padding: 0 0.2rem;
|
||||
}
|
||||
#rightside {
|
||||
right: -42px;
|
||||
}
|
||||
}
|
||||
/* IPAD菜单栏调整 */
|
||||
|
||||
/* 1. 容器溢出穿透 */
|
||||
#nav, #blog-info, #nav-group {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
#ls-menu-container {
|
||||
position: relative !important;
|
||||
display: inline-flex !important;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
padding: 0 15px;
|
||||
cursor: pointer !important;
|
||||
z-index: 2000 !important;
|
||||
}
|
||||
|
||||
/* 2. 悬浮面板:宽大且高不透明度 */
|
||||
#ls-menu-panel {
|
||||
position: absolute !important;
|
||||
top: 100% !important;
|
||||
left: 0 !important;
|
||||
margin-top: 15px !important;
|
||||
width: 420px;
|
||||
padding: 24px;
|
||||
border-radius: 18px;
|
||||
|
||||
/* 高不透明度磨砂玻璃 (0.9) */
|
||||
background: rgba(255, 255, 255, 0.95) !important;
|
||||
backdrop-filter: blur(20px) saturate(180%) !important;
|
||||
-webkit-backdrop-filter: blur(20px) saturate(180%) !important;
|
||||
|
||||
border: 1px solid rgba(255, 255, 255, 0.5) !important;
|
||||
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.18) !important;
|
||||
|
||||
opacity: 0 !important;
|
||||
visibility: hidden !important;
|
||||
transform: translateY(12px) !important;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||
z-index: 999999 !important;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* 3. 透明感应桥梁 */
|
||||
#ls-menu-panel::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 25px;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
/* 4. 触发效果 */
|
||||
#ls-menu-container:hover #ls-menu-panel {
|
||||
opacity: 1 !important;
|
||||
visibility: visible !important;
|
||||
transform: translateY(0) !important;
|
||||
}
|
||||
|
||||
#ls-menu-container:hover i.fa-fingerprint {
|
||||
color: #49b1f5;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/* 5. 内部网格:保持两列 */
|
||||
.ls-section { margin-bottom: 22px; }
|
||||
.ls-section:last-child { margin-bottom: 0; }
|
||||
.ls-title {
|
||||
color: #333;
|
||||
font-weight: 800;
|
||||
font-size: 15px;
|
||||
margin-bottom: 12px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.ls-grid {
|
||||
display: grid !important;
|
||||
grid-template-columns: repeat(2, 1fr); /* 恢复为两列 */
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.ls-grid a {
|
||||
color: #444 !important;
|
||||
font-size: 14px !important;
|
||||
padding: 10px 12px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: all 0.2s ease;
|
||||
background: rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
.ls-grid a:hover {
|
||||
background: #49b1f5 !important;
|
||||
color: #fff !important;
|
||||
transform: translateX(4px); /* 悬停时轻微右移,增加动感 */
|
||||
}
|
||||
|
||||
.ls-grid a i {
|
||||
margin-right: 12px;
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
/* 6. 深色模式适配 */
|
||||
[data-theme='dark'] #ls-menu-panel {
|
||||
background: rgba(30, 30, 30, 0.95) !important;
|
||||
border-color: rgba(255, 255, 255, 0.1) !important;
|
||||
}
|
||||
[data-theme='dark'] .ls-title { color: #eee; }
|
||||
[data-theme='dark'] .ls-grid a {
|
||||
color: #ccc !important;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
#nav.show-title .nav-page-title {
|
||||
display: flex !important;
|
||||
opacity: 1 !important;
|
||||
visibility: visible !important;
|
||||
position: absolute !important;
|
||||
left: 50% !important;
|
||||
transform: translateX(-50%) !important;
|
||||
white-space: nowrap !important;
|
||||
z-index: 1000 !important;
|
||||
}
|
||||
|
||||
#nav.show-title #menus,
|
||||
#nav.show-title .nav-site-title {
|
||||
opacity: 0 !important;
|
||||
visibility: hidden !important;
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
#nav .nav-page-title {
|
||||
display: none;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
#nav #blog-info {
|
||||
overflow: visible !important;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
[data-theme=light] {
|
||||
--fontcolor: #363636;
|
||||
--text: rgba(60, 60, 67, 0.6);
|
||||
--bg: #edf0f7;
|
||||
}
|
||||
|
||||
[data-theme=dark] {
|
||||
--fontcolor: #F7F7FA;
|
||||
--text: #a1a2b8;
|
||||
--bg: #30343f;
|
||||
}
|
||||
div#poem_sentence{
|
||||
text-align: center;
|
||||
font-family: serif,cursive;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 0.5rem;
|
||||
padding: 1rem;
|
||||
border-radius: 12px;
|
||||
background: var(--bg);
|
||||
min-height: 62px;
|
||||
}
|
||||
|
||||
div#poem_info{
|
||||
display: flex;
|
||||
color: var(--text);
|
||||
font-size: 100%;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
div#poem_author{
|
||||
order: 1;
|
||||
padding: 2px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
div#poem_dynasty{
|
||||
order: 0;
|
||||
padding: 2px 4px 2px 6px;
|
||||
background: var(-btn-bg);
|
||||
color: var(--fontcolor);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
#card-poem{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0.5rem!important;
|
||||
min-height: 130px;
|
||||
background: linear-gradient(-45deg, rgba(255, 255, 255, .7),
|
||||
rgba(255, 255, 255, .8),
|
||||
rgba(255, 255, 255, .8),
|
||||
rgba(255, 255, 255, .7))!important;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
[data-theme=dark] #card-poem{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0.5rem!important;
|
||||
min-height: 130px;
|
||||
background: linear-gradient(-45deg, rgba(24, 40, 72, .7),
|
||||
rgba(35, 37, 58, .8),
|
||||
rgba(35, 37, 58, .8),
|
||||
rgba(24, 40, 72, .7))!important;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
/* ================= 主题变量 ================= */
|
||||
:root {
|
||||
--card-bg: #fff;
|
||||
--card-border: 1px solid #e3e8f7;
|
||||
--card-box-shadow: 0 3px 8px 6px rgba(7,17,27,0.09);
|
||||
--card-hover-box-shadow: 0 3px 8px 6px rgba(7,17,27,0.2);
|
||||
--card-secondbg: #f1f3f8;
|
||||
--button-hover-bg: #2679cc;
|
||||
--text: #4c4948;
|
||||
--button-bg: #f1f3f8;
|
||||
--fancybox-bg: rgba(255,255,255,0.5);
|
||||
}
|
||||
|
||||
[data-theme=dark] {
|
||||
--card-bg: #181818;
|
||||
--card-secondbg: #30343f;
|
||||
--card-border: 1px solid #42444a;
|
||||
--card-box-shadow: 0 3px 8px 6px rgba(7,17,27,0.09);
|
||||
--card-hover-box-shadow: 0 3px 8px 6px rgba(7,17,27,0.2);
|
||||
--button-bg: #30343f;
|
||||
--button-hover-bg: #2679cc;
|
||||
--text: rgba(255,255,255,0.702);
|
||||
--fancybox-bg: rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
/* ================= 卡片布局 ================= */
|
||||
#talk {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-wrap: wrap; /* 允许换行 */
|
||||
align-items: flex-start; /* 防止高度拉伸导致变形 */
|
||||
}
|
||||
|
||||
#talk .talk_item {
|
||||
width: calc(33.333% - 6px);
|
||||
background: var(--card-bg);
|
||||
border: var(--card-border);
|
||||
box-shadow: var(--card-box-shadow);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-right: 9px;
|
||||
margin-bottom: 9px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: box-shadow .3s ease-in-out;
|
||||
}
|
||||
|
||||
#talk .talk_item:hover {
|
||||
box-shadow: var(--card-hover-box-shadow);
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
#talk .talk_item {
|
||||
width: calc(50% - 5px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 450px) {
|
||||
#talk .talk_item {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* 给 Meting 播放器预留约 80px 的固定高度 */
|
||||
meting-js {
|
||||
display: block;
|
||||
min-height: 100px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* 如果有视频,也预留一下比例高度 */
|
||||
.talk_content iframe {
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
/* ================= 头部信息 ================= */
|
||||
#talk .talk_meta,
|
||||
#talk .talk_bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#talk .talk_meta {
|
||||
width: 100%;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px dashed grey;
|
||||
}
|
||||
|
||||
#talk .talk_meta .avatar {
|
||||
margin: 0 !important;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
#talk .talk_meta .info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
#talk .talk_meta .info .talk_nick {
|
||||
color: #6dbdc3;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
#talk .talk_meta .info svg.is-badge.icon {
|
||||
width: 15px;
|
||||
padding-top: 3px;
|
||||
}
|
||||
|
||||
#talk .talk_meta .info span.talk_date {
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
/* ================= 内容区域 ================= */
|
||||
#talk .talk_item .talk_content {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#talk .talk_item .talk_content .zone_imgbox {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
--w: calc(25% - 8px);
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#talk .talk_item .talk_content .zone_imgbox a {
|
||||
display: block;
|
||||
border-radius: 12px;
|
||||
width: var(--w);
|
||||
aspect-ratio: 1/1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#talk .talk_item .talk_content .zone_imgbox a:first-child {
|
||||
width: 100%;
|
||||
aspect-ratio: 1.8;
|
||||
}
|
||||
|
||||
#talk .talk_item .talk_content .zone_imgbox img {
|
||||
border-radius: 10px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0 !important;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
/* ================= 底部信息 ================= */
|
||||
#talk .talk_item .talk_bottom {
|
||||
margin-top: 15px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px dashed grey;
|
||||
justify-content: space-between;
|
||||
opacity: .9;
|
||||
}
|
||||
|
||||
#talk .talk_item .talk_bottom .icon {
|
||||
float: right;
|
||||
transition: all .3s;
|
||||
}
|
||||
|
||||
#talk .talk_item .talk_bottom .icon:hover {
|
||||
color: #49b1f5;
|
||||
}
|
||||
|
||||
#talk .talk_item .talk_bottom span.talk_tag,
|
||||
#talk .talk_item .talk_bottom span.location_tag {
|
||||
font-size: 14px;
|
||||
background-color: var(--card-secondbg);
|
||||
border-radius: 12px;
|
||||
padding: 3px 15px 3px 10px;
|
||||
transition: box-shadow 0.3s ease;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
#talk .talk_item .talk_bottom span.location_tag {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
#talk .talk_item .talk_bottom span.talk_tag:hover,
|
||||
#talk .talk_item .talk_bottom span.location_tag:hover {
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* ================= 文本链接 ================= */
|
||||
#talk .talk_item .talk_content > a {
|
||||
margin: 0 3px;
|
||||
color: #ff7d73 !important;
|
||||
}
|
||||
|
||||
#talk .talk_item .talk_content > a:hover {
|
||||
text-decoration: none !important;
|
||||
color: #ff5143 !important;
|
||||
}
|
||||
|
||||
/* ================= 响应式 ================= */
|
||||
@media screen and (max-width: 900px) {
|
||||
#talk .talk_item .talk_content .zone_imgbox {
|
||||
--w: calc(33% - 5px);
|
||||
}
|
||||
|
||||
#talk .talk_item #post-comment {
|
||||
margin: 0 3px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.zone_imgbox {
|
||||
gap: 6px;
|
||||
--w: calc(50% - 3px);
|
||||
}
|
||||
|
||||
span.talk_date {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ================= 豆瓣卡片 ================= */
|
||||
#talk .talk_item .talk_content .douban-card {
|
||||
margin-top: 10px !important;
|
||||
text-decoration: none;
|
||||
align-items: center;
|
||||
border-radius: 12px;
|
||||
color: #faebd7;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 10px;
|
||||
max-width: 400px;
|
||||
overflow: hidden;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* ================= 外链卡片 ================= */
|
||||
#talk .talk_item .talk_content .shuoshuo-external-link {
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
margin-top: 10px;
|
||||
border-radius: 12px;
|
||||
background-color: var(--card-secondbg);
|
||||
color: var(--card-text);
|
||||
border: var(--card-border);
|
||||
transition: background-color .3s ease-in-out;
|
||||
}
|
||||
|
||||
.shuoshuo-external-link:hover {
|
||||
background-color: var(--button-hover-bg);
|
||||
}
|
||||
|
||||
.shuoshuo-external-link .external-link {
|
||||
display: flex;
|
||||
color: var(--text) !important;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.shuoshuo-external-link .external-link:hover {
|
||||
color: white !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.shuoshuo-external-link .external-link-left {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin: 10px;
|
||||
border-radius: 12px;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.shuoshuo-external-link .external-link-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: calc(100% - 80px);
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.shuoshuo-external-link .external-link-right .external-link-title {
|
||||
font-size: 1.0rem;
|
||||
font-weight: 800;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.shuoshuo-external-link .external-link-right i {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
/* ================= 结尾 ================= */
|
||||
.limit {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/* maintop */
|
||||
|
||||
#main_top {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
z-index: 1;
|
||||
max-width: 1400px;
|
||||
margin: 20px auto;
|
||||
width: 100%;
|
||||
padding: 0 15px;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.hide-aside #main_top {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.hide-aside #main_top #bber-talk {
|
||||
max-width: 936px;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 2000px) {
|
||||
.hide-aside #main_top #bber-talk {
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
#main_top {
|
||||
max-width: 70%;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1210px) {
|
||||
.hide-aside #main_top {
|
||||
padding: 0 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.hide-aside #main_top {
|
||||
width: 100%;
|
||||
padding: 0 15px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.hide-aside #main_top {
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
div#main_top {
|
||||
margin-top: 20px;
|
||||
padding: 0 5px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 重点修改:bber-talk 的磨砂玻璃背景 */
|
||||
#bber-talk {
|
||||
/* 统一磨砂玻璃效果 */
|
||||
background: linear-gradient(-45deg, rgba(255, 255, 255, .7), rgba(255, 255, 255, .8), rgba(255, 255, 255, .8), rgba(255, 255, 255, .7)) !important;
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
|
||||
border-radius: 12px; /* 给哔哔栏加一点圆角,更符合玻璃质感 */
|
||||
border: 1px solid rgba(255, 255, 255, 0.3); /* 增加边缘高光 */
|
||||
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
min-height: 50px;
|
||||
padding: .5rem 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
font-weight: 700;
|
||||
transition: all .3s ease-in-out;
|
||||
}
|
||||
|
||||
/* 暗黑模式适配 */
|
||||
[data-theme=dark] #bber-talk {
|
||||
background: linear-gradient(-45deg, rgba(24, 40, 72, .7), rgba(35, 37, 58, .8), rgba(35, 37, 58, .8), rgba(24, 40, 72, .7)) !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
#bber-talk:hover {
|
||||
transform: translateY(-2px); /* 悬停微动,增加交互感 */
|
||||
box-shadow: 0 8px 20px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
#bber-talk,
|
||||
#bber-talk a {
|
||||
color: var(--font-color);
|
||||
}
|
||||
|
||||
#bber-talk svg.icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: -.15em;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
#bber-talk .item i {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
#bber-talk > i {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
#bber-talk .talk-list {
|
||||
flex: 1;
|
||||
max-height: 32px;
|
||||
font-size: 16px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#bber-talk .talk-list:hover {
|
||||
color: var(--default-bg-color);
|
||||
transition: all .2s ease-in-out;
|
||||
}
|
||||
|
||||
#bber-talk .talk-list li {
|
||||
list-style: none;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 770px) {
|
||||
#bber-talk .talk-list {
|
||||
text-align: center;
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,699 @@
|
||||
/* #card-newest-comments img {border-radius: 10px; /* 设置最新评论圆角半径为10px,可以根据需要调整} */
|
||||
|
||||
/* 侧边栏个人信息卡片动态渐变色 */
|
||||
#aside-content>.card-widget.card-info {
|
||||
position: relative;
|
||||
z-index: 1; /* 确保原内容在上层显示 */
|
||||
background: linear-gradient(-45deg, rgba(255, 255, 255, .7),
|
||||
rgba(255, 255, 255, .8),
|
||||
rgba(255, 255, 255, .8),
|
||||
rgba(255, 255, 255, .7));
|
||||
backdrop-filter: blur(10px);
|
||||
/* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */
|
||||
}
|
||||
|
||||
#aside-content > .card-widget.card-info::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(to bottom,
|
||||
rgba(255, 255, 255, 0.0),
|
||||
rgba(255, 255, 255, 0.3),
|
||||
rgba(255, 255, 255, 0.6),
|
||||
rgba(255, 255, 255, 0.7)); /* 鼠标悬停时显示的背景图片 */
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
opacity: 0.5;
|
||||
transition: opacity 0.8s; /* 过渡效果持续时间 */
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
[data-theme=dark] #aside-content > .card-widget.card-info::before {
|
||||
background: linear-gradient(to bottom,
|
||||
rgba(24, 40, 72, .1),
|
||||
rgba(35, 37, 58, .3),
|
||||
rgba(35, 37, 58, .6),
|
||||
rgba(24, 40, 72, .7)); /* 鼠标悬停时显示的背景图片 */
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
#aside-content > .card-widget.card-info:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
/* 卡片动态效果,好好看哈哈 */
|
||||
#aside-content div.card-widget.card-info .avatar-img {
|
||||
transition: opacity 0.3s ease; /* 添加渐变效果 */
|
||||
}
|
||||
|
||||
#aside-content div.card-widget.card-info .avatar-img:hover {
|
||||
opacity: 0; /* 鼠标移上去时完全透明 */
|
||||
}
|
||||
|
||||
#card-info-btn:hover, #card-info-btn:hover .icon, #card-info-btn:hover span {
|
||||
transform: scale(1.1); /* 鼠标悬停时放大到110% */
|
||||
}
|
||||
|
||||
[data-theme=dark] #aside-content>.card-widget.card-info {
|
||||
background: linear-gradient(-45deg, rgba(24, 40, 72, .6),
|
||||
rgba(35, 37, 58, .5),
|
||||
rgba(35, 37, 58, .5),
|
||||
rgba(24, 40, 72, .6));
|
||||
backdrop-filter: blur(10px);
|
||||
/* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */
|
||||
}
|
||||
|
||||
/* 侧边栏公告栏卡片渐变色 */
|
||||
#aside-content>.card-widget.card-announcement {
|
||||
background: linear-gradient(-45deg, rgba(255, 255, 255, .7),
|
||||
rgba(255, 255, 255, .8),
|
||||
rgba(255, 255, 255, .8),
|
||||
rgba(255, 255, 255, .7));
|
||||
backdrop-filter: blur(10px);
|
||||
/* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */
|
||||
}
|
||||
|
||||
[data-theme=dark] #aside-content>.card-widget.card-announcement {
|
||||
background: linear-gradient(-45deg, rgba(24, 40, 72, .7),
|
||||
rgba(35, 37, 58, .8),
|
||||
rgba(35, 37, 58, .8),
|
||||
rgba(24, 40, 72, .7));
|
||||
backdrop-filter: blur(10px);
|
||||
/* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */
|
||||
}
|
||||
|
||||
/* 侧边栏目录最新文章卡片渐变色 */
|
||||
#aside-content>.sticky_layout>.card-widget {
|
||||
background: linear-gradient(-45deg, rgba(255, 255, 255, .7),
|
||||
rgba(255, 255, 255, .8),
|
||||
rgba(255, 255, 255, .8),
|
||||
rgba(255, 255, 255, .7));
|
||||
backdrop-filter: blur(10px);
|
||||
/* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */
|
||||
}
|
||||
|
||||
[data-theme=dark] #aside-content>.sticky_layout>.card-widget {
|
||||
background: linear-gradient(-45deg, rgba(24, 40, 72, .7),
|
||||
rgba(35, 37, 58, .8),
|
||||
rgba(35, 37, 58, .8),
|
||||
rgba(24, 40, 72, .7));
|
||||
backdrop-filter: blur(10px);
|
||||
/* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */
|
||||
}
|
||||
|
||||
/* 侧边栏欢迎卡片渐变色 */
|
||||
#aside-content>.card-widget.welcomeBoxClass {
|
||||
background: linear-gradient(-45deg, rgba(255, 255, 255, .7),
|
||||
rgba(255, 255, 255, .8),
|
||||
rgba(255, 255, 255, .8),
|
||||
rgba(255, 255, 255, .7));
|
||||
backdrop-filter: blur(10px);
|
||||
/* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */
|
||||
}
|
||||
|
||||
[data-theme=dark] #aside-content>.card-widget.welcomeBoxClass {
|
||||
background: linear-gradient(-45deg, rgba(24, 40, 72, .7),
|
||||
rgba(35, 37, 58, .8),
|
||||
rgba(35, 37, 58, .8),
|
||||
rgba(24, 40, 72, .7));
|
||||
backdrop-filter: blur(10px);
|
||||
/* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */
|
||||
}
|
||||
|
||||
/* 公告卡片渐变色 */
|
||||
#recent-posts>#noticeList {
|
||||
background: linear-gradient(-45deg, rgba(255, 255, 255, .7),
|
||||
rgba(255, 255, 255, .8),
|
||||
rgba(255, 255, 255, .8),
|
||||
rgba(255, 255, 255, .7));
|
||||
backdrop-filter: blur(10px);
|
||||
/* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */
|
||||
}
|
||||
|
||||
[data-theme=dark] #recent-posts>#noticeList {
|
||||
background: linear-gradient(-45deg, rgba(24, 40, 72, .7),
|
||||
rgba(35, 37, 58, .8),
|
||||
rgba(35, 37, 58, .8),
|
||||
rgba(24, 40, 72, .7));
|
||||
backdrop-filter: blur(10px);
|
||||
/* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */
|
||||
}
|
||||
|
||||
/* 个人信息Follow me按钮 */
|
||||
#aside-content>.card-widget.card-info>#card-info-btn {
|
||||
background-color: #3eb8be;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
/*文章页面*/
|
||||
.layout>#post {
|
||||
background: linear-gradient(-45deg, rgba(255, 255, 255, .7),
|
||||
rgba(255, 255, 255, .8),
|
||||
rgba(255, 255, 255, .8),
|
||||
rgba(255, 255, 255, .7));
|
||||
backdrop-filter: blur(10px);
|
||||
/* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */
|
||||
}
|
||||
|
||||
[data-theme=dark] .layout>#post {
|
||||
background: linear-gradient(-45deg, rgba(24, 40, 72, .7),
|
||||
rgba(35, 37, 58, .8),
|
||||
rgba(35, 37, 58, .8),
|
||||
rgba(24, 40, 72, .7));
|
||||
backdrop-filter: blur(10px);
|
||||
/* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */
|
||||
}
|
||||
|
||||
/*分类条*/
|
||||
#recent-posts>.category-bar {
|
||||
background: linear-gradient(-45deg, rgba(255, 255, 255, .7),
|
||||
rgba(255, 255, 255, .8),
|
||||
rgba(255, 255, 255, .8),
|
||||
rgba(255, 255, 255, .7))!important;
|
||||
backdrop-filter: blur(10px)!important;
|
||||
/* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */
|
||||
}
|
||||
|
||||
[data-theme=dark] #recent-posts>.category-bar {
|
||||
background: linear-gradient(-45deg, rgba(24, 40, 72, .7),
|
||||
rgba(35, 37, 58, .8),
|
||||
rgba(35, 37, 58, .8),
|
||||
rgba(24, 40, 72, .7))!important;
|
||||
backdrop-filter: blur(10px)!important;
|
||||
/* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */
|
||||
}
|
||||
|
||||
/*主页文章预览页面*/
|
||||
#recent-posts>.recent-post-items>.recent-post-item {
|
||||
background: linear-gradient(-45deg, rgba(255, 255, 255, .7),
|
||||
rgba(255, 255, 255, .8),
|
||||
rgba(255, 255, 255, .8),
|
||||
rgba(255, 255, 255, .7))!important;
|
||||
backdrop-filter: blur(10px)!important;
|
||||
/* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */
|
||||
}
|
||||
|
||||
[data-theme=dark] #recent-posts>.recent-post-items>.recent-post-item {
|
||||
background: linear-gradient(-45deg, rgba(24, 40, 72, .7),
|
||||
rgba(35, 37, 58, .8),
|
||||
rgba(35, 37, 58, .8),
|
||||
rgba(24, 40, 72, .7))!important;
|
||||
backdrop-filter: blur(10px)!important;
|
||||
/* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */
|
||||
}
|
||||
|
||||
/*分类页面*/
|
||||
.layout>#page {
|
||||
background: linear-gradient(-45deg, rgba(255, 255, 255, .7),
|
||||
rgba(255, 255, 255, .8),
|
||||
rgba(255, 255, 255, .8),
|
||||
rgba(255, 255, 255, .7));
|
||||
backdrop-filter: blur(10px);
|
||||
/* -webkit-backdrop-filter: blur(10px); 兼容性前缀,适用于一些旧版本的浏览器 */
|
||||
}
|
||||
|
||||
[data-theme=dark] .layout>#page {
|
||||
background: linear-gradient(-45deg, rgba(24, 40, 72, .7),
|
||||
rgba(35, 37, 58, .8),
|
||||
rgba(35, 37, 58, .8),
|
||||
rgba(24, 40, 72, .7));
|
||||
backdrop-filter: blur(10px);
|
||||
/* -webkit-backdrop-filter: blur(10px); 兼容性前缀,适用于一些旧版本的浏览器 */
|
||||
}
|
||||
|
||||
|
||||
/*时间轴页面*/
|
||||
.layout>#archive {
|
||||
background: linear-gradient(-45deg, rgba(255, 255, 255, .7),
|
||||
rgba(255, 255, 255, .8),
|
||||
rgba(255, 255, 255, .8),
|
||||
rgba(255, 255, 255, .7));
|
||||
backdrop-filter: blur(10px);
|
||||
/* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */
|
||||
}
|
||||
|
||||
[data-theme=dark] .layout>#archive {
|
||||
background: linear-gradient(-45deg, rgba(24, 40, 72, .7),
|
||||
rgba(35, 37, 58, .8),
|
||||
rgba(35, 37, 58, .8),
|
||||
rgba(24, 40, 72, .7));
|
||||
backdrop-filter: blur(10px);
|
||||
/* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */
|
||||
}
|
||||
|
||||
/*分类点进去的页面*/
|
||||
.layout>#category {
|
||||
background: linear-gradient(-45deg, rgba(255, 255, 255, .7),
|
||||
rgba(255, 255, 255, .8),
|
||||
rgba(255, 255, 255, .8),
|
||||
rgba(255, 255, 255, .7));
|
||||
backdrop-filter: blur(10px);
|
||||
/* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */
|
||||
}
|
||||
|
||||
[data-theme=dark] .layout>#category {
|
||||
background: linear-gradient(-45deg, rgba(24, 40, 72, .7),
|
||||
rgba(35, 37, 58, .8),
|
||||
rgba(35, 37, 58, .8),
|
||||
rgba(24, 40, 72, .7));
|
||||
backdrop-filter: blur(10px);
|
||||
/* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */
|
||||
}
|
||||
|
||||
/*标签点进去的页面*/
|
||||
.layout>#tag {
|
||||
background: linear-gradient(-45deg, rgba(255, 255, 255, .7),
|
||||
rgba(255, 255, 255, .8),
|
||||
rgba(255, 255, 255, .8),
|
||||
rgba(255, 255, 255, .7));
|
||||
backdrop-filter: blur(10px);
|
||||
/* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */
|
||||
}
|
||||
|
||||
[data-theme=dark] .layout>#tag {
|
||||
background: linear-gradient(-45deg, rgba(24, 40, 72, .7),
|
||||
rgba(35, 37, 58, .8),
|
||||
rgba(35, 37, 58, .8),
|
||||
rgba(24, 40, 72, .7));
|
||||
backdrop-filter: blur(10px);
|
||||
/* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */
|
||||
}
|
||||
|
||||
.layout.hide-aside {
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
/* 设置黑夜的时候,社交按钮为白色 */
|
||||
[data-theme=dark] .social-icon i {
|
||||
color: rgba(188, 188, 188) !important; /* 设置为白色 */
|
||||
}
|
||||
|
||||
.layout{
|
||||
width: 100%;
|
||||
max-width:1400px;
|
||||
}
|
||||
/* 重新定义宽度 */
|
||||
|
||||
.layout > div:first-child {
|
||||
width: 100%;
|
||||
}
|
||||
#post {
|
||||
width: 78%;
|
||||
}
|
||||
.aside-content {
|
||||
max-width: 100%;
|
||||
min-width: 300px;
|
||||
}
|
||||
.layout.hide-aside {
|
||||
max-width: 1400px;
|
||||
}
|
||||
/* 定义是否侧边栏的宽度 */
|
||||
|
||||
#recent-posts > .recent-post-item {
|
||||
height: 19em;
|
||||
border: var(--style-border);
|
||||
}
|
||||
#recent-posts > .recent-post-item >.recent-post-info {
|
||||
padding: 0 56px;
|
||||
width: 64%;
|
||||
}
|
||||
@media screen and (max-width: 768px) {
|
||||
#recent-posts > .recent-post-item {
|
||||
height: auto;
|
||||
}
|
||||
#recent-posts > .recent-post-item >.recent-post-info {
|
||||
padding: 20px 20px 30px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
/* 主页文章列表图片宽度 */
|
||||
|
||||
.loading-img {
|
||||
background: url(https://free.picui.cn/free/2025/08/10/689845496a283.png) no-repeat center center;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
#footer {
|
||||
background-color: rgba(0, 0, 0, 0); /* 修改透明色 */
|
||||
color: #fff;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#footer .footer-other {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.footer-copyright{
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.my-footer-logo {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.my-footer-wave-svg {
|
||||
height: 50px;
|
||||
width: 100%;
|
||||
transform: scale(-1, -1) translateY(-10px); /*;*/
|
||||
}
|
||||
|
||||
.my-footer-wave-path {
|
||||
fill: #177ecd;
|
||||
}
|
||||
|
||||
.my-footer-content {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 1230px;
|
||||
padding: 40px 15px 450px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.my-footer-content-column {
|
||||
box-sizing: border-box;
|
||||
float: left;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
width: 100%;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.my-footer-content-column ul li a {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.my-footer-logo-link {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.my-footer-menu {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.my-footer-menu-name {
|
||||
color: #fffff2;
|
||||
font-size: 15px;
|
||||
font-weight: 900;
|
||||
letter-spacing: .1em;
|
||||
line-height: 18px;
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.my-footer-menu-list {
|
||||
list-style: none;
|
||||
margin-bottom: 0;
|
||||
margin-top: 10px;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.my-footer-menu-list li {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.my-footer-call-to-action-description {
|
||||
color: #fffff2;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.my-footer-call-to-action-button:hover {
|
||||
background-color: #fffff2;
|
||||
color: #00bef0;
|
||||
}
|
||||
|
||||
.button:last-of-type {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.my-footer-call-to-action-button {
|
||||
background-color: #03708c;
|
||||
border-radius: 21px;
|
||||
color: #fffff2;
|
||||
display: inline-block;
|
||||
font-size: 11px;
|
||||
font-weight: 900;
|
||||
letter-spacing: .1em;
|
||||
line-height: 18px;
|
||||
padding: 12px 30px;
|
||||
margin: 0 10px 10px 0;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
transition: background-color .2s;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.my-footer-call-to-action {
|
||||
margin-top: 17px;
|
||||
}
|
||||
|
||||
.my-footer-call-to-action-title {
|
||||
color: #fffff2;
|
||||
font-size: 14px;
|
||||
font-weight: 900;
|
||||
letter-spacing: .1em;
|
||||
line-height: 18px;
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.my-footer-call-to-action-link-wrapper {
|
||||
margin-bottom: 0;
|
||||
margin-top: 10px;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.my-footer-call-to-action-link-wrapper a {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
.my-footer-social-links {
|
||||
bottom: -1px;
|
||||
height: 54px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 236px;
|
||||
}
|
||||
|
||||
.my-footer-social-amoeba-svg {
|
||||
height: 54px;
|
||||
left: 0;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 236px;
|
||||
}
|
||||
|
||||
.my-footer-social-amoeba-path {
|
||||
fill: #03708c;
|
||||
}
|
||||
|
||||
.my-footer-social-link.email {
|
||||
height: 41px;
|
||||
left: 5px;
|
||||
top: 14px;
|
||||
width: 41px;
|
||||
}
|
||||
|
||||
.my-footer-social-link {
|
||||
display: block;
|
||||
padding: 10px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.hidden-link-text {
|
||||
position: absolute;
|
||||
clip: rect(1px 1px 1px 1px);
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
-webkit-clip-path: inset(0px 0px 99.9% 99.9%);
|
||||
clip-path: inset(0px 0px 99.9% 99.9%);
|
||||
overflow: hidden;
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
.my-footer-social-icon-svg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.my-footer-social-icon-path {
|
||||
fill: #fffff2;
|
||||
transition: fill .2s;
|
||||
}
|
||||
|
||||
.my-footer-social-link.follow {
|
||||
height: 42px;
|
||||
left: 124px;
|
||||
top: 13px;
|
||||
width: 42px;
|
||||
}
|
||||
|
||||
.my-footer-social-link.rss {
|
||||
height: 43px;
|
||||
left: 178px;
|
||||
top: 10px;
|
||||
width: 43px;
|
||||
}
|
||||
|
||||
.my-footer-social-link.github {
|
||||
height: 62px;
|
||||
left: 54px;
|
||||
top: -4px;
|
||||
width: 62px;
|
||||
}
|
||||
|
||||
.my-footer-copyright {
|
||||
background-color: #03708c;
|
||||
color: #fff;
|
||||
padding: 15px 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.my-footer-copyright-wrapper {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
.my-footer-copyright-text {
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
line-height: 18px;
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.my-footer-copyright-link {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.my-footer-content-div {
|
||||
background: #177ecd
|
||||
}
|
||||
|
||||
.my-footer-svg-div {
|
||||
width: 100%;
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
/* Media Query For different screens */
|
||||
@media (min-width: 320px) and (max-width: 479px) {
|
||||
/* smartphones, portrait iPhone, portrait 480x320 phones (Android) */
|
||||
.my-footer-content {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 1230px;
|
||||
padding: 40px 15px 649px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 不展示logo */
|
||||
.my-footer-logo {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 480px) and (max-width: 599px) {
|
||||
/* smartphones, Android phones, landscape iPhone */
|
||||
.my-footer-content {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 1230px;
|
||||
padding: 40px 15px 738px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.my-footer-logo {
|
||||
padding-left: 177px; /* Qlogo 稍微偏移一点 */
|
||||
padding-right: 170px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 600px) and (max-width: 800px) {
|
||||
/* portrait tablets, portrait iPad, e-readers (Nook/Kindle), landscape 800x480 phones (Android) */
|
||||
.my-footer-content {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 1230px;
|
||||
padding: 40px 15px 758px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@media (min-width: 801px) {
|
||||
/* tablet, landscape iPad, lo-res laptops ands desktops */
|
||||
|
||||
}
|
||||
|
||||
@media (min-width: 1025px) {
|
||||
/* big landscape tablets, laptops, and desktops */
|
||||
|
||||
}
|
||||
|
||||
@media (min-width: 1281px) {
|
||||
/* hi-res laptops and desktops */
|
||||
|
||||
}
|
||||
|
||||
|
||||
@media (min-width: 760px) {
|
||||
.my-footer-content {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 1230px;
|
||||
padding: 10px 15px 237px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
||||
.my-footer-content-column {
|
||||
/*五列的话 19.99 %*/
|
||||
width: 24.99%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 568px) {
|
||||
|
||||
}
|
||||
|
||||
@media (min-width: 600px) and (max-width: 760px) {
|
||||
/* 在这里编写适用于小屏幕的样式 */
|
||||
.my-footer-logo {
|
||||
padding-left: 212px; /* Qlogo 稍微偏移一点 */
|
||||
padding-right: 204px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 页脚波浪的颜色 */
|
||||
[data-theme='dark'] .my-footer-wave-path {
|
||||
fill: #0f3858;
|
||||
}
|
||||
[data-theme='dark'] .my-footer-content-div {
|
||||
background-color: #0f3858;
|
||||
}
|
||||
|
||||
/* 页脚海底的颜色 */
|
||||
[data-theme='dark'] .my-footer-copyright {
|
||||
background-color: #2b3f49;
|
||||
}
|
||||
[data-theme='dark'] .my-footer-social-amoeba-path {
|
||||
fill: #2b3f49;
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/* 1. 强制清空外层容器的背景、边框和阴影 */
|
||||
#swiperBox,
|
||||
#swiper_container,
|
||||
.blog-slider,
|
||||
.swiper-container {
|
||||
padding: 0 !important;
|
||||
margin: 0 auto !important;
|
||||
background: none !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
width: 100% !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
/* 2. 强力锁定比例,防止 JS 动态修改高度 */
|
||||
div#swiper_container {
|
||||
width: 100% !important;
|
||||
|
||||
/* 使用 vw 单位根据屏幕宽度动态计算高度 */
|
||||
/* 16:9 比例下,高度 = 宽度 * 56.25% */
|
||||
height: 56.25vw !important;
|
||||
|
||||
/* 设定最大高度,防止在 PC 端显得过大(可根据需求调整,比如 400px) */
|
||||
max-height: 450px !important;
|
||||
|
||||
/* 设定最小高度,防止在极小屏幕下太扁 */
|
||||
min-height: 180px !important;
|
||||
|
||||
aspect-ratio: 16 / 9 !important;
|
||||
display: block !important;
|
||||
border-radius: 12px;
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
/* 强制内部所有幻灯片跟随父容器高度 */
|
||||
.swiper-wrapper, .swiper-slide, .blog-slider__item {
|
||||
height: 100% !important;
|
||||
}
|
||||
/* 3. 彻底隐藏分页圆点或橙色元素 */
|
||||
.blog-slider__pagination, .swiper-pagination {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.blog-slider__pagination .swiper-pagination-bullet,
|
||||
.swiper-pagination-bullet,
|
||||
.swiper-button-next,
|
||||
.swiper-button-prev {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
/* 4. 图片填充修复:确保铺满不留白 */
|
||||
.blog-slider__item {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
background-size: cover !important;
|
||||
background-position: center !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* 5. 打包内容块并整体居中 */
|
||||
.blog-slider__content {
|
||||
background: rgba(0, 0, 0, 0.45) !important;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
position: absolute !important;
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* 核心:将内容容器设为居中参考点 */
|
||||
.blog-slider__content .blog-slider__item_content_wrapper, /* 尝试抓取可能的内层包裹 */
|
||||
.blog-slider__content > div:first-child, /* 备选抓取 */
|
||||
.blog-slider__content {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
justify-content: center !important;
|
||||
align-items: center !important;
|
||||
}
|
||||
|
||||
/* 针对标题和描述的统一样式 */
|
||||
.blog-slider__title,
|
||||
.blog-slider__text {
|
||||
position: relative !important; /* 放弃绝对定位,回归正常流,防止重叠 */
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
transform: none !important;
|
||||
width: 85% !important;
|
||||
text-align: center !important;
|
||||
margin: 5px auto !important; /* 自动上下留白 */
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
a.blog-slider__title {
|
||||
font-size: 1.4rem !important;
|
||||
font-weight: bold !important;
|
||||
line-height: 1.3 !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.blog-slider__text {
|
||||
font-size: 0.9rem !important;
|
||||
opacity: 0.9;
|
||||
/* 限制描述文字行数,防止在16:9中撑破容器 */
|
||||
display: -webkit-box !important;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 针对手机端的极小屏幕适配 */
|
||||
@media screen and (max-width: 768px) {
|
||||
a.blog-slider__title {
|
||||
font-size: 1.1rem !important;
|
||||
}
|
||||
.blog-slider__text {
|
||||
font-size: 0.8rem !important;
|
||||
-webkit-line-clamp: 1; /* 手机端只留一行描述,防止空间拥挤 */
|
||||
}
|
||||
}
|
||||
/* 6. 移动端微调:防止文字过大或间距过宽 */
|
||||
@media screen and (max-width: 768px) {
|
||||
.blog-slider__content {
|
||||
padding: 0 15px !important;
|
||||
}
|
||||
a.blog-slider__title {
|
||||
font-size: 1.1rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 7. 左右箭头样式:白色且无背景 */
|
||||
.swiper-button-next::after, .swiper-button-prev::after {
|
||||
font-size: 1.2rem !important;
|
||||
color: #fff !important;
|
||||
background: none !important;
|
||||
}
|
||||
@@ -0,0 +1,544 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<link rel="icon" href="https://free.picui.cn/free/2025/08/10/689845496a283.png" type="image/x-icon">
|
||||
|
||||
<title>安全跳转中...</title>
|
||||
<style type="text/css">
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
overflow: hidden;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.avatar-placeholder,
|
||||
.avatar {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
border-radius: 50%;
|
||||
margin-bottom: 16px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.avatar-placeholder {
|
||||
background: rgba(255, 255, 255, 0.18);
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 14px;
|
||||
margin: 6px 0 22px;
|
||||
color: #c4c4c4;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.loading {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
padding: 28px;
|
||||
border-radius: 26px;
|
||||
animation: fadein 0.6s ease;
|
||||
width: 460px;
|
||||
max-width: calc(100vw - 32px);
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes fadein {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.url-text {
|
||||
margin-bottom: 12px;
|
||||
font-size: 15px;
|
||||
letter-spacing: 0.4px;
|
||||
}
|
||||
|
||||
.jump-url {
|
||||
position: relative;
|
||||
display: block;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 22px;
|
||||
padding: 16px 68px 16px 16px;
|
||||
border-radius: 18px;
|
||||
min-height: 58px;
|
||||
text-align: left;
|
||||
word-break: break-all;
|
||||
line-height: 1.6;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.jump-url-text {
|
||||
display: block;
|
||||
display: -webkit-box;
|
||||
padding-right: 2px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1.6;
|
||||
max-height: 3.2em;
|
||||
word-break: break-all;
|
||||
overflow-wrap: anywhere;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.copy-btn-container {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
right: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.copy-toast {
|
||||
position: absolute;
|
||||
top: -34px;
|
||||
right: 0;
|
||||
padding: 6px 10px;
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
transform: translateY(6px);
|
||||
pointer-events: none;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.loading[data-copy-state="success"] .copy-toast,
|
||||
.loading[data-copy-state="error"] .copy-toast {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #a4a4a4;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.25s ease-in-out;
|
||||
}
|
||||
|
||||
.copy-btn-container svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.countdown-text {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 0;
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.countdown-number {
|
||||
display: inline-block;
|
||||
margin: 0 2px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.button {
|
||||
padding: 0 20px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
height: 44px;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease, opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.button:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.cancel-button {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.confirm-button {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
border-radius: 999px;
|
||||
overflow: hidden;
|
||||
height: 8px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.progress {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: width 1s linear;
|
||||
}
|
||||
|
||||
|
||||
body {
|
||||
background: linear-gradient(135deg, #364f6b, #222831);
|
||||
}
|
||||
|
||||
.loading {
|
||||
border: 1px solid #6d7682;
|
||||
background: rgba(57, 62, 70, 0.92);
|
||||
color: #efefef;
|
||||
box-shadow: 0 18px 40px rgba(0, 0, 0, 0.22);
|
||||
}
|
||||
|
||||
.loading:hover {
|
||||
box-shadow: 0 22px 46px rgba(0, 0, 0, 0.28);
|
||||
}
|
||||
|
||||
.description {
|
||||
color: #f3f3f3;
|
||||
}
|
||||
|
||||
.url-text,
|
||||
.countdown-text {
|
||||
color: #efefef;
|
||||
}
|
||||
|
||||
.jump-url {
|
||||
border: 1px solid #737b86;
|
||||
background-color: #333940;
|
||||
color: #efefef;
|
||||
}
|
||||
|
||||
.copy-toast {
|
||||
background: rgba(239, 239, 239, 0.95);
|
||||
color: #222831;
|
||||
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
background-color: #222831;
|
||||
border-color: #737b86;
|
||||
color: #c8d0da;
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
color: #ffffff;
|
||||
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
|
||||
.cancel-button {
|
||||
background-color: rgba(135, 44, 44, 0.16);
|
||||
color: #ffd5d5;
|
||||
border: 1px solid rgba(255, 118, 118, 0.12);
|
||||
}
|
||||
|
||||
.confirm-button {
|
||||
background: linear-gradient(135deg, #28566f, #2f6f90);
|
||||
color: #fff;
|
||||
border-color: rgba(86, 148, 182, 0.2);
|
||||
box-shadow: 0 12px 24px rgba(40, 86, 111, 0.26);
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
background-color: #444;
|
||||
}
|
||||
|
||||
.progress {
|
||||
background-color: #888;
|
||||
}
|
||||
|
||||
|
||||
@media (max-width: 520px) {
|
||||
body {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.loading {
|
||||
padding: 22px 18px;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
function GetQueryString(name) {
|
||||
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
|
||||
var r = window.location.search.substr(1).match(reg);
|
||||
return r ? decodeURI(r[2]) : null;
|
||||
}
|
||||
|
||||
function decodeSafeUrlParam(paramStr) {
|
||||
if (!paramStr) {
|
||||
return paramStr;
|
||||
}
|
||||
var base64 = paramStr.replace(/-/g, "+").replace(/_/g, "/");
|
||||
var paddingLength = (4 - (base64.length % 4)) % 4;
|
||||
if (paddingLength) {
|
||||
base64 += "=".repeat(paddingLength);
|
||||
}
|
||||
return decodeURIComponent(atob(base64).split("").map(function (char) {
|
||||
return "%" + ("00" + char.charCodeAt(0).toString(16)).slice(-2);
|
||||
}).join(""));
|
||||
}
|
||||
|
||||
let countdown = 10;
|
||||
let jump_url = GetQueryString('u');
|
||||
let copyToastTimer = null;
|
||||
|
||||
|
||||
try {
|
||||
jump_url = decodeSafeUrlParam(jump_url);
|
||||
} catch (error) {
|
||||
jump_url = null;
|
||||
}
|
||||
|
||||
|
||||
const UrlReg = /^(http|https|thunder|qqdl|ed2k|Flashget|qbrowser|ftp|rtsp|mms):\/\//i;
|
||||
if (!jump_url || !UrlReg.test(jump_url)) {
|
||||
document.title = '参数错误,正在返回首页...';
|
||||
jump_url = location.origin;
|
||||
}
|
||||
|
||||
let progressElement;
|
||||
let countdownElement;
|
||||
let countdownTextElement;
|
||||
let jumpUrlElement;
|
||||
let loadingElement;
|
||||
|
||||
function updateCopyToast(state, message) {
|
||||
if (!loadingElement) {
|
||||
return;
|
||||
}
|
||||
loadingElement.setAttribute("data-copy-state", state);
|
||||
document.getElementById("copy-toast").textContent = message;
|
||||
if (copyToastTimer) {
|
||||
clearTimeout(copyToastTimer);
|
||||
}
|
||||
if (state === "success" || state === "error") {
|
||||
copyToastTimer = setTimeout(function () {
|
||||
loadingElement.setAttribute("data-copy-state", "idle");
|
||||
}, 1600);
|
||||
}
|
||||
}
|
||||
|
||||
function updateCountdown() {
|
||||
if (countdown < 0) {
|
||||
countdownTextElement.textContent = "💡请自行确认链接安全后再继续跳转";
|
||||
if (progressElement) {
|
||||
progressElement.style.width = "100%";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
countdownElement.textContent = countdown;
|
||||
if (progressElement && 10 > 0) {
|
||||
progressElement.style.width = (countdown / 10 * 100) + "%";
|
||||
}
|
||||
|
||||
if (countdown === 0) {
|
||||
jump();
|
||||
} else {
|
||||
countdown--;
|
||||
setTimeout(updateCountdown, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function jump() {
|
||||
location.href = jump_url;
|
||||
}
|
||||
|
||||
function closeWindow() {
|
||||
function isWeChat() {
|
||||
return /MicroMessenger/i.test(navigator.userAgent);
|
||||
}
|
||||
|
||||
function isQQ() {
|
||||
return /QQ/i.test(navigator.userAgent) && !/MicroMessenger/i.test(navigator.userAgent);
|
||||
}
|
||||
|
||||
if (isWeChat()) {
|
||||
if (typeof WeixinJSBridge !== "undefined") {
|
||||
WeixinJSBridge.call("closeWindow");
|
||||
} else {
|
||||
document.addEventListener("WeixinJSBridgeReady", function () {
|
||||
WeixinJSBridge.call("closeWindow");
|
||||
}, false);
|
||||
}
|
||||
} else if (isQQ()) {
|
||||
if (typeof mqq !== "undefined" && mqq.ui && mqq.ui.closeWebView) {
|
||||
mqq.ui.closeWebView();
|
||||
} else {
|
||||
window.history.back();
|
||||
}
|
||||
} else {
|
||||
window.opener = null;
|
||||
window.open("", "_self");
|
||||
window.close();
|
||||
}
|
||||
}
|
||||
|
||||
function fallbackCopyText(text) {
|
||||
const tempInput = document.createElement("input");
|
||||
tempInput.value = text;
|
||||
document.body.appendChild(tempInput);
|
||||
tempInput.select();
|
||||
tempInput.setSelectionRange(0, text.length);
|
||||
const copied = document.execCommand("copy");
|
||||
document.body.removeChild(tempInput);
|
||||
if (!copied) {
|
||||
throw new Error("copy failed");
|
||||
}
|
||||
}
|
||||
|
||||
function copyToClipboard() {
|
||||
const urlText = document.getElementById("jump-url-text").textContent;
|
||||
if (!urlText) {
|
||||
updateCopyToast("error", "暂无可复制内容");
|
||||
return;
|
||||
}
|
||||
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(urlText).then(function () {
|
||||
updateCopyToast("success", "复制成功");
|
||||
}).catch(function () {
|
||||
try {
|
||||
fallbackCopyText(urlText);
|
||||
updateCopyToast("success", "复制成功");
|
||||
} catch (error) {
|
||||
updateCopyToast("error", "复制失败");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
fallbackCopyText(urlText);
|
||||
updateCopyToast("success", "复制成功");
|
||||
} catch (error) {
|
||||
updateCopyToast("error", "复制失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function loadAvatar() {
|
||||
const avatarImg = document.querySelector(".avatar");
|
||||
const placeholder = document.querySelector(".avatar-placeholder");
|
||||
const img = new Image();
|
||||
img.src = "https://free.picui.cn/free/2025/08/10/689845496a283.png";
|
||||
img.onload = function () {
|
||||
avatarImg.src = img.src;
|
||||
avatarImg.style.display = "block";
|
||||
placeholder.style.display = "none";
|
||||
};
|
||||
}
|
||||
|
||||
window.addEventListener("load", function () {
|
||||
loadAvatar();
|
||||
loadingElement = document.querySelector(".loading");
|
||||
progressElement = document.getElementById("progress");
|
||||
countdownElement = document.getElementById("countdown");
|
||||
countdownTextElement = document.querySelector(".countdown-text");
|
||||
jumpUrlElement = document.getElementById("jump-url-text");
|
||||
jumpUrlElement.textContent = jump_url;
|
||||
updateCountdown();
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="avatar-placeholder"></div>
|
||||
<img src="" alt="头像" class="avatar">
|
||||
<div class="description">Bi's Blog</div>
|
||||
<div class="subtitle">安全中心</div>
|
||||
|
||||
<div class="loading" data-copy-state="idle">
|
||||
<div class="content">
|
||||
<div class="url-text">您即将离开本站,跳转到:</div>
|
||||
<div class="jump-url" id="jump-url">
|
||||
<span class="jump-url-text" id="jump-url-text"></span>
|
||||
<div class="copy-btn-container">
|
||||
<span class="copy-toast" id="copy-toast">复制成功</span>
|
||||
<button class="copy-btn" onclick="copyToClipboard()" aria-label="复制链接">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
|
||||
<path d="M208 0L332.1 0c12.7 0 24.9 5.1 33.9 14.1l67.9 67.9c9 9 14.1 21.2 14.1 33.9L448 336c0 26.5-21.5 48-48 48l-192 0c-26.5 0-48-21.5-48-48l0-288c0-26.5 21.5-48 48-48zM48 128l80 0 0 64-64 0 0 256 192 0 0-32 64 0 0 48c0 26.5-21.5 48-48 48L48 512c-26.5 0-48-21.5-48-48L0 176c0-26.5 21.5-48 48-48z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="countdown-text">⚡将在<span class="countdown-number" id="countdown"></span>秒后跳转,请自行确认链接安全性</div>
|
||||
|
||||
<div class="progress-bar">
|
||||
<div class="progress" id="progress"></div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="button-container">
|
||||
<button class="button cancel-button" onclick="closeWindow()">取消跳转</button>
|
||||
<button class="button confirm-button" onclick="jump()">立即跳转</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 457 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 269 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 64 KiB |
@@ -0,0 +1 @@
|
||||
https://blog.biss.click/posts/34725d47/
|
||||
@@ -0,0 +1,65 @@
|
||||
// 打字机效果
|
||||
function typeTextMachineStyle(text, targetSelector, options = {}) {
|
||||
const {
|
||||
delay = 50,
|
||||
startDelay = 2000,
|
||||
onComplete = null,
|
||||
clearBefore = true,
|
||||
eraseBefore = true, // 新增:是否以打字机方式清除原文本
|
||||
eraseDelay = 30, // 新增:删除每个字符的间隔
|
||||
} = options;
|
||||
|
||||
const el = document.querySelector(targetSelector);
|
||||
if (!el || typeof text !== "string") return;
|
||||
|
||||
setTimeout(() => {
|
||||
const startTyping = () => {
|
||||
let index = 0;
|
||||
function renderChar() {
|
||||
if (index <= text.length) {
|
||||
el.textContent = text.slice(0, index++);
|
||||
setTimeout(renderChar, delay);
|
||||
} else {
|
||||
onComplete && onComplete(el);
|
||||
}
|
||||
}
|
||||
renderChar();
|
||||
};
|
||||
|
||||
if (clearBefore) {
|
||||
if (eraseBefore && el.textContent.length > 0) {
|
||||
let currentText = el.textContent;
|
||||
let eraseIndex = currentText.length;
|
||||
|
||||
function eraseChar() {
|
||||
if (eraseIndex > 0) {
|
||||
el.textContent = currentText.slice(0, --eraseIndex);
|
||||
setTimeout(eraseChar, eraseDelay);
|
||||
} else {
|
||||
startTyping(); // 删除完毕后开始打字
|
||||
}
|
||||
}
|
||||
|
||||
eraseChar();
|
||||
} else {
|
||||
el.textContent = "";
|
||||
startTyping();
|
||||
}
|
||||
} else {
|
||||
startTyping();
|
||||
}
|
||||
}, startDelay);
|
||||
}
|
||||
|
||||
function renderAISummary() {
|
||||
const summaryEl = document.querySelector('.ai-summary .ai-explanation');
|
||||
if (!summaryEl) return;
|
||||
|
||||
const summaryText = summaryEl.getAttribute('data-summary');
|
||||
if (summaryText) {
|
||||
typeTextMachineStyle(summaryText, ".ai-summary .ai-explanation"); // 如果需要切换,在这里调用另一个函数即可
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('pjax:complete', renderAISummary);
|
||||
document.addEventListener('DOMContentLoaded', renderAISummary);
|
||||
@@ -0,0 +1,397 @@
|
||||
const cheerio = require('cheerio')
|
||||
const moment = require('moment')
|
||||
|
||||
hexo.extend.filter.register('after_render:html', function (locals) {
|
||||
const $ = cheerio.load(locals)
|
||||
const post = $('#posts-chart')
|
||||
const tag = $('#tags-chart')
|
||||
const category = $('#categories-chart')
|
||||
const htmlEncode = false
|
||||
|
||||
if (post.length > 0 || tag.length > 0 || category.length > 0) {
|
||||
if (post.length > 0 && $('#postsChart').length === 0) {
|
||||
if (post.attr('data-encode') === 'true') htmlEncode = true
|
||||
post.after(postsChart(post.attr('data-start')))
|
||||
}
|
||||
if (tag.length > 0 && $('#tagsChart').length === 0) {
|
||||
if (tag.attr('data-encode') === 'true') htmlEncode = true
|
||||
tag.after(tagsChart(tag.attr('data-length')))
|
||||
}
|
||||
if (category.length > 0 && $('#categoriesChart').length === 0) {
|
||||
if (category.attr('data-encode') === 'true') htmlEncode = true
|
||||
category.after(categoriesChart(category.attr('data-parent')))
|
||||
}
|
||||
|
||||
if (htmlEncode) {
|
||||
return $.root().html().replace(/&#/g, '&#')
|
||||
} else {
|
||||
return $.root().html()
|
||||
}
|
||||
} else {
|
||||
return locals
|
||||
}
|
||||
}, 15)
|
||||
|
||||
function postsChart (startMonth) {
|
||||
const startDate = moment(startMonth || '2020-01')
|
||||
const endDate = moment()
|
||||
|
||||
const monthMap = new Map()
|
||||
const dayTime = 3600 * 24 * 1000
|
||||
for (let time = startDate; time <= endDate; time += dayTime) {
|
||||
const month = moment(time).format('YYYY-MM')
|
||||
if (!monthMap.has(month)) {
|
||||
monthMap.set(month, 0)
|
||||
}
|
||||
}
|
||||
hexo.locals.get('posts').forEach(function (post) {
|
||||
const month = post.date.format('YYYY-MM')
|
||||
if (monthMap.has(month)) {
|
||||
monthMap.set(month, monthMap.get(month) + 1)
|
||||
}
|
||||
})
|
||||
const monthArr = JSON.stringify([...monthMap.keys()])
|
||||
const monthValueArr = JSON.stringify([...monthMap.values()])
|
||||
|
||||
return `
|
||||
<script id="postsChart">
|
||||
var color = document.documentElement.getAttribute('data-theme') === 'light' ? '#4c4948' : 'rgba(255,255,255,0.7)'
|
||||
var postsChart = echarts.init(document.getElementById('posts-chart'), 'light');
|
||||
var postsOption = {
|
||||
title: {
|
||||
text: '文章发布统计图',
|
||||
x: 'center',
|
||||
textStyle: {
|
||||
color: color
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
xAxis: {
|
||||
name: '日期',
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
nameTextStyle: {
|
||||
color: color
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
show: true,
|
||||
color: color
|
||||
},
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: color
|
||||
}
|
||||
},
|
||||
data: ${monthArr}
|
||||
},
|
||||
yAxis: {
|
||||
name: '文章篇数',
|
||||
type: 'value',
|
||||
nameTextStyle: {
|
||||
color: color
|
||||
},
|
||||
splitLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
show: true,
|
||||
color: color
|
||||
},
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: color
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
name: '文章篇数',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
lineStyle: {
|
||||
width: 0
|
||||
},
|
||||
showSymbol: false,
|
||||
itemStyle: {
|
||||
opacity: 1,
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||
offset: 0,
|
||||
color: 'rgba(128, 255, 165)'
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(1, 191, 236)'
|
||||
}])
|
||||
},
|
||||
areaStyle: {
|
||||
opacity: 1,
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||
offset: 0,
|
||||
color: 'rgba(128, 255, 165)'
|
||||
}, {
|
||||
offset: 1,
|
||||
color: 'rgba(1, 191, 236)'
|
||||
}])
|
||||
},
|
||||
data: ${monthValueArr},
|
||||
markLine: {
|
||||
data: [{
|
||||
name: '平均值',
|
||||
type: 'average',
|
||||
label: {
|
||||
color: color
|
||||
}
|
||||
}]
|
||||
}
|
||||
}]
|
||||
};
|
||||
postsChart.setOption(postsOption);
|
||||
window.addEventListener('resize', () => {
|
||||
postsChart.resize();
|
||||
});
|
||||
postsChart.on('click', 'series', (event) => {
|
||||
if (event.componentType === 'series') window.location.href = '/archives/' + event.name.replace('-', '/');
|
||||
});
|
||||
</script>`
|
||||
}
|
||||
|
||||
function tagsChart (len) {
|
||||
const tagArr = []
|
||||
hexo.locals.get('tags').map(function (tag) {
|
||||
tagArr.push({ name: tag.name, value: tag.length, path: tag.path })
|
||||
})
|
||||
tagArr.sort((a, b) => { return b.value - a.value })
|
||||
|
||||
const dataLength = Math.min(tagArr.length, len) || tagArr.length
|
||||
const tagNameArr = []
|
||||
for (let i = 0; i < dataLength; i++) {
|
||||
tagNameArr.push(tagArr[i].name)
|
||||
}
|
||||
const tagNameArrJson = JSON.stringify(tagNameArr)
|
||||
const tagArrJson = JSON.stringify(tagArr)
|
||||
|
||||
return `
|
||||
<script id="tagsChart">
|
||||
var color = document.documentElement.getAttribute('data-theme') === 'light' ? '#4c4948' : 'rgba(255,255,255,0.7)'
|
||||
var tagsChart = echarts.init(document.getElementById('tags-chart'), 'light');
|
||||
var tagsOption = {
|
||||
title: {
|
||||
text: 'Top ${dataLength} 标签统计图',
|
||||
x: 'center',
|
||||
textStyle: {
|
||||
color: color
|
||||
}
|
||||
},
|
||||
tooltip: {},
|
||||
xAxis: {
|
||||
name: '标签',
|
||||
type: 'category',
|
||||
nameTextStyle: {
|
||||
color: color
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
show: true,
|
||||
color: color,
|
||||
interval: 0
|
||||
},
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: color
|
||||
}
|
||||
},
|
||||
data: ${tagNameArrJson}
|
||||
},
|
||||
yAxis: {
|
||||
name: '文章篇数',
|
||||
type: 'value',
|
||||
splitLine: {
|
||||
show: false
|
||||
},
|
||||
nameTextStyle: {
|
||||
color: color
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
show: true,
|
||||
color: color
|
||||
},
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: color
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
name: '文章篇数',
|
||||
type: 'bar',
|
||||
data: ${tagArrJson},
|
||||
itemStyle: {
|
||||
borderRadius: [5, 5, 0, 0],
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||
offset: 0,
|
||||
color: 'rgba(128, 255, 165)'
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(1, 191, 236)'
|
||||
}])
|
||||
},
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||
offset: 0,
|
||||
color: 'rgba(128, 255, 195)'
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(1, 211, 255)'
|
||||
}])
|
||||
}
|
||||
},
|
||||
markLine: {
|
||||
data: [{
|
||||
name: '平均值',
|
||||
type: 'average',
|
||||
label: {
|
||||
color: color
|
||||
}
|
||||
}]
|
||||
}
|
||||
}]
|
||||
};
|
||||
tagsChart.setOption(tagsOption);
|
||||
window.addEventListener('resize', () => {
|
||||
tagsChart.resize();
|
||||
});
|
||||
tagsChart.on('click', 'series', (event) => {
|
||||
if(event.data.path) window.location.href = '/' + event.data.path;
|
||||
});
|
||||
</script>`
|
||||
}
|
||||
|
||||
function categoriesChart (dataParent) {
|
||||
const categoryArr = []
|
||||
let categoryParentFlag = false
|
||||
hexo.locals.get('categories').map(function (category) {
|
||||
if (category.parent) categoryParentFlag = true
|
||||
categoryArr.push({
|
||||
name: category.name,
|
||||
value: category.length,
|
||||
path: category.path,
|
||||
id: category._id,
|
||||
parentId: category.parent || '0'
|
||||
})
|
||||
})
|
||||
categoryParentFlag = categoryParentFlag && dataParent === 'true'
|
||||
categoryArr.sort((a, b) => { return b.value - a.value })
|
||||
function translateListToTree (data, parent) {
|
||||
let tree = []
|
||||
let temp
|
||||
data.forEach((item, index) => {
|
||||
if (data[index].parentId == parent) {
|
||||
let obj = data[index];
|
||||
temp = translateListToTree(data, data[index].id);
|
||||
if (temp.length > 0) {
|
||||
obj.children = temp
|
||||
}
|
||||
if (tree.indexOf())
|
||||
tree.push(obj)
|
||||
}
|
||||
})
|
||||
return tree
|
||||
}
|
||||
const categoryNameJson = JSON.stringify(categoryArr.map(function (category) { return category.name }))
|
||||
const categoryArrJson = JSON.stringify(categoryArr)
|
||||
const categoryArrParentJson = JSON.stringify(translateListToTree(categoryArr, '0'))
|
||||
|
||||
return `
|
||||
<script id="categoriesChart">
|
||||
var color = document.documentElement.getAttribute('data-theme') === 'light' ? '#4c4948' : 'rgba(255,255,255,0.7)'
|
||||
var categoriesChart = echarts.init(document.getElementById('categories-chart'), 'light');
|
||||
var categoryParentFlag = ${categoryParentFlag}
|
||||
var categoriesOption = {
|
||||
title: {
|
||||
text: '文章分类统计图',
|
||||
x: 'center',
|
||||
textStyle: {
|
||||
color: color
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
top: 'bottom',
|
||||
data: ${categoryNameJson},
|
||||
textStyle: {
|
||||
color: color
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
series: []
|
||||
};
|
||||
categoriesOption.series.push(
|
||||
categoryParentFlag ?
|
||||
{
|
||||
nodeClick :false,
|
||||
name: '文章篇数',
|
||||
type: 'sunburst',
|
||||
radius: ['15%', '90%'],
|
||||
center: ['50%', '55%'],
|
||||
sort: 'desc',
|
||||
data: ${categoryArrParentJson},
|
||||
itemStyle: {
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2,
|
||||
emphasis: {
|
||||
focus: 'ancestor',
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(255, 255, 255, 0.5)'
|
||||
}
|
||||
}
|
||||
}
|
||||
:
|
||||
{
|
||||
name: '文章篇数',
|
||||
type: 'pie',
|
||||
radius: [30, 80],
|
||||
roseType: 'area',
|
||||
label: {
|
||||
color: color,
|
||||
formatter: '{b} : {c} ({d}%)'
|
||||
},
|
||||
data: ${categoryArrJson},
|
||||
itemStyle: {
|
||||
emphasis: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(255, 255, 255, 0.5)'
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
categoriesChart.setOption(categoriesOption);
|
||||
window.addEventListener('resize', () => {
|
||||
categoriesChart.resize();
|
||||
});
|
||||
categoriesChart.on('click', 'series', (event) => {
|
||||
if(event.data.path) window.location.href = '/' + event.data.path;
|
||||
});
|
||||
</script>`
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
|
||||
//版权图标动态显示
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const currentYear = new Date().getFullYear();
|
||||
const copyrightElement = document.querySelector('.copyright');
|
||||
if (copyrightElement) {
|
||||
copyrightElement.innerHTML = `©2025 - ${currentYear} <i class="fa-solid fa-heart "></i> By Bi`;
|
||||
}
|
||||
});
|
||||
|
||||
// 运行时间动态显示
|
||||
function showDateTime() {
|
||||
const timeDisplay = document.getElementById('span_dt_dt');
|
||||
if (!timeDisplay) return;
|
||||
|
||||
const startTime = new Date("2025-07-05T15:41:23");
|
||||
const now = new Date();
|
||||
const elapsedMilliseconds = now - startTime;
|
||||
const seconds = Math.floor(elapsedMilliseconds / 1000);
|
||||
|
||||
const oneYearInSeconds = 365 * 24 * 60 * 60;
|
||||
if (seconds < oneYearInSeconds) {
|
||||
const days = Math.floor(seconds / (24 * 60 * 60));
|
||||
const remainingSecondsAfterDays = seconds % (24 * 60 * 60);
|
||||
const hours = Math.floor(remainingSecondsAfterDays / (60 * 60));
|
||||
const remainingSecondsAfterHours = remainingSecondsAfterDays % (60 * 60);
|
||||
const minutes = Math.floor(remainingSecondsAfterHours / 60);
|
||||
const sec = remainingSecondsAfterHours % 60;
|
||||
|
||||
timeDisplay.innerHTML = `
|
||||
<span style="color:#ffff00">${days}</span> 天
|
||||
<span style="color:#ffff00">${hours}</span> 时
|
||||
<span style="color:#ffff00">${minutes}</span> 分
|
||||
<span style="color:#ffff00">${sec}</span> 秒
|
||||
`;
|
||||
} else {
|
||||
const years = Math.floor(seconds / oneYearInSeconds);
|
||||
const remainingSecondsAfterYears = seconds % oneYearInSeconds;
|
||||
const days = Math.floor(remainingSecondsAfterYears / (24 * 60 * 60));
|
||||
const remainingSecondsAfterDays = remainingSecondsAfterYears % (24 * 60 * 60);
|
||||
const hours = Math.floor(remainingSecondsAfterDays / (60 * 60));
|
||||
const remainingSecondsAfterHours = remainingSecondsAfterDays % (60 * 60);
|
||||
const minutes = Math.floor(remainingSecondsAfterHours / 60);
|
||||
const sec = remainingSecondsAfterHours % 60;
|
||||
|
||||
timeDisplay.innerHTML = `
|
||||
<span style="color:#ffff00">${years}</span> 年
|
||||
<span style="color:#ffff00">${days}</span> 天
|
||||
<span style="color:#ffff00">${hours}</span> 时
|
||||
<span style="color:#ffff00">${minutes}</span> 分
|
||||
<span style="color:#ffff00">${sec}</span> 秒
|
||||
`;
|
||||
}
|
||||
|
||||
setTimeout(showDateTime, 1000);
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const frameworkInfo = document.querySelector('.framework-info');
|
||||
if (frameworkInfo) {
|
||||
frameworkInfo.innerHTML = '本站已运行<span id="span_dt_dt"></span>';
|
||||
}
|
||||
showDateTime();
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,987 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
let headerContentWidth, $nav
|
||||
let mobileSidebarOpen = false
|
||||
|
||||
const adjustMenu = init => {
|
||||
const getAllWidth = ele => Array.from(ele).reduce((width, i) => width + i.offsetWidth, 0)
|
||||
|
||||
if (init) {
|
||||
const blogInfoWidth = getAllWidth(document.querySelector('#blog-info > a').children)
|
||||
const menusWidth = getAllWidth(document.getElementById('menus').children)
|
||||
headerContentWidth = blogInfoWidth + menusWidth
|
||||
$nav = document.getElementById('nav')
|
||||
}
|
||||
|
||||
const hideMenuIndex = window.innerWidth <= 768 || headerContentWidth > $nav.offsetWidth - 120
|
||||
$nav.classList.toggle('hide-menu', hideMenuIndex)
|
||||
}
|
||||
|
||||
// 初始化header
|
||||
const initAdjust = () => {
|
||||
adjustMenu(true)
|
||||
$nav.classList.add('show')
|
||||
}
|
||||
|
||||
// sidebar menus
|
||||
const sidebarFn = {
|
||||
open: () => {
|
||||
btf.overflowPaddingR.add()
|
||||
btf.animateIn(document.getElementById('menu-mask'), 'to_show 0.5s')
|
||||
document.getElementById('sidebar-menus').classList.add('open')
|
||||
mobileSidebarOpen = true
|
||||
},
|
||||
close: () => {
|
||||
btf.overflowPaddingR.remove()
|
||||
btf.animateOut(document.getElementById('menu-mask'), 'to_hide 0.5s')
|
||||
document.getElementById('sidebar-menus').classList.remove('open')
|
||||
mobileSidebarOpen = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 首頁top_img底下的箭頭
|
||||
*/
|
||||
const scrollDownInIndex = () => {
|
||||
const handleScrollToDest = () => {
|
||||
btf.scrollToDest(document.getElementById('content-inner').offsetTop, 300)
|
||||
}
|
||||
|
||||
const $scrollDownEle = document.getElementById('scroll-down')
|
||||
$scrollDownEle && btf.addEventListenerPjax($scrollDownEle, 'click', handleScrollToDest)
|
||||
}
|
||||
|
||||
/**
|
||||
* 代碼
|
||||
* 只適用於Hexo默認的代碼渲染
|
||||
*/
|
||||
const addHighlightTool = () => {
|
||||
const highLight = GLOBAL_CONFIG.highlight
|
||||
if (!highLight) return
|
||||
|
||||
const { highlightCopy, highlightLang, highlightHeightLimit, highlightFullpage, highlightMacStyle, plugin } = highLight
|
||||
const isHighlightShrink = GLOBAL_CONFIG_SITE.isHighlightShrink
|
||||
const isShowTool = highlightCopy || highlightLang || isHighlightShrink !== undefined || highlightFullpage || highlightMacStyle
|
||||
const isNotHighlightJs = plugin !== 'highlight.js'
|
||||
const isPrismjs = plugin === 'prismjs'
|
||||
const $figureHighlight = isNotHighlightJs
|
||||
? Array.from(document.querySelectorAll('code[class*="language-"]')).map(code => code.parentElement)
|
||||
: document.querySelectorAll('figure.highlight')
|
||||
|
||||
if (!((isShowTool || highlightHeightLimit) && $figureHighlight.length)) return
|
||||
|
||||
const highlightShrinkClass = isHighlightShrink === true ? 'closed' : ''
|
||||
const highlightShrinkEle = isHighlightShrink !== undefined ? '<i class="fas fa-angle-down expand"></i>' : ''
|
||||
const highlightCopyEle = highlightCopy ? '<i class="fas fa-paste copy-button"></i>' : ''
|
||||
const highlightMacStyleEle = '<div class="macStyle"><div class="mac-close"></div><div class="mac-minimize"></div><div class="mac-maximize"></div></div>'
|
||||
const highlightFullpageEle = highlightFullpage ? '<i class="fa-solid fa-up-right-and-down-left-from-center fullpage-button"></i>' : ''
|
||||
|
||||
const alertInfo = (ele, text) => {
|
||||
if (GLOBAL_CONFIG.Snackbar !== undefined) {
|
||||
btf.snackbarShow(text)
|
||||
} else {
|
||||
const newEle = document.createElement('div')
|
||||
newEle.className = 'copy-notice'
|
||||
newEle.textContent = text
|
||||
document.body.appendChild(newEle)
|
||||
|
||||
const buttonRect = ele.getBoundingClientRect()
|
||||
const scrollTop = window.pageYOffset || document.documentElement.scrollTop
|
||||
const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft
|
||||
|
||||
// X-axis boundary check
|
||||
const halfWidth = newEle.offsetWidth / 2
|
||||
const centerLeft = buttonRect.left + scrollLeft + buttonRect.width / 2
|
||||
const finalLeft = Math.max(halfWidth + 10, Math.min(window.innerWidth - halfWidth - 10, centerLeft))
|
||||
|
||||
// Show tooltip below button if too close to top
|
||||
const normalTop = buttonRect.top + scrollTop - 40
|
||||
const shouldShowBelow = buttonRect.top < 60 || normalTop < 10
|
||||
|
||||
const topValue = shouldShowBelow ? buttonRect.top + scrollTop + buttonRect.height + 10 : normalTop
|
||||
|
||||
newEle.style.cssText = `
|
||||
top: ${topValue + 10}px;
|
||||
left: ${finalLeft}px;
|
||||
transform: translateX(-50%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease, top 0.3s ease;
|
||||
`
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
newEle.style.opacity = '1'
|
||||
newEle.style.top = `${topValue}px`
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
newEle.style.opacity = '0'
|
||||
newEle.style.top = `${topValue + 10}px`
|
||||
setTimeout(() => {
|
||||
newEle?.remove()
|
||||
}, 300)
|
||||
}, 800)
|
||||
}
|
||||
}
|
||||
|
||||
const copy = async (text, ctx) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text)
|
||||
alertInfo(ctx, GLOBAL_CONFIG.copy.success)
|
||||
} catch (err) {
|
||||
console.error('Failed to copy: ', err)
|
||||
alertInfo(ctx, GLOBAL_CONFIG.copy.noSupport)
|
||||
}
|
||||
}
|
||||
|
||||
// click events
|
||||
const highlightCopyFn = (ele, clickEle) => {
|
||||
const $buttonParent = ele.parentNode
|
||||
$buttonParent.classList.add('copy-true')
|
||||
const preCodeSelector = isNotHighlightJs ? 'pre code' : 'table .code pre'
|
||||
const codeElement = $buttonParent.querySelector(preCodeSelector)
|
||||
if (!codeElement) return
|
||||
copy(codeElement.innerText, clickEle)
|
||||
$buttonParent.classList.remove('copy-true')
|
||||
}
|
||||
|
||||
const highlightShrinkFn = ele => ele.classList.toggle('closed')
|
||||
|
||||
const codeFullpage = (item, clickEle) => {
|
||||
const wrapEle = item.closest('figure.highlight')
|
||||
const isFullpage = wrapEle.classList.toggle('code-fullpage')
|
||||
|
||||
document.body.style.overflow = isFullpage ? 'hidden' : ''
|
||||
clickEle.classList.toggle('fa-down-left-and-up-right-to-center', isFullpage)
|
||||
clickEle.classList.toggle('fa-up-right-and-down-left-from-center', !isFullpage)
|
||||
}
|
||||
|
||||
const highlightToolsFn = e => {
|
||||
const $target = e.target.classList
|
||||
const currentElement = e.currentTarget
|
||||
if ($target.contains('expand')) highlightShrinkFn(currentElement)
|
||||
else if ($target.contains('copy-button')) highlightCopyFn(currentElement, e.target)
|
||||
else if ($target.contains('fullpage-button')) codeFullpage(currentElement, e.target)
|
||||
}
|
||||
|
||||
const expandCode = e => e.currentTarget.classList.toggle('expand-done')
|
||||
|
||||
// 獲取隱藏狀態下元素的真實高度
|
||||
const getActualHeight = item => {
|
||||
if (item.offsetHeight > 0) return item.offsetHeight
|
||||
const hiddenElements = new Map()
|
||||
|
||||
const fix = () => {
|
||||
let current = item
|
||||
while (current !== document.body && current != null) {
|
||||
if (window.getComputedStyle(current).display === 'none') {
|
||||
hiddenElements.set(current, current.getAttribute('style') || '')
|
||||
}
|
||||
current = current.parentNode
|
||||
}
|
||||
|
||||
const style = 'visibility: hidden !important; display: block !important;'
|
||||
hiddenElements.forEach((originalStyle, elem) => {
|
||||
elem.setAttribute('style', originalStyle ? originalStyle + ';' + style : style)
|
||||
})
|
||||
}
|
||||
|
||||
const restore = () => {
|
||||
hiddenElements.forEach((originalStyle, elem) => {
|
||||
if (originalStyle === '') elem.removeAttribute('style')
|
||||
else elem.setAttribute('style', originalStyle)
|
||||
})
|
||||
}
|
||||
|
||||
fix()
|
||||
const height = item.offsetHeight
|
||||
restore()
|
||||
return height
|
||||
}
|
||||
|
||||
const createEle = (lang, item) => {
|
||||
const fragment = document.createDocumentFragment()
|
||||
|
||||
if (isShowTool) {
|
||||
const hlTools = document.createElement('div')
|
||||
hlTools.className = `highlight-tools ${highlightShrinkClass}`
|
||||
hlTools.innerHTML = highlightMacStyleEle + highlightShrinkEle + lang + highlightCopyEle + highlightFullpageEle
|
||||
btf.addEventListenerPjax(hlTools, 'click', highlightToolsFn)
|
||||
fragment.appendChild(hlTools)
|
||||
}
|
||||
|
||||
if (highlightHeightLimit && getActualHeight(item) > highlightHeightLimit + 30) {
|
||||
const ele = document.createElement('div')
|
||||
ele.className = 'code-expand-btn'
|
||||
ele.innerHTML = '<i class="fas fa-angle-double-down"></i>'
|
||||
btf.addEventListenerPjax(ele, 'click', expandCode)
|
||||
fragment.appendChild(ele)
|
||||
}
|
||||
|
||||
isNotHighlightJs ? item.parentNode.insertBefore(fragment, item) : item.insertBefore(fragment, item.firstChild)
|
||||
}
|
||||
|
||||
$figureHighlight.forEach(item => {
|
||||
let langName = ''
|
||||
if (isNotHighlightJs) {
|
||||
const newClassName = isPrismjs ? 'prismjs' : 'default'
|
||||
btf.wrap(item, 'figure', { class: `highlight ${newClassName}` })
|
||||
}
|
||||
|
||||
if (!highlightLang) {
|
||||
createEle('', item)
|
||||
return
|
||||
}
|
||||
|
||||
if (isNotHighlightJs) {
|
||||
langName = isPrismjs ? item.getAttribute('data-language') || 'Code' : item.querySelector('code').getAttribute('class').replace('language-', '')
|
||||
} else {
|
||||
langName = item.getAttribute('class').split(' ')[1]
|
||||
if (langName === 'plain' || langName === undefined) langName = 'Code'
|
||||
}
|
||||
createEle(`<div class="code-lang">${langName}</div>`, item)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* PhotoFigcaption
|
||||
*/
|
||||
const addPhotoFigcaption = () => {
|
||||
if (!GLOBAL_CONFIG.isPhotoFigcaption) return
|
||||
document.querySelectorAll('#article-container img').forEach(item => {
|
||||
const altValue = item.title || item.alt
|
||||
if (!altValue) return
|
||||
const ele = document.createElement('div')
|
||||
ele.className = 'img-alt text-center'
|
||||
ele.textContent = altValue
|
||||
item.insertAdjacentElement('afterend', ele)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Lightbox
|
||||
*/
|
||||
const runLightbox = () => {
|
||||
btf.loadLightbox(document.querySelectorAll('#article-container img:not(.no-lightbox)'))
|
||||
}
|
||||
|
||||
/**
|
||||
* justified-gallery 圖庫排版
|
||||
*/
|
||||
|
||||
const fetchUrl = async url => {
|
||||
try {
|
||||
const response = await fetch(url)
|
||||
return await response.json()
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch URL:', error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
const runJustifiedGallery = (container, data, config) => {
|
||||
const { isButton, limit, firstLimit, tabs } = config
|
||||
|
||||
const dataLength = data.length
|
||||
const maxGroupKey = Math.ceil((dataLength - firstLimit) / limit + 1)
|
||||
|
||||
// Gallery configuration
|
||||
const igConfig = {
|
||||
gap: 5,
|
||||
isConstantSize: true,
|
||||
sizeRange: [150, 600],
|
||||
// useResizeObserver: true,
|
||||
// observeChildren: true,
|
||||
useTransform: true
|
||||
// useRecycle: false
|
||||
}
|
||||
|
||||
const ig = new InfiniteGrid.JustifiedInfiniteGrid(container, igConfig)
|
||||
let isLayoutHidden = false
|
||||
|
||||
// Utility functions
|
||||
const sanitizeString = str => (str && str.replace(/"/g, '"')) || ''
|
||||
|
||||
const createImageItem = item => {
|
||||
const alt = item.alt ? `alt="${sanitizeString(item.alt)}"` : ''
|
||||
const title = item.title ? `title="${sanitizeString(item.title)}"` : ''
|
||||
return `<div class="item">
|
||||
<img src="${item.url}" data-grid-maintained-target="true" ${alt} ${title} />
|
||||
</div>`
|
||||
}
|
||||
|
||||
const getItems = (nextGroupKey, count, isFirst = false) => {
|
||||
const startIndex = isFirst ? (nextGroupKey - 1) * count : (nextGroupKey - 2) * count + firstLimit
|
||||
return data.slice(startIndex, startIndex + count).map(createImageItem)
|
||||
}
|
||||
|
||||
// Load more button
|
||||
const addLoadMoreButton = container => {
|
||||
const button = document.createElement('button')
|
||||
button.innerHTML = `${GLOBAL_CONFIG.infinitegrid.buttonText}<i class="fa-solid fa-arrow-down"></i>`
|
||||
|
||||
button.addEventListener('click', () => {
|
||||
button.remove()
|
||||
btf.setLoading.add(container)
|
||||
appendItems(ig.getGroups().length + 1, limit)
|
||||
}, { once: true })
|
||||
|
||||
container.insertAdjacentElement('afterend', button)
|
||||
}
|
||||
|
||||
const appendItems = (nextGroupKey, count, isFirst) => {
|
||||
ig.append(getItems(nextGroupKey, count, isFirst), nextGroupKey)
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
const handleRenderComplete = e => {
|
||||
if (tabs) {
|
||||
const parentNode = container.parentNode
|
||||
if (isLayoutHidden) {
|
||||
parentNode.style.visibility = 'visible'
|
||||
}
|
||||
if (container.offsetHeight === 0) {
|
||||
parentNode.style.visibility = 'hidden'
|
||||
isLayoutHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
const { updated, isResize, mounted } = e
|
||||
if (!updated.length || !mounted.length || isResize) return
|
||||
|
||||
btf.loadLightbox(container.querySelectorAll('img:not(.medium-zoom-image)'))
|
||||
|
||||
if (ig.getGroups().length === maxGroupKey) {
|
||||
btf.setLoading.remove(container)
|
||||
!tabs && ig.off('renderComplete', handleRenderComplete)
|
||||
return
|
||||
}
|
||||
|
||||
if (isButton) {
|
||||
btf.setLoading.remove(container)
|
||||
addLoadMoreButton(container)
|
||||
}
|
||||
}
|
||||
|
||||
const handleRequestAppend = btf.debounce(e => {
|
||||
const nextGroupKey = (+e.groupKey || 0) + 1
|
||||
|
||||
if (nextGroupKey === 1) appendItems(nextGroupKey, firstLimit, true)
|
||||
else appendItems(nextGroupKey, limit)
|
||||
|
||||
if (nextGroupKey === maxGroupKey) ig.off('requestAppend', handleRequestAppend)
|
||||
}, 300)
|
||||
|
||||
btf.setLoading.add(container)
|
||||
ig.on('renderComplete', handleRenderComplete)
|
||||
|
||||
if (isButton) {
|
||||
appendItems(1, firstLimit, true)
|
||||
} else {
|
||||
ig.on('requestAppend', handleRequestAppend)
|
||||
ig.renderItems()
|
||||
}
|
||||
|
||||
btf.addGlobalFn('pjaxSendOnce', () => ig.destroy())
|
||||
}
|
||||
|
||||
const addJustifiedGallery = async (elements, tabs = false) => {
|
||||
if (!elements.length) return
|
||||
|
||||
const initGallery = async () => {
|
||||
for (const element of elements) {
|
||||
if (btf.isHidden(element) || element.classList.contains('loaded')) continue
|
||||
|
||||
const config = {
|
||||
isButton: element.getAttribute('data-button') === 'true',
|
||||
limit: parseInt(element.getAttribute('data-limit'), 10),
|
||||
firstLimit: parseInt(element.getAttribute('data-first'), 10),
|
||||
tabs
|
||||
}
|
||||
|
||||
const container = element.firstElementChild
|
||||
const content = container.textContent
|
||||
container.textContent = ''
|
||||
element.classList.add('loaded')
|
||||
|
||||
try {
|
||||
const data = element.getAttribute('data-type') === 'url' ? await fetchUrl(content) : JSON.parse(content)
|
||||
runJustifiedGallery(container, data, config)
|
||||
} catch (error) {
|
||||
console.error('Gallery data parsing failed:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof InfiniteGrid === 'function') {
|
||||
await initGallery()
|
||||
} else {
|
||||
await btf.getScript(GLOBAL_CONFIG.infinitegrid.js)
|
||||
await initGallery()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* rightside scroll percent
|
||||
*/
|
||||
const rightsideScrollPercent = currentTop => {
|
||||
const scrollPercent = btf.getScrollPercent(currentTop, document.body)
|
||||
const goUpElement = document.getElementById('go-up')
|
||||
|
||||
if (scrollPercent < 95) {
|
||||
goUpElement.classList.add('show-percent')
|
||||
goUpElement.querySelector('.scroll-percent').textContent = scrollPercent
|
||||
} else {
|
||||
goUpElement.classList.remove('show-percent')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 滾動處理
|
||||
*/
|
||||
const scrollFn = () => {
|
||||
const $rightside = document.getElementById('rightside')
|
||||
const innerHeight = window.innerHeight + 56
|
||||
let initTop = 0
|
||||
const $header = document.getElementById('page-header')
|
||||
const isChatBtn = typeof chatBtn !== 'undefined'
|
||||
const isShowPercent = GLOBAL_CONFIG.percent.rightside
|
||||
|
||||
// 檢查文檔高度是否小於視窗高度
|
||||
const checkDocumentHeight = () => {
|
||||
if (document.body.scrollHeight <= innerHeight) {
|
||||
$rightside.classList.add('rightside-show')
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 如果文檔高度小於視窗高度,直接返回
|
||||
if (checkDocumentHeight()) return
|
||||
|
||||
// find the scroll direction
|
||||
const scrollDirection = currentTop => {
|
||||
const result = currentTop > initTop // true is down & false is up
|
||||
initTop = currentTop
|
||||
return result
|
||||
}
|
||||
|
||||
let flag = ''
|
||||
const scrollTask = btf.throttle(() => {
|
||||
const currentTop = window.scrollY || document.documentElement.scrollTop
|
||||
const isDown = scrollDirection(currentTop)
|
||||
if (currentTop > 56) {
|
||||
if (flag === '') {
|
||||
$header.classList.add('nav-fixed')
|
||||
$rightside.classList.add('rightside-show')
|
||||
}
|
||||
|
||||
if (isDown) {
|
||||
if (flag !== 'down') {
|
||||
$header.classList.remove('nav-visible')
|
||||
isChatBtn && window.chatBtn.hide()
|
||||
flag = 'down'
|
||||
}
|
||||
} else {
|
||||
if (flag !== 'up') {
|
||||
$header.classList.add('nav-visible')
|
||||
isChatBtn && window.chatBtn.show()
|
||||
flag = 'up'
|
||||
}
|
||||
}
|
||||
} else {
|
||||
flag = ''
|
||||
if (currentTop === 0) {
|
||||
$header.classList.remove('nav-fixed', 'nav-visible')
|
||||
}
|
||||
$rightside.classList.remove('rightside-show')
|
||||
}
|
||||
|
||||
isShowPercent && rightsideScrollPercent(currentTop)
|
||||
checkDocumentHeight()
|
||||
}, 300)
|
||||
|
||||
btf.addEventListenerPjax(window, 'scroll', scrollTask, { passive: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* toc,anchor
|
||||
*/
|
||||
const scrollFnToDo = () => {
|
||||
const isToc = GLOBAL_CONFIG_SITE.isToc
|
||||
const isAnchor = GLOBAL_CONFIG.isAnchor
|
||||
const $article = document.getElementById('article-container')
|
||||
|
||||
if (!($article && (isToc || isAnchor))) return
|
||||
|
||||
let $tocLink, $cardToc, autoScrollToc, $tocPercentage, isExpand
|
||||
|
||||
if (isToc) {
|
||||
const $cardTocLayout = document.getElementById('card-toc')
|
||||
$cardToc = $cardTocLayout.querySelector('.toc-content')
|
||||
$tocLink = $cardToc.querySelectorAll('.toc-link')
|
||||
$tocPercentage = $cardTocLayout.querySelector('.toc-percentage')
|
||||
isExpand = $cardToc.classList.contains('is-expand')
|
||||
|
||||
// toc元素點擊
|
||||
const tocItemClickFn = e => {
|
||||
const target = e.target.closest('.toc-link')
|
||||
if (!target) return
|
||||
|
||||
e.preventDefault()
|
||||
btf.scrollToDest(btf.getEleTop(document.getElementById(decodeURI(target.getAttribute('href')).replace('#', ''))), 300)
|
||||
if (window.innerWidth < 900) {
|
||||
$cardTocLayout.classList.remove('open')
|
||||
}
|
||||
}
|
||||
|
||||
btf.addEventListenerPjax($cardToc, 'click', tocItemClickFn)
|
||||
|
||||
autoScrollToc = item => {
|
||||
const sidebarHeight = $cardToc.clientHeight
|
||||
const itemOffsetTop = item.offsetTop
|
||||
const itemHeight = item.clientHeight
|
||||
const scrollTop = $cardToc.scrollTop
|
||||
const offset = itemOffsetTop - scrollTop
|
||||
const middlePosition = (sidebarHeight - itemHeight) / 2
|
||||
|
||||
if (offset !== middlePosition) {
|
||||
$cardToc.scrollTop = scrollTop + (offset - middlePosition)
|
||||
}
|
||||
}
|
||||
|
||||
// 處理 hexo-blog-encrypt 事件
|
||||
$cardToc.style.display = 'block'
|
||||
}
|
||||
|
||||
// find head position & add active class
|
||||
const $articleList = $article.querySelectorAll('h1,h2,h3,h4,h5,h6')
|
||||
let detectItem = ''
|
||||
|
||||
// Optimization: Cache header positions
|
||||
let headerList = []
|
||||
const updateHeaderPositions = () => {
|
||||
headerList = Array.from($articleList).map(ele => ({
|
||||
ele,
|
||||
top: btf.getEleTop(ele),
|
||||
id: ele.id
|
||||
}))
|
||||
}
|
||||
|
||||
updateHeaderPositions()
|
||||
btf.addEventListenerPjax(window, 'resize', btf.throttle(updateHeaderPositions, 200))
|
||||
|
||||
const findHeadPosition = top => {
|
||||
if (top === 0) return false
|
||||
|
||||
let currentId = ''
|
||||
let currentIndex = ''
|
||||
|
||||
for (let i = 0; i < headerList.length; i++) {
|
||||
const item = headerList[i]
|
||||
if (top > item.top - 80) {
|
||||
currentId = item.id ? '#' + encodeURI(item.id) : ''
|
||||
currentIndex = i
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (detectItem === currentIndex) return
|
||||
|
||||
if (isAnchor) btf.updateAnchor(currentId)
|
||||
|
||||
detectItem = currentIndex
|
||||
|
||||
if (isToc) {
|
||||
$cardToc.querySelectorAll('.active').forEach(i => i.classList.remove('active'))
|
||||
|
||||
if (currentId) {
|
||||
const currentActive = $tocLink[currentIndex]
|
||||
currentActive.classList.add('active')
|
||||
|
||||
setTimeout(() => autoScrollToc(currentActive), 0)
|
||||
|
||||
if (!isExpand) {
|
||||
let parent = currentActive.parentNode
|
||||
while (!parent.matches('.toc')) {
|
||||
if (parent.matches('li')) parent.classList.add('active')
|
||||
parent = parent.parentNode
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// main of scroll
|
||||
const tocScrollFn = btf.throttle(() => {
|
||||
const currentTop = window.scrollY || document.documentElement.scrollTop
|
||||
if (isToc && GLOBAL_CONFIG.percent.toc) {
|
||||
$tocPercentage.textContent = btf.getScrollPercent(currentTop, $article)
|
||||
}
|
||||
findHeadPosition(currentTop)
|
||||
}, 100)
|
||||
|
||||
btf.addEventListenerPjax(window, 'scroll', tocScrollFn, { passive: true })
|
||||
}
|
||||
|
||||
const handleThemeChange = mode => {
|
||||
const globalFn = window.globalFn || {}
|
||||
const themeChange = globalFn.themeChange || {}
|
||||
if (!themeChange) {
|
||||
return
|
||||
}
|
||||
|
||||
Object.keys(themeChange).forEach(key => {
|
||||
const themeChangeFn = themeChange[key]
|
||||
if (['disqus', 'disqusjs'].includes(key)) {
|
||||
setTimeout(() => themeChangeFn(mode), 300)
|
||||
} else {
|
||||
themeChangeFn(mode)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Rightside
|
||||
*/
|
||||
const rightSideFn = {
|
||||
readmode: () => { // read mode
|
||||
const $body = document.body
|
||||
const newEle = document.createElement('button')
|
||||
|
||||
const exitReadMode = () => {
|
||||
$body.classList.remove('read-mode')
|
||||
newEle.remove()
|
||||
newEle.removeEventListener('click', exitReadMode)
|
||||
}
|
||||
|
||||
$body.classList.add('read-mode')
|
||||
newEle.type = 'button'
|
||||
newEle.className = 'exit-readmode'
|
||||
newEle.innerHTML = '<i class="fas fa-sign-out-alt"></i>'
|
||||
newEle.addEventListener('click', exitReadMode)
|
||||
$body.appendChild(newEle)
|
||||
},
|
||||
darkmode: () => { // switch between light and dark mode
|
||||
const willChangeMode = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark'
|
||||
if (willChangeMode === 'dark') {
|
||||
btf.activateDarkMode()
|
||||
GLOBAL_CONFIG.Snackbar !== undefined && btf.snackbarShow(GLOBAL_CONFIG.Snackbar.day_to_night)
|
||||
} else {
|
||||
btf.activateLightMode()
|
||||
GLOBAL_CONFIG.Snackbar !== undefined && btf.snackbarShow(GLOBAL_CONFIG.Snackbar.night_to_day)
|
||||
}
|
||||
btf.saveToLocal.set('theme', willChangeMode, 2)
|
||||
handleThemeChange(willChangeMode)
|
||||
},
|
||||
'rightside-config': item => { // Show or hide rightside-hide-btn
|
||||
const hideLayout = item.firstElementChild
|
||||
if (hideLayout.classList.contains('show')) {
|
||||
hideLayout.classList.add('status')
|
||||
setTimeout(() => {
|
||||
hideLayout.classList.remove('status')
|
||||
}, 300)
|
||||
}
|
||||
|
||||
hideLayout.classList.toggle('show')
|
||||
},
|
||||
'go-up': () => { // Back to top
|
||||
btf.scrollToDest(0, 500)
|
||||
},
|
||||
'hide-aside-btn': () => { // Hide aside
|
||||
const $htmlDom = document.documentElement.classList
|
||||
const saveStatus = $htmlDom.contains('hide-aside') ? 'show' : 'hide'
|
||||
btf.saveToLocal.set('aside-status', saveStatus, 2)
|
||||
$htmlDom.toggle('hide-aside')
|
||||
},
|
||||
'mobile-toc-button': (p, item) => { // Show mobile toc
|
||||
const tocEle = document.getElementById('card-toc')
|
||||
tocEle.style.transition = 'transform 0.3s ease-in-out'
|
||||
|
||||
const tocEleHeight = tocEle.clientHeight
|
||||
const btData = item.getBoundingClientRect()
|
||||
|
||||
const tocEleBottom = window.innerHeight - btData.bottom - 30
|
||||
if (tocEleHeight > tocEleBottom) {
|
||||
tocEle.style.transformOrigin = `right ${tocEleHeight - tocEleBottom - btData.height / 2}px`
|
||||
}
|
||||
|
||||
tocEle.classList.toggle('open')
|
||||
tocEle.addEventListener('transitionend', () => {
|
||||
tocEle.style.cssText = ''
|
||||
}, { once: true })
|
||||
},
|
||||
'chat-btn': () => { // Show chat
|
||||
window.chatBtnFn()
|
||||
},
|
||||
translateLink: () => { // switch between traditional and simplified chinese
|
||||
window.translateFn.translatePage()
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('rightside').addEventListener('click', e => {
|
||||
const $target = e.target.closest('[id]')
|
||||
if ($target && rightSideFn[$target.id]) {
|
||||
rightSideFn[$target.id](e.currentTarget, $target)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* menu
|
||||
* 側邊欄sub-menu 展開/收縮
|
||||
*/
|
||||
const clickFnOfSubMenu = () => {
|
||||
const handleClickOfSubMenu = e => {
|
||||
const target = e.target.closest('.site-page.group')
|
||||
if (!target) return
|
||||
target.classList.toggle('hide')
|
||||
}
|
||||
|
||||
const menusItems = document.querySelector('#sidebar-menus .menus_items')
|
||||
menusItems && menusItems.addEventListener('click', handleClickOfSubMenu)
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机端目录点击
|
||||
*/
|
||||
const openMobileMenu = () => {
|
||||
const toggleMenu = document.getElementById('toggle-menu')
|
||||
if (!toggleMenu) return
|
||||
btf.addEventListenerPjax(toggleMenu, 'click', () => { sidebarFn.open() })
|
||||
}
|
||||
|
||||
/**
|
||||
* 複製時加上版權信息
|
||||
*/
|
||||
const addCopyright = () => {
|
||||
const { limitCount, languages } = GLOBAL_CONFIG.copyright
|
||||
|
||||
const handleCopy = e => {
|
||||
e.preventDefault()
|
||||
const copyFont = window.getSelection(0).toString()
|
||||
let textFont = copyFont
|
||||
if (copyFont.length > limitCount) {
|
||||
textFont = `${copyFont}\n\n\n${languages.author}\n${languages.link}${window.location.href}\n${languages.source}\n${languages.info}`
|
||||
}
|
||||
if (e.clipboardData) {
|
||||
return e.clipboardData.setData('text', textFont)
|
||||
} else {
|
||||
return window.clipboardData.setData('text', textFont)
|
||||
}
|
||||
}
|
||||
|
||||
document.body.addEventListener('copy', handleCopy)
|
||||
}
|
||||
|
||||
/**
|
||||
* 網頁運行時間
|
||||
*/
|
||||
const addRuntime = () => {
|
||||
const $runtimeCount = document.getElementById('runtimeshow')
|
||||
if ($runtimeCount) {
|
||||
const publishDate = $runtimeCount.getAttribute('data-publishDate')
|
||||
$runtimeCount.textContent = `${btf.diffDate(publishDate)} ${GLOBAL_CONFIG.runtime}`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 最後一次更新時間
|
||||
*/
|
||||
const addLastPushDate = () => {
|
||||
const $lastPushDateItem = document.getElementById('last-push-date')
|
||||
if ($lastPushDateItem) {
|
||||
const lastPushDate = $lastPushDateItem.getAttribute('data-lastPushDate')
|
||||
$lastPushDateItem.textContent = btf.diffDate(lastPushDate, true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* table overflow
|
||||
*/
|
||||
const addTableWrap = () => {
|
||||
const $table = document.querySelectorAll('#article-container table')
|
||||
if (!$table.length) return
|
||||
|
||||
$table.forEach(item => {
|
||||
if (!item.closest('.highlight')) {
|
||||
btf.wrap(item, 'div', { class: 'table-wrap' })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* tag-hide
|
||||
*/
|
||||
const clickFnOfTagHide = () => {
|
||||
const hideButtons = document.querySelectorAll('#article-container .hide-button')
|
||||
if (!hideButtons.length) return
|
||||
hideButtons.forEach(item => item.addEventListener('click', e => {
|
||||
const currentTarget = e.currentTarget
|
||||
currentTarget.classList.add('open')
|
||||
addJustifiedGallery(currentTarget.nextElementSibling.querySelectorAll('.gallery-container'))
|
||||
}, { once: true }))
|
||||
}
|
||||
|
||||
const tabsFn = () => {
|
||||
const navTabsElements = document.querySelectorAll('#article-container .tabs')
|
||||
if (!navTabsElements.length) return
|
||||
|
||||
const setActiveClass = (elements, activeIndex) => {
|
||||
elements.forEach((el, index) => {
|
||||
el.classList.toggle('active', index === activeIndex)
|
||||
})
|
||||
}
|
||||
|
||||
const handleNavClick = e => {
|
||||
const target = e.target.closest('button')
|
||||
if (!target || target.classList.contains('active')) return
|
||||
|
||||
const navItems = [...e.currentTarget.children]
|
||||
const tabContents = [...e.currentTarget.nextElementSibling.children]
|
||||
const indexOfButton = navItems.indexOf(target)
|
||||
setActiveClass(navItems, indexOfButton)
|
||||
e.currentTarget.classList.remove('no-default')
|
||||
setActiveClass(tabContents, indexOfButton)
|
||||
addJustifiedGallery(tabContents[indexOfButton].querySelectorAll('.gallery-container'), true)
|
||||
}
|
||||
|
||||
const handleToTopClick = tabElement => e => {
|
||||
if (e.target.closest('button')) {
|
||||
btf.scrollToDest(btf.getEleTop(tabElement), 300)
|
||||
}
|
||||
}
|
||||
|
||||
navTabsElements.forEach(tabElement => {
|
||||
btf.addEventListenerPjax(tabElement.firstElementChild, 'click', handleNavClick)
|
||||
btf.addEventListenerPjax(tabElement.lastElementChild, 'click', handleToTopClick(tabElement))
|
||||
})
|
||||
}
|
||||
|
||||
const toggleCardCategory = () => {
|
||||
const cardCategory = document.querySelector('#aside-cat-list.expandBtn')
|
||||
if (!cardCategory) return
|
||||
|
||||
const handleToggleBtn = e => {
|
||||
const target = e.target
|
||||
if (target.nodeName === 'I') {
|
||||
e.preventDefault()
|
||||
target.parentNode.classList.toggle('expand')
|
||||
}
|
||||
}
|
||||
btf.addEventListenerPjax(cardCategory, 'click', handleToggleBtn, true)
|
||||
}
|
||||
|
||||
const addPostOutdateNotice = () => {
|
||||
const ele = document.getElementById('post-outdate-notice')
|
||||
if (!ele) return
|
||||
|
||||
const { limitDay, messagePrev, messageNext, postUpdate } = JSON.parse(ele.getAttribute('data'))
|
||||
const diffDay = btf.diffDate(postUpdate)
|
||||
if (diffDay >= limitDay) {
|
||||
ele.textContent = `${messagePrev} ${diffDay} ${messageNext}`
|
||||
ele.hidden = false
|
||||
}
|
||||
}
|
||||
|
||||
const lazyloadImg = () => {
|
||||
window.lazyLoadInstance = new LazyLoad({
|
||||
elements_selector: 'img',
|
||||
threshold: 0,
|
||||
data_src: 'lazy-src'
|
||||
})
|
||||
|
||||
btf.addGlobalFn('pjaxComplete', () => {
|
||||
window.lazyLoadInstance.update()
|
||||
}, 'lazyload')
|
||||
}
|
||||
|
||||
const relativeDate = selector => {
|
||||
selector.forEach(item => {
|
||||
item.textContent = btf.diffDate(item.getAttribute('datetime'), true)
|
||||
item.style.display = 'inline'
|
||||
})
|
||||
}
|
||||
|
||||
const justifiedIndexPostUI = () => {
|
||||
const recentPostsElement = document.getElementById('recent-posts')
|
||||
if (!(recentPostsElement && recentPostsElement.classList.contains('masonry'))) return
|
||||
|
||||
const init = () => {
|
||||
const masonryItem = new InfiniteGrid.MasonryInfiniteGrid('.recent-post-items', {
|
||||
gap: { horizontal: 10, vertical: 20 },
|
||||
useTransform: true,
|
||||
useResizeObserver: true
|
||||
})
|
||||
masonryItem.renderItems()
|
||||
btf.addGlobalFn('pjaxCompleteOnce', () => { masonryItem.destroy() }, 'removeJustifiedIndexPostUI')
|
||||
}
|
||||
|
||||
typeof InfiniteGrid === 'function' ? init() : btf.getScript(`${GLOBAL_CONFIG.infinitegrid.js}`).then(init)
|
||||
}
|
||||
|
||||
const unRefreshFn = () => {
|
||||
window.addEventListener('resize', () => {
|
||||
adjustMenu(false)
|
||||
mobileSidebarOpen && btf.isHidden(document.getElementById('toggle-menu')) && sidebarFn.close()
|
||||
})
|
||||
|
||||
const menuMask = document.getElementById('menu-mask')
|
||||
menuMask && menuMask.addEventListener('click', () => { sidebarFn.close() })
|
||||
|
||||
clickFnOfSubMenu()
|
||||
GLOBAL_CONFIG.islazyloadPlugin && lazyloadImg()
|
||||
GLOBAL_CONFIG.copyright !== undefined && addCopyright()
|
||||
|
||||
if (GLOBAL_CONFIG.autoDarkmode) {
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
|
||||
if (btf.saveToLocal.get('theme') !== undefined) return
|
||||
e.matches ? handleThemeChange('dark') : handleThemeChange('light')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const forPostFn = () => {
|
||||
addHighlightTool()
|
||||
addPhotoFigcaption()
|
||||
addJustifiedGallery(document.querySelectorAll('#article-container .gallery-container'))
|
||||
runLightbox()
|
||||
scrollFnToDo()
|
||||
addTableWrap()
|
||||
clickFnOfTagHide()
|
||||
tabsFn()
|
||||
}
|
||||
|
||||
const refreshFn = () => {
|
||||
initAdjust()
|
||||
justifiedIndexPostUI()
|
||||
|
||||
if (GLOBAL_CONFIG_SITE.pageType === 'post') {
|
||||
addPostOutdateNotice()
|
||||
GLOBAL_CONFIG.relativeDate.post && relativeDate(document.querySelectorAll('#post-meta time'))
|
||||
} else {
|
||||
GLOBAL_CONFIG.relativeDate.homepage && relativeDate(document.querySelectorAll('#recent-posts time'))
|
||||
GLOBAL_CONFIG.runtime && addRuntime()
|
||||
addLastPushDate()
|
||||
toggleCardCategory()
|
||||
}
|
||||
|
||||
GLOBAL_CONFIG_SITE.pageType === 'home' && scrollDownInIndex()
|
||||
scrollFn()
|
||||
|
||||
forPostFn()
|
||||
GLOBAL_CONFIG_SITE.pageType !== 'shuoshuo' && btf.switchComments(document)
|
||||
openMobileMenu()
|
||||
}
|
||||
|
||||
btf.addGlobalFn('pjaxComplete', refreshFn, 'refreshFn')
|
||||
refreshFn()
|
||||
unRefreshFn()
|
||||
|
||||
// 處理 hexo-blog-encrypt 事件
|
||||
window.addEventListener('hexo-blog-decrypt', e => {
|
||||
forPostFn()
|
||||
window.translateFn.translateInitialization()
|
||||
Object.values(window.globalFn.encrypt).forEach(fn => {
|
||||
fn()
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,13 @@
|
||||
function randomPost() {
|
||||
fetch('/sitemap.xml').then(res => res.text()).then(str => (new window.DOMParser()).parseFromString(str, "text/xml")).then(data => {
|
||||
let ls = data.querySelectorAll('url loc');
|
||||
let locationHref,locSplit;
|
||||
do {
|
||||
locationHref = ls[Math.floor(Math.random() * ls.length)].innerHTML
|
||||
locSplit = locationHref.split('/')[3] || ''
|
||||
} while (locSplit !== 'posts');
|
||||
//若所有文章都如 https://…….com/posts/2022/07/…… 格式,主域名后字符是 posts,则循环条件改为:
|
||||
//while (locSplit !== 'posts');
|
||||
location.href = locationHref
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,562 @@
|
||||
window.addEventListener('load', () => {
|
||||
const { algolia } = GLOBAL_CONFIG
|
||||
const { appId, apiKey, indexName, hitsPerPage = 5, languages } = algolia
|
||||
|
||||
if (!appId || !apiKey || !indexName) {
|
||||
return console.error('Algolia setting is invalid!')
|
||||
}
|
||||
|
||||
const $searchMask = document.getElementById('search-mask')
|
||||
const $searchDialog = document.querySelector('#algolia-search .search-dialog')
|
||||
|
||||
const animateElements = show => {
|
||||
const action = show ? 'animateIn' : 'animateOut'
|
||||
const maskAnimation = show ? 'to_show 0.5s' : 'to_hide 0.5s'
|
||||
const dialogAnimation = show ? 'titleScale 0.5s' : 'search_close .5s'
|
||||
btf[action]($searchMask, maskAnimation)
|
||||
btf[action]($searchDialog, dialogAnimation)
|
||||
}
|
||||
|
||||
const fixSafariHeight = () => {
|
||||
if (window.innerWidth < 768) {
|
||||
$searchDialog.style.setProperty('--search-height', `${window.innerHeight}px`)
|
||||
}
|
||||
}
|
||||
|
||||
const openSearch = () => {
|
||||
btf.overflowPaddingR.add()
|
||||
animateElements(true)
|
||||
showLoading(false)
|
||||
|
||||
setTimeout(() => {
|
||||
const searchInput = document.querySelector('#algolia-search-input .ais-SearchBox-input')
|
||||
if (searchInput) searchInput.focus()
|
||||
}, 100)
|
||||
|
||||
const handleEscape = event => {
|
||||
if (event.code === 'Escape') {
|
||||
closeSearch()
|
||||
document.removeEventListener('keydown', handleEscape)
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', handleEscape)
|
||||
fixSafariHeight()
|
||||
window.addEventListener('resize', fixSafariHeight)
|
||||
}
|
||||
|
||||
const closeSearch = () => {
|
||||
btf.overflowPaddingR.remove()
|
||||
animateElements(false)
|
||||
window.removeEventListener('resize', fixSafariHeight)
|
||||
}
|
||||
|
||||
const searchClickFn = () => {
|
||||
btf.addEventListenerPjax(document.querySelector('#search-button > .search'), 'click', openSearch)
|
||||
}
|
||||
|
||||
const searchFnOnce = () => {
|
||||
$searchMask.addEventListener('click', closeSearch)
|
||||
document.querySelector('#algolia-search .search-close-button').addEventListener('click', closeSearch)
|
||||
}
|
||||
|
||||
const cutContent = content => {
|
||||
if (!content) return ''
|
||||
|
||||
let contentStr = ''
|
||||
if (typeof content === 'string') {
|
||||
contentStr = content.trim()
|
||||
} else if (typeof content === 'object') {
|
||||
if (content.value !== undefined) {
|
||||
contentStr = String(content.value).trim()
|
||||
if (!contentStr) return ''
|
||||
} else if (content.matchedWords || content.matchLevel || content.fullyHighlighted !== undefined) {
|
||||
return ''
|
||||
} else {
|
||||
try {
|
||||
contentStr = JSON.stringify(content).trim()
|
||||
if (contentStr === '{}' || contentStr === '[]' || contentStr === '""') {
|
||||
return ''
|
||||
}
|
||||
} catch (e) {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
} else if (content.toString && typeof content.toString === 'function') {
|
||||
contentStr = content.toString().trim()
|
||||
if (contentStr === '[object Object]' || contentStr === '[object Array]') {
|
||||
return ''
|
||||
}
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
|
||||
const firstOccur = contentStr.indexOf('<mark>')
|
||||
let start = firstOccur - 30
|
||||
let end = firstOccur + 120
|
||||
let pre = ''
|
||||
let post = ''
|
||||
|
||||
if (start <= 0) {
|
||||
start = 0
|
||||
end = 140
|
||||
} else {
|
||||
pre = '...'
|
||||
}
|
||||
|
||||
if (end > contentStr.length) {
|
||||
end = contentStr.length
|
||||
} else {
|
||||
post = '...'
|
||||
}
|
||||
|
||||
// Ensure we don't cut off HTML tags in the middle
|
||||
let substr = contentStr.substring(start, end)
|
||||
|
||||
// Handle tag completeness
|
||||
// Check for incomplete opening tags at the beginning
|
||||
const firstCloseBracket = substr.indexOf('>')
|
||||
const firstOpenBracket = substr.indexOf('<')
|
||||
|
||||
// If there's a closing bracket but no opening bracket before it, we've cut a tag
|
||||
if (firstCloseBracket !== -1 && (firstOpenBracket === -1 || firstCloseBracket < firstOpenBracket)) {
|
||||
substr = substr.substring(firstCloseBracket + 1)
|
||||
}
|
||||
|
||||
// Check for incomplete closing tags at the end
|
||||
const lastOpenBracket = substr.lastIndexOf('<')
|
||||
const lastCloseBracket = substr.lastIndexOf('>')
|
||||
|
||||
// If there's an opening bracket after the last closing bracket, we've cut a tag
|
||||
if (lastOpenBracket !== -1 && lastOpenBracket > lastCloseBracket) {
|
||||
substr = substr.substring(0, lastOpenBracket)
|
||||
}
|
||||
|
||||
// Balance tags in the substring
|
||||
const tagStack = []
|
||||
let balancedStr = ''
|
||||
let i = 0
|
||||
|
||||
while (i < substr.length) {
|
||||
if (substr[i] === '<') {
|
||||
// Check if it's a closing tag
|
||||
if (substr[i + 1] === '/') {
|
||||
const closeTagEnd = substr.indexOf('>', i)
|
||||
if (closeTagEnd !== -1) {
|
||||
const closeTagName = substr.substring(i + 2, closeTagEnd)
|
||||
// Remove matching opening tag from stack
|
||||
for (let j = tagStack.length - 1; j >= 0; j--) {
|
||||
if (tagStack[j] === closeTagName) {
|
||||
tagStack.splice(j, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
balancedStr += substr.substring(i, closeTagEnd + 1)
|
||||
i = closeTagEnd + 1
|
||||
continue
|
||||
}
|
||||
} else if (substr.substr(i, 2) === '<!' || (substr.indexOf('/>', i) !== -1 && substr.indexOf('/>', i) < substr.indexOf('>', i))) {
|
||||
const tagEnd = substr.indexOf('>', i)
|
||||
if (tagEnd !== -1) {
|
||||
balancedStr += substr.substring(i, tagEnd + 1)
|
||||
i = tagEnd + 1
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
const tagEnd = substr.indexOf('>', i)
|
||||
if (tagEnd !== -1) {
|
||||
const tagName = substr.substring(i + 1, (substr.indexOf(' ', i) > -1 && substr.indexOf(' ', i) < tagEnd)
|
||||
? substr.indexOf(' ', i)
|
||||
: tagEnd).split(/\s/)[0]
|
||||
tagStack.push(tagName)
|
||||
balancedStr += substr.substring(i, tagEnd + 1)
|
||||
i = tagEnd + 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
balancedStr += substr[i]
|
||||
i++
|
||||
}
|
||||
|
||||
// Close any unclosed tags
|
||||
while (tagStack.length > 0) {
|
||||
const tagName = tagStack.pop()
|
||||
balancedStr += `</${tagName}>`
|
||||
}
|
||||
|
||||
// If we removed content from the beginning, add prefix
|
||||
if (start > 0 || pre) {
|
||||
const actualFirstOpenBracket = contentStr.indexOf('<', start > 0 ? start - 30 : 0)
|
||||
const actualFirstMark = contentStr.indexOf('<mark>', start > 0 ? start - 30 : 0)
|
||||
|
||||
if (actualFirstOpenBracket !== -1 &&
|
||||
(actualFirstMark === -1 || actualFirstOpenBracket < actualFirstMark)) {
|
||||
pre = '...'
|
||||
}
|
||||
}
|
||||
|
||||
substr = balancedStr
|
||||
return `${pre}${substr}${post}`
|
||||
}
|
||||
|
||||
// Helper function to handle Algolia highlight results
|
||||
const extractHighlightValue = highlightObj => {
|
||||
if (!highlightObj) return ''
|
||||
|
||||
if (typeof highlightObj === 'string') {
|
||||
return highlightObj.trim()
|
||||
}
|
||||
|
||||
if (typeof highlightObj === 'object' && highlightObj.value !== undefined) {
|
||||
return String(highlightObj.value).trim()
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
// Initialize Algolia client
|
||||
let searchClient
|
||||
|
||||
if (window['algoliasearch/lite'] && typeof window['algoliasearch/lite'].liteClient === 'function') {
|
||||
searchClient = window['algoliasearch/lite'].liteClient(appId, apiKey)
|
||||
} else if (typeof window.algoliasearch === 'function') {
|
||||
searchClient = window.algoliasearch(appId, apiKey)
|
||||
} else {
|
||||
return console.error('Algolia search client not found!')
|
||||
}
|
||||
|
||||
if (!searchClient) {
|
||||
return console.error('Failed to initialize Algolia search client')
|
||||
}
|
||||
|
||||
// Search state
|
||||
let currentQuery = ''
|
||||
|
||||
// Show loading state
|
||||
const showLoading = show => {
|
||||
const loadingIndicator = document.getElementById('loading-status')
|
||||
if (loadingIndicator) {
|
||||
loadingIndicator.hidden = !show
|
||||
}
|
||||
}
|
||||
|
||||
// Cache frequently used elements
|
||||
const elements = {
|
||||
get searchInput () { return document.querySelector('#algolia-search-input .ais-SearchBox-input') },
|
||||
get hits () { return document.getElementById('algolia-hits') },
|
||||
get hitsEmpty () { return document.getElementById('algolia-hits-empty') },
|
||||
get hitsList () { return document.querySelector('#algolia-hits .ais-Hits-list') },
|
||||
get hitsWrapper () { return document.querySelector('#algolia-hits .ais-Hits') },
|
||||
get pagination () { return document.getElementById('algolia-pagination') },
|
||||
get paginationList () { return document.querySelector('#algolia-pagination .ais-Pagination-list') },
|
||||
get stats () { return document.querySelector('#algolia-info .ais-Stats-text') },
|
||||
}
|
||||
|
||||
// Show/hide search results area
|
||||
const toggleResultsVisibility = hasResults => {
|
||||
elements.pagination.style.display = hasResults ? '' : 'none'
|
||||
elements.stats.style.display = hasResults ? '' : 'none'
|
||||
}
|
||||
|
||||
// Render search results
|
||||
const renderHits = (hits, query, page = 0) => {
|
||||
if (hits.length === 0 && query) {
|
||||
elements.hitsEmpty.textContent = languages.hits_empty.replace(/\$\{query}/, query)
|
||||
elements.hitsEmpty.style.display = ''
|
||||
elements.hitsWrapper.style.display = 'none'
|
||||
elements.stats.style.display = 'none'
|
||||
return
|
||||
}
|
||||
|
||||
elements.hitsEmpty.style.display = 'none'
|
||||
|
||||
const hitsHTML = hits.map((hit, index) => {
|
||||
const itemNumber = page * hitsPerPage + index + 1
|
||||
const link = hit.permalink || (GLOBAL_CONFIG.root + hit.path)
|
||||
const result = hit._highlightResult || hit
|
||||
|
||||
// Content extraction
|
||||
let content = ''
|
||||
try {
|
||||
if (result.contentStripTruncate) {
|
||||
content = cutContent(result.contentStripTruncate)
|
||||
} else if (result.contentStrip) {
|
||||
content = cutContent(result.contentStrip)
|
||||
} else if (result.content) {
|
||||
content = cutContent(result.content)
|
||||
} else if (hit.contentStripTruncate) {
|
||||
content = cutContent(hit.contentStripTruncate)
|
||||
} else if (hit.contentStrip) {
|
||||
content = cutContent(hit.contentStrip)
|
||||
} else if (hit.content) {
|
||||
content = cutContent(hit.content)
|
||||
}
|
||||
} catch (error) {
|
||||
content = ''
|
||||
}
|
||||
|
||||
// Title handling
|
||||
let title = 'no-title'
|
||||
try {
|
||||
if (result.title) {
|
||||
title = extractHighlightValue(result.title) || 'no-title'
|
||||
} else if (hit.title) {
|
||||
title = extractHighlightValue(hit.title) || 'no-title'
|
||||
}
|
||||
|
||||
if (!title || title === 'no-title') {
|
||||
if (typeof hit.title === 'string' && hit.title.trim()) {
|
||||
title = hit.title.trim()
|
||||
} else if (hit.title && typeof hit.title === 'object' && hit.title.value) {
|
||||
title = String(hit.title.value).trim() || 'no-title'
|
||||
} else {
|
||||
title = 'no-title'
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
title = 'no-title'
|
||||
}
|
||||
|
||||
return `
|
||||
<li class="ais-Hits-item" value="${itemNumber}">
|
||||
<a href="${link}" class="algolia-hit-item-link">
|
||||
<span class="algolia-hits-item-title">${title}</span>
|
||||
${content ? `<div class="algolia-hit-item-content">${content}</div>` : ''}
|
||||
</a>
|
||||
</li>`
|
||||
}).join('')
|
||||
|
||||
elements.hitsList.innerHTML = hitsHTML
|
||||
elements.hitsWrapper.style.display = query ? '' : 'none'
|
||||
|
||||
if (hits.length > 0) {
|
||||
elements.stats.style.display = ''
|
||||
}
|
||||
}
|
||||
|
||||
// Render pagination
|
||||
const renderPagination = (page, nbPages) => {
|
||||
if (nbPages <= 1) {
|
||||
elements.pagination.style.display = 'none'
|
||||
elements.paginationList.innerHTML = ''
|
||||
return
|
||||
}
|
||||
|
||||
elements.pagination.style.display = 'block'
|
||||
|
||||
const isFirstPage = page === 0
|
||||
const isLastPage = page === nbPages - 1
|
||||
|
||||
// Responsive page display
|
||||
const isMobile = window.innerWidth < 768
|
||||
const maxVisiblePages = isMobile ? 3 : 5
|
||||
let startPage = Math.max(0, page - Math.floor(maxVisiblePages / 2))
|
||||
const endPage = Math.min(nbPages - 1, startPage + maxVisiblePages - 1)
|
||||
|
||||
// Adjust starting page to maintain max visible pages
|
||||
if (endPage - startPage + 1 < maxVisiblePages) {
|
||||
startPage = Math.max(0, endPage - maxVisiblePages + 1)
|
||||
}
|
||||
|
||||
let pagesHTML = ''
|
||||
|
||||
// Only add ellipsis and first page when there are many pages
|
||||
if (nbPages > maxVisiblePages && startPage > 0) {
|
||||
pagesHTML += `
|
||||
<li class="ais-Pagination-item ais-Pagination-item--page">
|
||||
<a class="ais-Pagination-link" aria-label="Page 1" href="#" data-page="0">1</a>
|
||||
</li>`
|
||||
if (startPage > 1) {
|
||||
pagesHTML += `
|
||||
<li class="ais-Pagination-item ais-Pagination-item--ellipsis">
|
||||
<span class="ais-Pagination-link">...</span>
|
||||
</li>`
|
||||
}
|
||||
}
|
||||
|
||||
// Add middle page numbers
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
const isSelected = i === page
|
||||
if (isSelected) {
|
||||
pagesHTML += `
|
||||
<li class="ais-Pagination-item ais-Pagination-item--page ais-Pagination-item--selected">
|
||||
<span class="ais-Pagination-link" aria-label="Page ${i + 1}">${i + 1}</span>
|
||||
</li>`
|
||||
} else {
|
||||
pagesHTML += `
|
||||
<li class="ais-Pagination-item ais-Pagination-item--page">
|
||||
<a class="ais-Pagination-link" aria-label="Page ${i + 1}" href="#" data-page="${i}">${i + 1}</a>
|
||||
</li>`
|
||||
}
|
||||
}
|
||||
|
||||
// Only add ellipsis and last page when there are many pages
|
||||
if (nbPages > maxVisiblePages && endPage < nbPages - 1) {
|
||||
if (endPage < nbPages - 2) {
|
||||
pagesHTML += `
|
||||
<li class="ais-Pagination-item ais-Pagination-item--ellipsis">
|
||||
<span class="ais-Pagination-link">...</span>
|
||||
</li>`
|
||||
}
|
||||
pagesHTML += `
|
||||
<li class="ais-Pagination-item ais-Pagination-item--page">
|
||||
<a class="ais-Pagination-link" aria-label="Page ${nbPages}" href="#" data-page="${nbPages - 1}">${nbPages}</a>
|
||||
</li>`
|
||||
}
|
||||
|
||||
if (nbPages > 1) {
|
||||
elements.paginationList.innerHTML = `
|
||||
<li class="ais-Pagination-item ais-Pagination-item--previousPage ${isFirstPage ? 'ais-Pagination-item--disabled' : ''}">
|
||||
${isFirstPage
|
||||
? '<span class="ais-Pagination-link ais-Pagination-link--disabled" aria-label="Previous Page"><i class="fas fa-angle-left"></i></span>'
|
||||
: `<a class="ais-Pagination-link" aria-label="Previous Page" href="#" data-page="${page - 1}"><i class="fas fa-angle-left"></i></a>`
|
||||
}
|
||||
</li>
|
||||
${pagesHTML}
|
||||
<li class="ais-Pagination-item ais-Pagination-item--nextPage ${isLastPage ? 'ais-Pagination-item--disabled' : ''}">
|
||||
${isLastPage
|
||||
? '<span class="ais-Pagination-link ais-Pagination-link--disabled" aria-label="Next Page"><i class="fas fa-angle-right"></i></span>'
|
||||
: `<a class="ais-Pagination-link" aria-label="Next Page" href="#" data-page="${page + 1}"><i class="fas fa-angle-right"></i></a>`
|
||||
}
|
||||
</li>`
|
||||
elements.pagination.style.display = currentQuery ? '' : 'none'
|
||||
} else {
|
||||
elements.pagination.style.display = 'none'
|
||||
}
|
||||
}
|
||||
|
||||
// Render statistics
|
||||
const renderStats = (nbHits, processingTimeMS, query) => {
|
||||
if (query) {
|
||||
const stats = languages.hits_stats
|
||||
.replace(/\$\{hits}/, nbHits)
|
||||
.replace(/\$\{time}/, processingTimeMS)
|
||||
elements.stats.innerHTML = `<hr>${stats}`
|
||||
elements.stats.style.display = ''
|
||||
} else {
|
||||
elements.stats.style.display = 'none'
|
||||
}
|
||||
}
|
||||
|
||||
// Perform search
|
||||
const performSearch = async (query, page = 0) => {
|
||||
if (!query.trim()) {
|
||||
currentQuery = ''
|
||||
renderHits([], '', 0)
|
||||
renderPagination(0, 0)
|
||||
renderStats(0, 0, '')
|
||||
toggleResultsVisibility(false)
|
||||
return
|
||||
}
|
||||
|
||||
showLoading(true)
|
||||
currentQuery = query
|
||||
|
||||
try {
|
||||
let result
|
||||
|
||||
if (searchClient && typeof searchClient.search === 'function') {
|
||||
// v5 multi-index search
|
||||
const searchResult = await searchClient.search([{
|
||||
indexName,
|
||||
query,
|
||||
params: {
|
||||
page,
|
||||
hitsPerPage,
|
||||
highlightPreTag: '<mark>',
|
||||
highlightPostTag: '</mark>',
|
||||
attributesToHighlight: ['title', 'content', 'contentStrip', 'contentStripTruncate']
|
||||
}
|
||||
}])
|
||||
result = searchResult.results[0]
|
||||
} else if (searchClient && typeof searchClient.initIndex === 'function') {
|
||||
// v4 single-index search
|
||||
const index = searchClient.initIndex(indexName)
|
||||
result = await index.search(query, {
|
||||
page,
|
||||
hitsPerPage,
|
||||
highlightPreTag: '<mark>',
|
||||
highlightPostTag: '</mark>',
|
||||
attributesToHighlight: ['title', 'content', 'contentStrip', 'contentStripTruncate']
|
||||
})
|
||||
} else {
|
||||
throw new Error('Algolia: No compatible search method available')
|
||||
}
|
||||
|
||||
renderHits(result.hits || [], query, page)
|
||||
|
||||
const actualNbPages = result.nbHits <= hitsPerPage ? 1 : (result.nbPages || 0)
|
||||
renderPagination(page, actualNbPages)
|
||||
renderStats(result.nbHits || 0, result.processingTimeMS || 0, query)
|
||||
|
||||
const hasResults = result.hits && result.hits.length > 0
|
||||
toggleResultsVisibility(hasResults)
|
||||
|
||||
// Refresh Pjax links
|
||||
if (window.pjax) {
|
||||
window.pjax.refresh(document.getElementById('algolia-hits'))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Algolia search error:', error)
|
||||
renderHits([], query, page)
|
||||
renderPagination(0, 0)
|
||||
renderStats(0, 0, query)
|
||||
} finally {
|
||||
showLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Debounced search
|
||||
let searchTimeout
|
||||
const debouncedSearch = (query, delay = 300) => {
|
||||
clearTimeout(searchTimeout)
|
||||
searchTimeout = setTimeout(() => performSearch(query), delay)
|
||||
}
|
||||
|
||||
// Initialize search box and events
|
||||
const initializeSearch = () => {
|
||||
showLoading(false)
|
||||
|
||||
if (elements.searchInput) {
|
||||
elements.searchInput.addEventListener('input', e => {
|
||||
const query = e.target.value
|
||||
debouncedSearch(query)
|
||||
})
|
||||
}
|
||||
|
||||
const searchForm = document.querySelector('#algolia-search-input .ais-SearchBox-form')
|
||||
if (searchForm) {
|
||||
searchForm.addEventListener('submit', e => {
|
||||
e.preventDefault()
|
||||
const query = elements.searchInput.value
|
||||
performSearch(query)
|
||||
})
|
||||
}
|
||||
|
||||
// Pagination event delegation
|
||||
elements.pagination.addEventListener('click', e => {
|
||||
e.preventDefault()
|
||||
const link = e.target.closest('a[data-page]')
|
||||
if (link) {
|
||||
const page = parseInt(link.dataset.page, 10)
|
||||
if (!isNaN(page) && currentQuery) {
|
||||
performSearch(currentQuery, page)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Initial state
|
||||
toggleResultsVisibility(false)
|
||||
}
|
||||
|
||||
// Initialize
|
||||
initializeSearch()
|
||||
searchClickFn()
|
||||
searchFnOnce()
|
||||
|
||||
window.addEventListener('pjax:complete', () => {
|
||||
if (!btf.isHidden($searchMask)) closeSearch()
|
||||
searchClickFn()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,567 @@
|
||||
/**
|
||||
* Refer to hexo-generator-searchdb
|
||||
* https://github.com/next-theme/hexo-generator-searchdb/blob/main/dist/search.js
|
||||
* Modified by hexo-theme-butterfly
|
||||
*/
|
||||
|
||||
class LocalSearch {
|
||||
constructor ({
|
||||
path = '',
|
||||
unescape = false,
|
||||
top_n_per_article = 1
|
||||
}) {
|
||||
this.path = path
|
||||
this.unescape = unescape
|
||||
this.top_n_per_article = top_n_per_article
|
||||
this.isfetched = false
|
||||
this.datas = null
|
||||
}
|
||||
|
||||
getIndexByWord (words, text, caseSensitive = false) {
|
||||
const index = []
|
||||
const included = new Set()
|
||||
|
||||
if (!caseSensitive) {
|
||||
text = text.toLowerCase()
|
||||
}
|
||||
words.forEach(word => {
|
||||
if (this.unescape) {
|
||||
const div = document.createElement('div')
|
||||
div.innerText = word
|
||||
word = div.innerHTML
|
||||
}
|
||||
const wordLen = word.length
|
||||
if (wordLen === 0) return
|
||||
let startPosition = 0
|
||||
let position = -1
|
||||
if (!caseSensitive) {
|
||||
word = word.toLowerCase()
|
||||
}
|
||||
while ((position = text.indexOf(word, startPosition)) > -1) {
|
||||
index.push({ position, word })
|
||||
included.add(word)
|
||||
startPosition = position + wordLen
|
||||
}
|
||||
})
|
||||
// Sort index by position of keyword
|
||||
index.sort((left, right) => {
|
||||
if (left.position !== right.position) {
|
||||
return left.position - right.position
|
||||
}
|
||||
return right.word.length - left.word.length
|
||||
})
|
||||
return [index, included]
|
||||
}
|
||||
|
||||
// Merge hits into slices
|
||||
mergeIntoSlice (start, end, index) {
|
||||
let item = index[0]
|
||||
let { position, word } = item
|
||||
const hits = []
|
||||
const count = new Set()
|
||||
while (position + word.length <= end && index.length !== 0) {
|
||||
count.add(word)
|
||||
hits.push({
|
||||
position,
|
||||
length: word.length
|
||||
})
|
||||
const wordEnd = position + word.length
|
||||
|
||||
// Move to next position of hit
|
||||
index.shift()
|
||||
while (index.length !== 0) {
|
||||
item = index[0]
|
||||
position = item.position
|
||||
word = item.word
|
||||
if (wordEnd > position) {
|
||||
index.shift()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
hits,
|
||||
start,
|
||||
end,
|
||||
count: count.size
|
||||
}
|
||||
}
|
||||
|
||||
// Highlight title and content
|
||||
highlightKeyword (val, slice) {
|
||||
let result = ''
|
||||
let index = slice.start
|
||||
for (const { position, length } of slice.hits) {
|
||||
result += val.substring(index, position)
|
||||
index = position + length
|
||||
result += `<mark class="search-keyword">${val.substr(position, length)}</mark>`
|
||||
}
|
||||
result += val.substring(index, slice.end)
|
||||
return result
|
||||
}
|
||||
|
||||
getResultItems (keywords) {
|
||||
const resultItems = []
|
||||
this.datas.forEach(({ title, content, url }) => {
|
||||
// The number of different keywords included in the article.
|
||||
const [indexOfTitle, keysOfTitle] = this.getIndexByWord(keywords, title)
|
||||
const [indexOfContent, keysOfContent] = this.getIndexByWord(keywords, content)
|
||||
const includedCount = new Set([...keysOfTitle, ...keysOfContent]).size
|
||||
|
||||
// Show search results
|
||||
const hitCount = indexOfTitle.length + indexOfContent.length
|
||||
if (hitCount === 0) return
|
||||
|
||||
const slicesOfTitle = []
|
||||
if (indexOfTitle.length !== 0) {
|
||||
slicesOfTitle.push(this.mergeIntoSlice(0, title.length, indexOfTitle))
|
||||
}
|
||||
|
||||
let slicesOfContent = []
|
||||
while (indexOfContent.length !== 0) {
|
||||
const item = indexOfContent[0]
|
||||
const { position } = item
|
||||
// Cut out 120 characters. The maxlength of .search-input is 80.
|
||||
const start = Math.max(0, position - 20)
|
||||
const end = Math.min(content.length, position + 100)
|
||||
slicesOfContent.push(this.mergeIntoSlice(start, end, indexOfContent))
|
||||
}
|
||||
|
||||
// Sort slices in content by included keywords' count and hits' count
|
||||
slicesOfContent.sort((left, right) => {
|
||||
if (left.count !== right.count) {
|
||||
return right.count - left.count
|
||||
} else if (left.hits.length !== right.hits.length) {
|
||||
return right.hits.length - left.hits.length
|
||||
}
|
||||
return left.start - right.start
|
||||
})
|
||||
|
||||
// Select top N slices in content
|
||||
const upperBound = parseInt(this.top_n_per_article, 10)
|
||||
if (upperBound >= 0) {
|
||||
slicesOfContent = slicesOfContent.slice(0, upperBound)
|
||||
}
|
||||
|
||||
let resultItem = ''
|
||||
|
||||
url = new URL(url, location.origin)
|
||||
url.searchParams.append('highlight', keywords.join(' '))
|
||||
|
||||
if (slicesOfTitle.length !== 0) {
|
||||
resultItem += `<li class="local-search-hit-item"><a href="${url.href}"><span class="search-result-title">${this.highlightKeyword(title, slicesOfTitle[0])}</span>`
|
||||
} else {
|
||||
resultItem += `<li class="local-search-hit-item"><a href="${url.href}"><span class="search-result-title">${title}</span>`
|
||||
}
|
||||
|
||||
slicesOfContent.forEach(slice => {
|
||||
resultItem += `<p class="search-result">${this.highlightKeyword(content, slice)}...</p>`
|
||||
})
|
||||
|
||||
resultItem += '</a></li>'
|
||||
resultItems.push({
|
||||
item: resultItem,
|
||||
id: resultItems.length,
|
||||
hitCount,
|
||||
includedCount
|
||||
})
|
||||
})
|
||||
return resultItems
|
||||
}
|
||||
|
||||
fetchData () {
|
||||
const isXml = !this.path.endsWith('json')
|
||||
fetch(this.path)
|
||||
.then(response => response.text())
|
||||
.then(res => {
|
||||
// Get the contents from search data
|
||||
this.isfetched = true
|
||||
this.datas = isXml
|
||||
? [...new DOMParser().parseFromString(res, 'text/xml').querySelectorAll('entry')].map(element => ({
|
||||
title: element.querySelector('title').textContent,
|
||||
content: element.querySelector('content').textContent,
|
||||
url: element.querySelector('url').textContent
|
||||
}))
|
||||
: JSON.parse(res)
|
||||
// Only match articles with non-empty titles
|
||||
this.datas = this.datas.filter(data => data.title).map(data => {
|
||||
data.title = data.title.trim()
|
||||
data.content = data.content ? data.content.trim().replace(/<[^>]+>/g, '') : ''
|
||||
data.url = decodeURIComponent(data.url).replace(/\/{2,}/g, '/')
|
||||
return data
|
||||
})
|
||||
// Remove loading animation
|
||||
window.dispatchEvent(new Event('search:loaded'))
|
||||
})
|
||||
}
|
||||
|
||||
// Highlight by wrapping node in mark elements with the given class name
|
||||
highlightText (node, slice, className) {
|
||||
const val = node.nodeValue
|
||||
let index = slice.start
|
||||
const children = []
|
||||
for (const { position, length } of slice.hits) {
|
||||
const text = document.createTextNode(val.substring(index, position))
|
||||
index = position + length
|
||||
const mark = document.createElement('mark')
|
||||
mark.className = className
|
||||
mark.appendChild(document.createTextNode(val.substr(position, length)))
|
||||
children.push(text, mark)
|
||||
}
|
||||
node.nodeValue = val.substring(index, slice.end)
|
||||
children.forEach(element => {
|
||||
node.parentNode.insertBefore(element, node)
|
||||
})
|
||||
}
|
||||
|
||||
// Highlight the search words provided in the url in the text
|
||||
highlightSearchWords (body) {
|
||||
const params = new URL(location.href).searchParams.get('highlight')
|
||||
const keywords = params ? params.split(' ') : []
|
||||
if (!keywords.length || !body) return
|
||||
const walk = document.createTreeWalker(body, NodeFilter.SHOW_TEXT, null)
|
||||
const allNodes = []
|
||||
while (walk.nextNode()) {
|
||||
if (!walk.currentNode.parentNode.matches('button, select, textarea, .mermaid')) allNodes.push(walk.currentNode)
|
||||
}
|
||||
allNodes.forEach(node => {
|
||||
const [indexOfNode] = this.getIndexByWord(keywords, node.nodeValue)
|
||||
if (!indexOfNode.length) return
|
||||
const slice = this.mergeIntoSlice(0, node.nodeValue.length, indexOfNode)
|
||||
this.highlightText(node, slice, 'search-keyword')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
// Search
|
||||
const { path, top_n_per_article, unescape, languages, pagination } = GLOBAL_CONFIG.localSearch
|
||||
const enablePagination = pagination && pagination.enable
|
||||
const localSearch = new LocalSearch({
|
||||
path,
|
||||
top_n_per_article,
|
||||
unescape
|
||||
})
|
||||
|
||||
const input = document.querySelector('.local-search-input input')
|
||||
const statsItem = document.getElementById('local-search-stats')
|
||||
const $loadingStatus = document.getElementById('loading-status')
|
||||
const isXml = !path.endsWith('json')
|
||||
|
||||
// Pagination variables (only initialize if pagination is enabled)
|
||||
let currentPage = 0
|
||||
const hitsPerPage = pagination.hitsPerPage || 10
|
||||
|
||||
let currentResultItems = []
|
||||
|
||||
if (!enablePagination) {
|
||||
// If pagination is disabled, we don't need these variables
|
||||
currentPage = undefined
|
||||
currentResultItems = undefined
|
||||
}
|
||||
|
||||
// Cache frequently used elements
|
||||
const elements = {
|
||||
get pagination () { return document.getElementById('local-search-pagination') },
|
||||
get paginationList () { return document.querySelector('#local-search-pagination .ais-Pagination-list') }
|
||||
}
|
||||
|
||||
// Show/hide search results area
|
||||
const toggleResultsVisibility = hasResults => {
|
||||
if (enablePagination) {
|
||||
elements.pagination.style.display = hasResults ? '' : 'none'
|
||||
} else {
|
||||
elements.pagination.style.display = 'none'
|
||||
}
|
||||
}
|
||||
|
||||
// Render search results for current page
|
||||
const renderResults = (searchText, resultItems) => {
|
||||
const container = document.getElementById('local-search-results')
|
||||
|
||||
// Determine items to display based on pagination mode
|
||||
const itemsToDisplay = enablePagination
|
||||
? currentResultItems.slice(currentPage * hitsPerPage, (currentPage + 1) * hitsPerPage)
|
||||
: resultItems
|
||||
|
||||
// Handle empty page in pagination mode
|
||||
if (enablePagination && itemsToDisplay.length === 0 && currentResultItems.length > 0) {
|
||||
currentPage = 0
|
||||
renderResults(searchText, resultItems)
|
||||
return
|
||||
}
|
||||
|
||||
// Add numbering to items
|
||||
const numberedItems = itemsToDisplay.map((result, index) => {
|
||||
const itemNumber = enablePagination
|
||||
? currentPage * hitsPerPage + index + 1
|
||||
: index + 1
|
||||
return result.item.replace(
|
||||
'<li class="local-search-hit-item">',
|
||||
`<li class="local-search-hit-item" value="${itemNumber}">`
|
||||
)
|
||||
})
|
||||
|
||||
container.innerHTML = `<ol class="search-result-list">${numberedItems.join('')}</ol>`
|
||||
|
||||
// Update stats
|
||||
const displayCount = enablePagination ? currentResultItems.length : resultItems.length
|
||||
const stats = languages.hits_stats.replace(/\$\{hits}/, displayCount)
|
||||
statsItem.innerHTML = `<hr><div class="search-result-stats">${stats}</div>`
|
||||
|
||||
// Handle pagination
|
||||
if (enablePagination) {
|
||||
const nbPages = Math.ceil(currentResultItems.length / hitsPerPage)
|
||||
renderPagination(currentPage, nbPages, searchText)
|
||||
}
|
||||
|
||||
const hasResults = resultItems.length > 0
|
||||
toggleResultsVisibility(hasResults)
|
||||
|
||||
window.pjax && window.pjax.refresh(container)
|
||||
}
|
||||
|
||||
// Render pagination
|
||||
const renderPagination = (page, nbPages, query) => {
|
||||
if (nbPages <= 1) {
|
||||
elements.pagination.style.display = 'none'
|
||||
elements.paginationList.innerHTML = ''
|
||||
return
|
||||
}
|
||||
|
||||
elements.pagination.style.display = 'block'
|
||||
|
||||
const isFirstPage = page === 0
|
||||
const isLastPage = page === nbPages - 1
|
||||
|
||||
// Responsive page display
|
||||
const isMobile = window.innerWidth < 768
|
||||
const maxVisiblePages = isMobile ? 3 : 5
|
||||
let startPage = Math.max(0, page - Math.floor(maxVisiblePages / 2))
|
||||
const endPage = Math.min(nbPages - 1, startPage + maxVisiblePages - 1)
|
||||
|
||||
// Adjust starting page to maintain max visible pages
|
||||
if (endPage - startPage + 1 < maxVisiblePages) {
|
||||
startPage = Math.max(0, endPage - maxVisiblePages + 1)
|
||||
}
|
||||
|
||||
let pagesHTML = ''
|
||||
|
||||
// Only add ellipsis and first page when there are many pages
|
||||
if (nbPages > maxVisiblePages && startPage > 0) {
|
||||
pagesHTML += `
|
||||
<li class="ais-Pagination-item ais-Pagination-item--page">
|
||||
<a class="ais-Pagination-link" aria-label="Page 1" href="#" data-page="0">1</a>
|
||||
</li>`
|
||||
if (startPage > 1) {
|
||||
pagesHTML += `
|
||||
<li class="ais-Pagination-item ais-Pagination-item--ellipsis">
|
||||
<span class="ais-Pagination-link">...</span>
|
||||
</li>`
|
||||
}
|
||||
}
|
||||
|
||||
// Add middle page numbers
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
const isSelected = i === page
|
||||
if (isSelected) {
|
||||
pagesHTML += `
|
||||
<li class="ais-Pagination-item ais-Pagination-item--page ais-Pagination-item--selected">
|
||||
<span class="ais-Pagination-link" aria-label="Page ${i + 1}">${i + 1}</span>
|
||||
</li>`
|
||||
} else {
|
||||
pagesHTML += `
|
||||
<li class="ais-Pagination-item ais-Pagination-item--page">
|
||||
<a class="ais-Pagination-link" aria-label="Page ${i + 1}" href="#" data-page="${i}">${i + 1}</a>
|
||||
</li>`
|
||||
}
|
||||
}
|
||||
|
||||
// Only add ellipsis and last page when there are many pages
|
||||
if (nbPages > maxVisiblePages && endPage < nbPages - 1) {
|
||||
if (endPage < nbPages - 2) {
|
||||
pagesHTML += `
|
||||
<li class="ais-Pagination-item ais-Pagination-item--ellipsis">
|
||||
<span class="ais-Pagination-link">...</span>
|
||||
</li>`
|
||||
}
|
||||
pagesHTML += `
|
||||
<li class="ais-Pagination-item ais-Pagination-item--page">
|
||||
<a class="ais-Pagination-link" aria-label="Page ${nbPages}" href="#" data-page="${nbPages - 1}">${nbPages}</a>
|
||||
</li>`
|
||||
}
|
||||
|
||||
if (nbPages > 1) {
|
||||
elements.paginationList.innerHTML = `
|
||||
<li class="ais-Pagination-item ais-Pagination-item--previousPage ${isFirstPage ? 'ais-Pagination-item--disabled' : ''}">
|
||||
${isFirstPage
|
||||
? '<span class="ais-Pagination-link ais-Pagination-link--disabled" aria-label="Previous Page"><i class="fas fa-angle-left"></i></span>'
|
||||
: `<a class="ais-Pagination-link" aria-label="Previous Page" href="#" data-page="${page - 1}"><i class="fas fa-angle-left"></i></a>`
|
||||
}
|
||||
</li>
|
||||
${pagesHTML}
|
||||
<li class="ais-Pagination-item ais-Pagination-item--nextPage ${isLastPage ? 'ais-Pagination-item--disabled' : ''}">
|
||||
${isLastPage
|
||||
? '<span class="ais-Pagination-link ais-Pagination-link--disabled" aria-label="Next Page"><i class="fas fa-angle-right"></i></span>'
|
||||
: `<a class="ais-Pagination-link" aria-label="Next Page" href="#" data-page="${page + 1}"><i class="fas fa-angle-right"></i></a>`
|
||||
}
|
||||
</li>`
|
||||
} else {
|
||||
elements.pagination.style.display = 'none'
|
||||
}
|
||||
}
|
||||
|
||||
// Clear search results and stats
|
||||
const clearSearchResults = () => {
|
||||
const container = document.getElementById('local-search-results')
|
||||
container.textContent = ''
|
||||
statsItem.textContent = ''
|
||||
toggleResultsVisibility(false)
|
||||
if (enablePagination) {
|
||||
currentResultItems = []
|
||||
currentPage = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Show no results message
|
||||
const showNoResults = searchText => {
|
||||
const container = document.getElementById('local-search-results')
|
||||
container.textContent = ''
|
||||
const statsDiv = document.createElement('div')
|
||||
statsDiv.className = 'search-result-stats'
|
||||
statsDiv.textContent = languages.hits_empty.replace(/\$\{query}/, searchText)
|
||||
statsItem.innerHTML = statsDiv.outerHTML
|
||||
toggleResultsVisibility(false)
|
||||
if (enablePagination) {
|
||||
currentResultItems = []
|
||||
currentPage = 0
|
||||
}
|
||||
}
|
||||
|
||||
const inputEventFunction = () => {
|
||||
if (!localSearch.isfetched) return
|
||||
let searchText = input.value.trim().toLowerCase()
|
||||
isXml && (searchText = searchText.replace(/</g, '<').replace(/>/g, '>'))
|
||||
|
||||
if (searchText !== '') $loadingStatus.hidden = false
|
||||
|
||||
const keywords = searchText.split(/[-\s]+/)
|
||||
let resultItems = []
|
||||
|
||||
if (searchText.length > 0) {
|
||||
resultItems = localSearch.getResultItems(keywords)
|
||||
}
|
||||
|
||||
if (keywords.length === 1 && keywords[0] === '') {
|
||||
clearSearchResults()
|
||||
} else if (resultItems.length === 0) {
|
||||
showNoResults(searchText)
|
||||
} else {
|
||||
// Sort results by relevance
|
||||
resultItems.sort((left, right) => {
|
||||
if (left.includedCount !== right.includedCount) {
|
||||
return right.includedCount - left.includedCount
|
||||
} else if (left.hitCount !== right.hitCount) {
|
||||
return right.hitCount - left.hitCount
|
||||
}
|
||||
return right.id - left.id
|
||||
})
|
||||
|
||||
if (enablePagination) {
|
||||
currentResultItems = resultItems
|
||||
currentPage = 0
|
||||
}
|
||||
renderResults(searchText, resultItems)
|
||||
}
|
||||
|
||||
$loadingStatus.hidden = true
|
||||
}
|
||||
|
||||
let loadFlag = false
|
||||
const $searchMask = document.getElementById('search-mask')
|
||||
const $searchDialog = document.querySelector('#local-search .search-dialog')
|
||||
|
||||
// fix safari
|
||||
const fixSafariHeight = () => {
|
||||
if (window.innerWidth < 768) {
|
||||
$searchDialog.style.setProperty('--search-height', window.innerHeight + 'px')
|
||||
}
|
||||
}
|
||||
|
||||
const openSearch = () => {
|
||||
btf.overflowPaddingR.add()
|
||||
btf.animateIn($searchMask, 'to_show 0.5s')
|
||||
btf.animateIn($searchDialog, 'titleScale 0.5s')
|
||||
setTimeout(() => { input.focus() }, 300)
|
||||
if (!loadFlag) {
|
||||
!localSearch.isfetched && localSearch.fetchData()
|
||||
input.addEventListener('input', inputEventFunction)
|
||||
loadFlag = true
|
||||
}
|
||||
// shortcut: ESC
|
||||
document.addEventListener('keydown', function f (event) {
|
||||
if (event.code === 'Escape') {
|
||||
closeSearch()
|
||||
document.removeEventListener('keydown', f)
|
||||
}
|
||||
})
|
||||
|
||||
fixSafariHeight()
|
||||
window.addEventListener('resize', fixSafariHeight)
|
||||
}
|
||||
|
||||
const closeSearch = () => {
|
||||
btf.overflowPaddingR.remove()
|
||||
btf.animateOut($searchDialog, 'search_close .5s')
|
||||
btf.animateOut($searchMask, 'to_hide 0.5s')
|
||||
window.removeEventListener('resize', fixSafariHeight)
|
||||
}
|
||||
|
||||
const searchClickFn = () => {
|
||||
btf.addEventListenerPjax(document.querySelector('#search-button > .search'), 'click', openSearch)
|
||||
}
|
||||
|
||||
const searchFnOnce = () => {
|
||||
document.querySelector('#local-search .search-close-button').addEventListener('click', closeSearch)
|
||||
$searchMask.addEventListener('click', closeSearch)
|
||||
if (GLOBAL_CONFIG.localSearch.preload) {
|
||||
localSearch.fetchData()
|
||||
}
|
||||
localSearch.highlightSearchWords(document.getElementById('article-container'))
|
||||
|
||||
// Pagination event delegation - only add if pagination is enabled
|
||||
if (enablePagination) {
|
||||
elements.pagination.addEventListener('click', e => {
|
||||
e.preventDefault()
|
||||
const link = e.target.closest('a[data-page]')
|
||||
if (link) {
|
||||
const page = parseInt(link.dataset.page, 10)
|
||||
if (!isNaN(page) && currentResultItems.length > 0) {
|
||||
currentPage = page
|
||||
renderResults(input.value.trim().toLowerCase(), currentResultItems)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Initial state
|
||||
toggleResultsVisibility(false)
|
||||
}
|
||||
|
||||
window.addEventListener('search:loaded', () => {
|
||||
const $loadDataItem = document.getElementById('loading-database')
|
||||
$loadDataItem.nextElementSibling.style.visibility = 'visible'
|
||||
$loadDataItem.remove()
|
||||
})
|
||||
|
||||
searchClickFn()
|
||||
searchFnOnce()
|
||||
|
||||
// pjax
|
||||
window.addEventListener('pjax:complete', () => {
|
||||
!btf.isHidden($searchMask) && closeSearch()
|
||||
localSearch.highlightSearchWords(document.getElementById('article-container'))
|
||||
searchClickFn()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,321 @@
|
||||
function renderTalks() {
|
||||
const talkContainer = document.querySelector('#talk');
|
||||
if (!talkContainer) return;
|
||||
talkContainer.innerHTML = '';
|
||||
const generateIconSVG = () => {
|
||||
return `<svg viewBox="0 0 512 512"xmlns="http://www.w3.org/2000/svg"class="is-badge icon"><path d="m512 268c0 17.9-4.3 34.5-12.9 49.7s-20.1 27.1-34.6 35.4c.4 2.7.6 6.9.6 12.6 0 27.1-9.1 50.1-27.1 69.1-18.1 19.1-39.9 28.6-65.4 28.6-11.4 0-22.3-2.1-32.6-6.3-8 16.4-19.5 29.6-34.6 39.7-15 10.2-31.5 15.2-49.4 15.2-18.3 0-34.9-4.9-49.7-14.9-14.9-9.9-26.3-23.2-34.3-40-10.3 4.2-21.1 6.3-32.6 6.3-25.5 0-47.4-9.5-65.7-28.6-18.3-19-27.4-42.1-27.4-69.1 0-3 .4-7.2 1.1-12.6-14.5-8.4-26-20.2-34.6-35.4-8.5-15.2-12.8-31.8-12.8-49.7 0-19 4.8-36.5 14.3-52.3s22.3-27.5 38.3-35.1c-4.2-11.4-6.3-22.9-6.3-34.3 0-27 9.1-50.1 27.4-69.1s40.2-28.6 65.7-28.6c11.4 0 22.3 2.1 32.6 6.3 8-16.4 19.5-29.6 34.6-39.7 15-10.1 31.5-15.2 49.4-15.2s34.4 5.1 49.4 15.1c15 10.1 26.6 23.3 34.6 39.7 10.3-4.2 21.1-6.3 32.6-6.3 25.5 0 47.3 9.5 65.4 28.6s27.1 42.1 27.1 69.1c0 12.6-1.9 24-5.7 34.3 16 7.6 28.8 19.3 38.3 35.1 9.5 15.9 14.3 33.4 14.3 52.4zm-266.9 77.1 105.7-158.3c2.7-4.2 3.5-8.8 2.6-13.7-1-4.9-3.5-8.8-7.7-11.4-4.2-2.7-8.8-3.6-13.7-2.9-5 .8-9 3.2-12 7.4l-93.1 140-42.9-42.8c-3.8-3.8-8.2-5.6-13.1-5.4-5 .2-9.3 2-13.1 5.4-3.4 3.4-5.1 7.7-5.1 12.9 0 5.1 1.7 9.4 5.1 12.9l58.9 58.9 2.9 2.3c3.4 2.3 6.9 3.4 10.3 3.4 6.7-.1 11.8-2.9 15.2-8.7z"fill="#1da1f2"></path></svg>`;
|
||||
}
|
||||
const waterfall = (a) => {
|
||||
function b(a, b) {
|
||||
var c = window.getComputedStyle(b);
|
||||
return parseFloat(c["margin" + a]) || 0
|
||||
}
|
||||
|
||||
function c(a) {
|
||||
return a + "px"
|
||||
}
|
||||
|
||||
function d(a) {
|
||||
return parseFloat(a.style.top)
|
||||
}
|
||||
|
||||
function e(a) {
|
||||
return parseFloat(a.style.left)
|
||||
}
|
||||
|
||||
function f(a) {
|
||||
return a.clientWidth
|
||||
}
|
||||
|
||||
function g(a) {
|
||||
return a.clientHeight
|
||||
}
|
||||
|
||||
function h(a) {
|
||||
return d(a) + g(a) + b("Bottom", a)
|
||||
}
|
||||
|
||||
function i(a) {
|
||||
return e(a) + f(a) + b("Right", a)
|
||||
}
|
||||
|
||||
function j(a) {
|
||||
a = a.sort(function (a, b) {
|
||||
return h(a) === h(b) ? e(b) - e(a) : h(b) - h(a)
|
||||
})
|
||||
}
|
||||
|
||||
function k(b) {
|
||||
f(a) != t && (b.target.removeEventListener(b.type, arguments.callee), waterfall(a))
|
||||
}
|
||||
"string" == typeof a && (a = document.querySelector(a));
|
||||
var l = [].map.call(a.children, function (a) {
|
||||
return a.style.position = "absolute", a
|
||||
});
|
||||
a.style.position = "relative";
|
||||
var m = [];
|
||||
l.length && (l[0].style.top = "0px", l[0].style.left = c(b("Left", l[0])), m.push(l[0]));
|
||||
for (var n = 1; n < l.length; n++) {
|
||||
var o = l[n - 1],
|
||||
p = l[n],
|
||||
q = i(o) + f(p) <= f(a);
|
||||
if (!q) break;
|
||||
p.style.top = o.style.top, p.style.left = c(i(o) + b("Left", p)), m.push(p)
|
||||
}
|
||||
for (; n < l.length; n++) {
|
||||
j(m);
|
||||
var p = l[n],
|
||||
r = m.pop();
|
||||
p.style.top = c(h(r) + b("Top", p)), p.style.left = c(e(r)), m.push(p)
|
||||
}
|
||||
j(m);
|
||||
var s = m[0];
|
||||
a.style.height = c(h(s) + b("Bottom", s));
|
||||
var t = f(a);
|
||||
window.addEventListener ? window.addEventListener("resize", k) : document.body.onresize = k
|
||||
};
|
||||
|
||||
const fetchAndRenderTalks = () => {
|
||||
const url = 'https://mm.biss.click/api/echo/page';
|
||||
const cacheKey = 'talksCache';
|
||||
const cacheTimeKey = 'talksCacheTime';
|
||||
const cacheDuration = 30 * 60 * 1000;
|
||||
const cachedData = localStorage.getItem(cacheKey);
|
||||
const cachedTime = localStorage.getItem(cacheTimeKey);
|
||||
const now = Date.now();
|
||||
|
||||
if (cachedData && cachedTime && (now - cachedTime < cacheDuration)) {
|
||||
renderTalksList(JSON.parse(cachedData));
|
||||
} else {
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ page: 1, pageSize: 30 })
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data.code === 1 && data.data && Array.isArray(data.data.items)) {
|
||||
localStorage.setItem(cacheKey, JSON.stringify(data.data.items));
|
||||
localStorage.setItem(cacheTimeKey, now.toString());
|
||||
renderTalksList(data.data.items);
|
||||
}
|
||||
})
|
||||
.catch(err => console.error('Error fetching:', err));
|
||||
}
|
||||
};
|
||||
|
||||
const renderTalksList = (list) => {
|
||||
list.map(formatTalk).forEach(item => talkContainer.appendChild(generateTalkElement(item)));
|
||||
waterfall('#talk');
|
||||
};
|
||||
|
||||
const formatTalk = (item) => {
|
||||
const date = formatTime(item.created_at);
|
||||
let content = item.content || '';
|
||||
content = content.replace(/\[(.*?)\]\((.*?)\)/g, `<a href="$2" target="_blank" rel="nofollow noopener">@$1</a>`)
|
||||
.replace(/- \[ \]/g, '⚪')
|
||||
.replace(/- \[x\]/g, '⚫')
|
||||
.replace(/\n/g, '<br>');
|
||||
content = `<div class="talk_content_text">${content}</div>`;
|
||||
|
||||
// 图片
|
||||
if (Array.isArray(item.images) && item.images.length > 0) {
|
||||
const imgDiv = document.createElement('div');
|
||||
imgDiv.className = 'zone_imgbox';
|
||||
item.images.forEach(img => {
|
||||
const link = document.createElement('a');
|
||||
link.href = img.image_url + "?fmt=webp&q=75";
|
||||
link.setAttribute('data-fancybox', 'gallery');
|
||||
link.className = 'fancybox';
|
||||
const imgTag = document.createElement('img');
|
||||
imgTag.src = img.image_url + "?fmt=webp&q=75";
|
||||
link.appendChild(imgTag);
|
||||
imgDiv.appendChild(link);
|
||||
});
|
||||
content += imgDiv.outerHTML;
|
||||
}
|
||||
|
||||
// 外链 / GitHub 项目
|
||||
// 外链 / GitHub 项目
|
||||
if (['WEBSITE', 'GITHUBPROJ'].includes(item.extension_type)) {
|
||||
let siteUrl = '', title = '';
|
||||
let extensionBack = "https://pic.biss.click/image/1971bdc1-4349-4bb9-b683-20404f5da7d7.webp";
|
||||
|
||||
// 解析 extension 字段
|
||||
try {
|
||||
const extObj = typeof item.extension === 'string' ? JSON.parse(item.extension) : item.extension;
|
||||
siteUrl = extObj.site || extObj.url || item.extension;
|
||||
title = extObj.title || siteUrl;
|
||||
} catch {
|
||||
siteUrl = item.extension;
|
||||
title = siteUrl;
|
||||
}
|
||||
|
||||
// 特殊处理 GitHub 项目
|
||||
if (item.extension_type === 'GITHUBPROJ') {
|
||||
extensionBack = "https://pic.biss.click/image/ed410d4e-d3f8-4b26-8840-50dd58f7dc4e.webp";
|
||||
|
||||
// 提取 GitHub 项目名
|
||||
const match = siteUrl.match(/^https?:\/\/github\.com\/[^/]+\/([^/?#]+)/i);
|
||||
if (match) {
|
||||
title = match[1]; // 获取仓库名
|
||||
} else {
|
||||
// fallback:从最后一个路径段提取
|
||||
try {
|
||||
const parts = new URL(siteUrl).pathname.split('/').filter(Boolean);
|
||||
title = parts.pop() || siteUrl;
|
||||
} catch {
|
||||
// 如果 URL 无效则保留原始
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 输出 HTML 结构
|
||||
content += `
|
||||
<div class="shuoshuo-external-link">
|
||||
<a class="external-link" href="${siteUrl}" target="_blank" rel="nofollow noopener">
|
||||
<div class="external-link-left" style="background-image:url(${extensionBack})"></div>
|
||||
<div class="external-link-right">
|
||||
<div class="external-link-title">${title}</div>
|
||||
<div>点击跳转<i class="fa-solid fa-angle-right"></i></div>
|
||||
</div>
|
||||
</a>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
|
||||
// 音乐
|
||||
if (item.extension_type === 'MUSIC' && item.extension) {
|
||||
const link = item.extension;
|
||||
let server = '';
|
||||
if (link.includes('music.163.com')) server = 'netease';
|
||||
else if (link.includes('y.qq.com')) server = 'tencent';
|
||||
const idMatch = link.match(/id=(\d+)/);
|
||||
const id = idMatch ? idMatch[1] : '';
|
||||
if (server && id) {
|
||||
content += `<meting-js server="${server}" type="song" id="${id}" api="https://meting.qjqq.cn/?server=:server&type=:type&id=:id&auth=:auth&r=:r"></meting-js>`;
|
||||
}
|
||||
}
|
||||
|
||||
// 视频
|
||||
if (item.extension_type === 'VIDEO' && item.extension) {
|
||||
const video = item.extension;
|
||||
if (video.startsWith('BV')) {
|
||||
const bilibiliUrl = `https://www.bilibili.com/blackboard/html5mobileplayer.html?bvid=${video}&as_wide=1&high_quality=1&danmaku=0`;
|
||||
content += `
|
||||
<div style="position: relative; padding: 30% 45%; margin-top: 10px;">
|
||||
<iframe style="position:absolute;width:100%;height:100%;left:0;top:0;border-radius:12px;"
|
||||
src="${bilibiliUrl}"
|
||||
frameborder="no"
|
||||
allowfullscreen="true"
|
||||
loading="lazy"></iframe>
|
||||
</div>`;
|
||||
} else {
|
||||
const youtubeUrl = `https://www.youtube.com/embed/${video}`;
|
||||
content += `
|
||||
<div style="position: relative; padding: 30% 45%; margin-top: 10px;">
|
||||
<iframe style="position:absolute;width:100%;height:100%;left:0;top:0;border-radius:12px;"
|
||||
src="${youtubeUrl}"
|
||||
title="YouTube video player"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowfullscreen></iframe>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
content,
|
||||
user: item.username || '匿名',
|
||||
avatar: 'https://free.picui.cn/free/2025/08/10/689845496a283.png',
|
||||
date,
|
||||
location: '',
|
||||
tags: Array.isArray(item.tags) && item.tags.length ? item.tags.map(t => t.name) : ['无标签'],
|
||||
text: content.replace(/\[(.*?)\]\((.*?)\)/g, '[链接]')
|
||||
};
|
||||
};
|
||||
|
||||
const generateTalkElement = (item) => {
|
||||
const talkItem = document.createElement('div');
|
||||
talkItem.className = 'talk_item';
|
||||
|
||||
const talkMeta = document.createElement('div');
|
||||
talkMeta.className = 'talk_meta';
|
||||
const avatar = document.createElement('img');
|
||||
avatar.className = 'no-lightbox avatar';
|
||||
avatar.src = item.avatar;
|
||||
|
||||
const info = document.createElement('div');
|
||||
info.className = 'info';
|
||||
const nick = document.createElement('span');
|
||||
nick.className = 'talk_nick';
|
||||
nick.innerHTML = `${item.user} ${generateIconSVG()}`;
|
||||
const date = document.createElement('span');
|
||||
date.className = 'talk_date';
|
||||
date.textContent = item.date;
|
||||
info.appendChild(nick);
|
||||
info.appendChild(date);
|
||||
talkMeta.appendChild(avatar);
|
||||
talkMeta.appendChild(info);
|
||||
|
||||
const talkContent = document.createElement('div');
|
||||
talkContent.className = 'talk_content';
|
||||
talkContent.innerHTML = item.content;
|
||||
|
||||
const talkBottom = document.createElement('div');
|
||||
talkBottom.className = 'talk_bottom';
|
||||
const tags = document.createElement('div');
|
||||
const tag = document.createElement('span');
|
||||
tag.className = 'talk_tag';
|
||||
tag.textContent = `🏷️${item.tags}`;
|
||||
//const loc = document.createElement('span');
|
||||
//loc.className = 'location_tag';
|
||||
//loc.textContent = `🌍${item.location}`;
|
||||
tags.appendChild(tag);
|
||||
//tags.appendChild(loc);
|
||||
|
||||
const commentLink = document.createElement('a');
|
||||
commentLink.href = 'javascript:;';
|
||||
commentLink.onclick = () => goComment(item.text);
|
||||
const icon = document.createElement('span');
|
||||
icon.className = 'icon';
|
||||
icon.innerHTML = '<i class="fa-solid fa-message fa-fw"></i>';
|
||||
commentLink.appendChild(icon);
|
||||
|
||||
talkBottom.appendChild(tags);
|
||||
talkBottom.appendChild(commentLink);
|
||||
|
||||
talkItem.appendChild(talkMeta);
|
||||
talkItem.appendChild(talkContent);
|
||||
talkItem.appendChild(talkBottom);
|
||||
|
||||
return talkItem;
|
||||
};
|
||||
|
||||
const goComment = (e) => {
|
||||
const match = e.match(/<div class="talk_content_text">([\s\S]*?)<\/div>/);
|
||||
const textContent = match ? match[1] : "";
|
||||
const textarea = document.querySelector("#twikoo .el-textarea__inner");
|
||||
textarea.value = `> ${textContent}\n\n`;
|
||||
textarea.focus();
|
||||
btf.snackbarShow("已为您引用该说说,不删除空格效果更佳");
|
||||
};
|
||||
|
||||
const formatTime = (time) => {
|
||||
const d = new Date(time);
|
||||
const pad = (n) => n.toString().padStart(2, '0');
|
||||
return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
||||
};
|
||||
|
||||
fetchAndRenderTalks();
|
||||
}
|
||||
|
||||
renderTalks();
|
||||
|
||||
// function whenDOMReady() {
|
||||
// const talkContainer = document.querySelector('#talk');
|
||||
// talkContainer.innerHTML = '';
|
||||
// fetchAndRenderTalks();
|
||||
// }
|
||||
// whenDOMReady();
|
||||
// document.addEventListener("pjax:complete", whenDOMReady);
|
||||
@@ -0,0 +1,97 @@
|
||||
let talkTimer = null;
|
||||
|
||||
const cacheKey = 'talksCache';
|
||||
const cacheTimeKey = 'talksCacheTime';
|
||||
const cacheDuration = 30 * 60 * 1000; // 缓存有效期 30分钟
|
||||
|
||||
function indexTalk() {
|
||||
if (talkTimer) {
|
||||
clearInterval(talkTimer);
|
||||
talkTimer = null;
|
||||
}
|
||||
|
||||
if (!document.getElementById('bber-talk')) return;
|
||||
|
||||
function toText(ls) {
|
||||
return ls.map(item => {
|
||||
let c = item.content || '';
|
||||
|
||||
const hasImg = /\!\[.*?\]\(.*?\)/.test(c);
|
||||
const hasLink = /\[.*?\]\(.*?\)/.test(c);
|
||||
|
||||
c = c
|
||||
.replace(/#(.*?)\s/g, '')
|
||||
.replace(/\{.*?\}/g, '')
|
||||
.replace(/\!\[.*?\]\(.*?\)/g, '<i class="fa-solid fa-image"></i>')
|
||||
.replace(/\[.*?\]\(.*?\)/g, '<i class="fa-solid fa-link"></i>');
|
||||
|
||||
const icons = [];
|
||||
|
||||
if (item.images?.length && !hasImg) icons.push('fa-solid fa-image');
|
||||
if (item.extension_type === 'VIDEO') icons.push('fa-solid fa-video');
|
||||
if (item.extension_type === 'MUSIC') icons.push('fa-solid fa-music');
|
||||
if (item.extension_type === 'WEBSITE' && !hasLink) icons.push('fa-solid fa-link');
|
||||
if (item.extension_type === 'GITHUBPROJ' && !hasLink) icons.push('fab fa-github');
|
||||
|
||||
if (icons.length) c += ' ' + icons.map(i => `<i class="${i}"></i>`).join(' ');
|
||||
return c;
|
||||
});
|
||||
}
|
||||
|
||||
// 渲染与轮播
|
||||
function talk(ls) {
|
||||
let html = '';
|
||||
ls.forEach((item, i) => {
|
||||
html += `<li class="item item-${i + 1}">${item}</li>`;
|
||||
});
|
||||
|
||||
let box = document.querySelector("#bber-talk .talk-list");
|
||||
if (!box) return;
|
||||
|
||||
box.innerHTML = html;
|
||||
|
||||
talkTimer = setInterval(() => {
|
||||
if (box.children.length > 0) {
|
||||
box.appendChild(box.children[0]);
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
const cachedData = localStorage.getItem(cacheKey);
|
||||
const cachedTime = localStorage.getItem(cacheTimeKey);
|
||||
const currentTime = new Date().getTime();
|
||||
|
||||
// 判断缓存是否有效
|
||||
if (cachedData && cachedTime && (currentTime - cachedTime < cacheDuration)) {
|
||||
const data = toText(JSON.parse(cachedData));
|
||||
talk(data.slice(0, 6)); // 使用缓存渲染数据
|
||||
} else {
|
||||
fetch('https://mm.biss.click/api/echo/page', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ page: 1, pageSize: 30 })
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
// 适配新版结构:code=1 且 data.items 存在
|
||||
if (data.code === 1 && data.data && Array.isArray(data.data.items)) {
|
||||
localStorage.setItem(cacheKey, JSON.stringify(data.data.items));
|
||||
localStorage.setItem(cacheTimeKey, currentTime.toString());
|
||||
|
||||
const formattedData = toText(data.data.items);
|
||||
talk(formattedData.slice(0, 6));
|
||||
} else {
|
||||
console.warn('Unexpected API response format:', data);
|
||||
}
|
||||
})
|
||||
.catch(error => console.error('Error fetching data:', error));
|
||||
}
|
||||
}
|
||||
|
||||
// pjax 支持
|
||||
function whenDOMReady() {
|
||||
indexTalk();
|
||||
}
|
||||
|
||||
whenDOMReady();
|
||||
document.addEventListener("pjax:complete", whenDOMReady);
|
||||
@@ -0,0 +1,11 @@
|
||||
var _paq = window._paq = window._paq || [];
|
||||
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
|
||||
_paq.push(['trackPageView']);
|
||||
_paq.push(['enableLinkTracking']);
|
||||
(function() {
|
||||
var u="https://statistic.biss.click/";
|
||||
_paq.push(['setTrackerUrl', u+'matomo.php']);
|
||||
_paq.push(['setSiteId', '1']);
|
||||
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
|
||||
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
|
||||
})();
|
||||
@@ -0,0 +1,545 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
// ============================================================================
|
||||
// 配置区域 - 请根据实际情况修改
|
||||
// ============================================================================
|
||||
const CONFIG = {
|
||||
apiKey: "VramSTWKUAggeZ5viQw8SlCwXQqmGCmA", // ⚠️ 建议使用 Search-Only API Key
|
||||
server: {
|
||||
host: "typesense.biss.click",
|
||||
port: "443",
|
||||
protocol: "https"
|
||||
},
|
||||
indexName: "blogs",
|
||||
searchParams: {
|
||||
query_by: "title,content",
|
||||
highlight_full_fields: "title,content",
|
||||
per_page: 8,
|
||||
num_typos: 1,
|
||||
typo_tokens_threshold: 1,
|
||||
prefix: true
|
||||
},
|
||||
ui: {
|
||||
maxRetries: 10,
|
||||
retryDelay: 100,
|
||||
animationDuration: 300
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 状态管理
|
||||
// ============================================================================
|
||||
let searchInstance = null;
|
||||
let isInitialized = false;
|
||||
let isSearchOpen = false;
|
||||
let initRetryCount = 0;
|
||||
const MAX_INIT_RETRIES = 30; // 最多重试30次 (3秒)
|
||||
|
||||
// ============================================================================
|
||||
// 错误提示函数
|
||||
// ============================================================================
|
||||
function showErrorMessage() {
|
||||
const hitsContainer = document.getElementById('hits');
|
||||
if (!hitsContainer) return;
|
||||
|
||||
hitsContainer.innerHTML =
|
||||
'<div class="ts-empty">' +
|
||||
'<div style="color: #f44336;"><i class="fas fa-exclamation-triangle" style="font-size: 3rem;"></i></div>' +
|
||||
'<div style="font-size: 1.1rem; font-weight: bold; margin: 15px 0;">搜索服务加载失败</div>' +
|
||||
'<div style="font-size: 0.9rem; color: #666; line-height: 1.8;">' +
|
||||
'<p>依赖库未能正确加载,请检查以下配置:</p>' +
|
||||
'<ol style="text-align: left; max-width: 500px; margin: 15px auto;">' +
|
||||
'<li>确认已在 <code>_config.butterfly.yml</code> 中正确引入依赖</li>' +
|
||||
'<li>检查 JS 文件加载顺序(先 instantsearch.js,再 adapter)</li>' +
|
||||
'<li>尝试更换 CDN 或使用本地文件</li>' +
|
||||
'<li>打开浏览器控制台查看详细错误信息</li>' +
|
||||
'</ol>' +
|
||||
'</div>' +
|
||||
'<div style="margin-top: 20px;">' +
|
||||
'<button onclick="location.reload()" style="padding: 10px 20px; background: #49b1f5; color: white; border: none; border-radius: 5px; cursor: pointer;">重新加载页面</button>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 1. 动态插入 HTML 结构
|
||||
// ============================================================================
|
||||
const searchHTML = `
|
||||
<div id="typesense-search-mask" class="ts-mask" style="display:none;">
|
||||
<div id="typesense-search-container" class="ts-container">
|
||||
<div class="ts-header">
|
||||
<span class="ts-title">
|
||||
<i class="fas fa-search"></i> 本站搜索
|
||||
</span>
|
||||
<span id="close-typesense" class="ts-close" aria-label="关闭搜索">×</span>
|
||||
</div>
|
||||
<div id="searchbox"></div>
|
||||
<div id="stats" class="ts-stats"></div>
|
||||
<div id="hits" class="ts-hits"></div>
|
||||
<div id="pagination" class="ts-pagination"></div>
|
||||
<div class="ts-footer">
|
||||
<small>Search powered by <strong>Typesense</strong></small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.ts-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
z-index: 10000;
|
||||
backdrop-filter: blur(5px);
|
||||
-webkit-backdrop-filter: blur(5px);
|
||||
opacity: 0;
|
||||
transition: opacity 300ms ease;
|
||||
}
|
||||
.ts-mask.active { opacity: 1; }
|
||||
.ts-container {
|
||||
margin: 5% auto;
|
||||
width: 90%;
|
||||
max-width: 650px;
|
||||
background: var(--search-bg, var(--card-bg, #fff));
|
||||
padding: 25px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.3);
|
||||
position: relative;
|
||||
z-index: 10001;
|
||||
transform: translateY(-50px);
|
||||
opacity: 0;
|
||||
transition: all 300ms ease;
|
||||
}
|
||||
.ts-mask.active .ts-container {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
.ts-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 2px solid var(--text-highlight-color, #49b1f5);
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.ts-title {
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
color: var(--text-highlight-color, #49b1f5);
|
||||
}
|
||||
.ts-close {
|
||||
cursor: pointer;
|
||||
font-size: 28px;
|
||||
color: var(--font-color, #333);
|
||||
line-height: 1;
|
||||
transition: color 0.2s, transform 0.2s;
|
||||
}
|
||||
.ts-close:hover {
|
||||
color: var(--text-highlight-color, #49b1f5);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
.ais-SearchBox-input {
|
||||
position: relative;
|
||||
z-index: 10002;
|
||||
cursor: text;
|
||||
padding: 12px 40px 12px 15px !important;
|
||||
border-radius: 8px !important;
|
||||
border: 2px solid #eee !important;
|
||||
width: 100%;
|
||||
outline: none;
|
||||
transition: border-color 0.3s, box-shadow 0.3s;
|
||||
background: var(--card-bg, #fff);
|
||||
color: var(--font-color, #333);
|
||||
font-size: 1rem;
|
||||
}
|
||||
.ais-SearchBox-input:focus {
|
||||
border-color: var(--text-highlight-color, #49b1f5) !important;
|
||||
box-shadow: 0 0 0 3px rgba(73, 177, 245, 0.1);
|
||||
}
|
||||
.ts-stats {
|
||||
margin: 10px 0;
|
||||
font-size: 0.85rem;
|
||||
color: var(--font-color, #666);
|
||||
opacity: 0.8;
|
||||
}
|
||||
.ts-hits {
|
||||
max-height: 55vh;
|
||||
overflow-y: auto;
|
||||
margin-top: 15px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
.ts-hits::-webkit-scrollbar { width: 6px; }
|
||||
.ts-hits::-webkit-scrollbar-track {
|
||||
background: var(--card-bg, #f1f1f1);
|
||||
border-radius: 10px;
|
||||
}
|
||||
.ts-hits::-webkit-scrollbar-thumb {
|
||||
background: var(--text-highlight-color, #49b1f5);
|
||||
border-radius: 10px;
|
||||
}
|
||||
.ts-empty {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: var(--font-color, #999);
|
||||
}
|
||||
.ts-empty i {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 15px;
|
||||
opacity: 0.3;
|
||||
}
|
||||
.ts-empty code {
|
||||
background: #f5f5f5;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-family: monospace;
|
||||
color: #e91e63;
|
||||
}
|
||||
.ts-empty ol {
|
||||
padding-left: 20px;
|
||||
}
|
||||
.ts-empty li {
|
||||
margin: 8px 0;
|
||||
}
|
||||
.ts-result-item {
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
margin-bottom: 10px;
|
||||
padding: 15px;
|
||||
border: 1px solid transparent;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
background: var(--card-bg, #fff);
|
||||
}
|
||||
.ts-result-item:hover {
|
||||
background: var(--text-bg-hover, rgba(73, 177, 245, 0.05));
|
||||
border-color: var(--text-highlight-color, #49b1f5);
|
||||
transform: translateX(5px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.ts-result-title {
|
||||
font-weight: bold;
|
||||
color: var(--text-highlight-color, #49b1f5);
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 8px;
|
||||
display: block;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.ts-result-content {
|
||||
font-size: 0.9rem;
|
||||
color: var(--font-color, #666);
|
||||
line-height: 1.6;
|
||||
opacity: 0.85;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
.ts-result-item mark {
|
||||
background: #ffeb3b;
|
||||
color: #000;
|
||||
padding: 2px 4px;
|
||||
border-radius: 3px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.ts-pagination {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
}
|
||||
.ais-Pagination-list {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
gap: 5px;
|
||||
}
|
||||
.ais-Pagination-link {
|
||||
display: block;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
color: var(--font-color, #333);
|
||||
text-decoration: none;
|
||||
transition: all 0.2s;
|
||||
background: var(--card-bg, #fff);
|
||||
}
|
||||
.ais-Pagination-link:hover {
|
||||
background: var(--text-highlight-color, #49b1f5);
|
||||
color: #fff;
|
||||
}
|
||||
.ais-Pagination-item--selected .ais-Pagination-link {
|
||||
background: var(--text-highlight-color, #49b1f5);
|
||||
color: #fff;
|
||||
}
|
||||
.ts-footer {
|
||||
text-align: right;
|
||||
margin-top: 15px;
|
||||
border-top: 1px solid var(--border-color, #eee);
|
||||
padding-top: 10px;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.ts-container {
|
||||
margin: 10px;
|
||||
width: calc(100% - 20px);
|
||||
padding: 20px 15px;
|
||||
}
|
||||
.ts-hits { max-height: 50vh; }
|
||||
}
|
||||
[data-theme="dark"] .ts-mask,
|
||||
.dark-mode .ts-mask {
|
||||
background: rgba(0, 0, 0, 0.85);
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
|
||||
document.body.insertAdjacentHTML('beforeend', searchHTML);
|
||||
|
||||
const mask = document.getElementById('typesense-search-mask');
|
||||
const closeBtn = document.getElementById('close-typesense');
|
||||
const container = document.getElementById('typesense-search-container');
|
||||
|
||||
// ============================================================================
|
||||
// 搜索控制
|
||||
// ============================================================================
|
||||
function openSearch() {
|
||||
if (isSearchOpen) return;
|
||||
isSearchOpen = true;
|
||||
mask.style.display = 'block';
|
||||
void mask.offsetWidth;
|
||||
mask.classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
if (!isInitialized) {
|
||||
initTypesense();
|
||||
}
|
||||
focusSearchInput();
|
||||
}
|
||||
|
||||
function closeSearch() {
|
||||
if (!isSearchOpen) return;
|
||||
isSearchOpen = false;
|
||||
mask.classList.remove('active');
|
||||
setTimeout(function() {
|
||||
mask.style.display = 'none';
|
||||
document.body.style.overflow = '';
|
||||
}, CONFIG.ui.animationDuration);
|
||||
}
|
||||
|
||||
function focusSearchInput(retryCount) {
|
||||
retryCount = retryCount || 0;
|
||||
const input = document.querySelector('.ais-SearchBox-input');
|
||||
if (input) {
|
||||
input.focus();
|
||||
input.select();
|
||||
} else if (retryCount < CONFIG.ui.maxRetries) {
|
||||
setTimeout(function() {
|
||||
focusSearchInput(retryCount + 1);
|
||||
}, CONFIG.ui.retryDelay);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 事件监听
|
||||
// ============================================================================
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.closest('.search-typesense-trigger')) {
|
||||
e.preventDefault();
|
||||
openSearch();
|
||||
}
|
||||
});
|
||||
|
||||
closeBtn.addEventListener('click', closeSearch);
|
||||
mask.addEventListener('click', function(e) {
|
||||
if (e.target === mask) closeSearch();
|
||||
});
|
||||
container.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
window.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape' && isSearchOpen) {
|
||||
closeSearch();
|
||||
}
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
|
||||
e.preventDefault();
|
||||
openSearch();
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Typesense 初始化(带重试限制)
|
||||
// ============================================================================
|
||||
function initTypesense() {
|
||||
if (isInitialized || searchInstance) {
|
||||
console.warn('Typesense 搜索已初始化');
|
||||
return;
|
||||
}
|
||||
|
||||
var instantsearchLoaded = typeof instantsearch !== 'undefined';
|
||||
var adapterLoaded = typeof TypesenseInstantSearchAdapter !== 'undefined' ||
|
||||
typeof window.TypesenseInstantSearchAdapter !== 'undefined';
|
||||
|
||||
console.log('📦 依赖库检查 (' + (initRetryCount + 1) + '/' + MAX_INIT_RETRIES + '):');
|
||||
console.log(' instantsearch.js:', instantsearchLoaded ? '✅ 已加载' : '❌ 未加载');
|
||||
console.log(' TypesenseAdapter:', adapterLoaded ? '✅ 已加载' : '❌ 未加载');
|
||||
|
||||
if (!instantsearchLoaded || !adapterLoaded) {
|
||||
initRetryCount++;
|
||||
|
||||
if (initRetryCount >= MAX_INIT_RETRIES) {
|
||||
console.error('');
|
||||
console.error('❌ Typesense 依赖库加载失败!');
|
||||
console.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
console.error('');
|
||||
console.error('🔧 请检查 _config.butterfly.yml 配置:');
|
||||
console.error('');
|
||||
console.error('inject:');
|
||||
console.error(' bottom: # ⚠️ 使用 bottom 而不是 head');
|
||||
console.error(' - <script src="https://cdn.jsdelivr.net/npm/instantsearch.js@4.56.0"></script>');
|
||||
console.error(' - <script src="https://cdn.jsdelivr.net/npm/typesense-instantsearch-adapter@2.7.0/dist/typesense-instantsearch-adapter.min.js"></script>');
|
||||
console.error(' - <script src="/js/typesense-search-fixed.js"></script>');
|
||||
console.error('');
|
||||
console.error('💡 或在控制台手动检查:');
|
||||
console.error(' typeof instantsearch');
|
||||
console.error(' typeof TypesenseInstantSearchAdapter');
|
||||
console.error('');
|
||||
console.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
|
||||
showErrorMessage();
|
||||
return;
|
||||
}
|
||||
|
||||
console.warn('⏳ 100ms 后重试...');
|
||||
setTimeout(initTypesense, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
initRetryCount = 0;
|
||||
console.log('🚀 开始初始化 Typesense...');
|
||||
|
||||
try {
|
||||
const typesenseAdapter = new TypesenseInstantSearchAdapter({
|
||||
server: {
|
||||
apiKey: CONFIG.apiKey,
|
||||
nodes: [{
|
||||
host: CONFIG.server.host,
|
||||
port: CONFIG.server.port,
|
||||
protocol: CONFIG.server.protocol
|
||||
}],
|
||||
cacheSearchResultsForSeconds: 120
|
||||
},
|
||||
additionalSearchParameters: CONFIG.searchParams
|
||||
});
|
||||
|
||||
searchInstance = instantsearch({
|
||||
searchClient: typesenseAdapter.searchClient,
|
||||
indexName: CONFIG.indexName,
|
||||
routing: false
|
||||
});
|
||||
|
||||
searchInstance.addWidgets([
|
||||
instantsearch.widgets.searchBox({
|
||||
container: '#searchbox',
|
||||
placeholder: '输入关键词寻找故事...',
|
||||
autofocus: true,
|
||||
showReset: true,
|
||||
showSubmit: false,
|
||||
showLoadingIndicator: true
|
||||
}),
|
||||
instantsearch.widgets.stats({
|
||||
container: '#stats',
|
||||
templates: {
|
||||
text: function(data) {
|
||||
if (!data.query) return '';
|
||||
return '找到 <strong>' + data.nbHits + '</strong> 条结果 (' + data.processingTimeMS + 'ms)';
|
||||
}
|
||||
}
|
||||
}),
|
||||
instantsearch.widgets.hits({
|
||||
container: '#hits',
|
||||
templates: {
|
||||
empty: function(results) {
|
||||
return '<div class="ts-empty">' +
|
||||
'<div><i class="fas fa-search"></i></div>' +
|
||||
'<div>找不到与 "<strong>' + results.query + '</strong>" 相关的内容</div>' +
|
||||
'<div style="margin-top: 10px;">试试其他关键词吧 (´·ω·`)</div>' +
|
||||
'</div>';
|
||||
},
|
||||
item: function(hit) {
|
||||
// 使用 _highlightResult 获取高亮文本
|
||||
var titleHighlight = hit._highlightResult && hit._highlightResult.title
|
||||
? hit._highlightResult.title.value
|
||||
: (hit.title || '');
|
||||
|
||||
var contentHighlight = hit._highlightResult && hit._highlightResult.content
|
||||
? hit._highlightResult.content.value
|
||||
: (hit.content || '');
|
||||
|
||||
// 截取内容长度
|
||||
if (contentHighlight.length > 200) {
|
||||
contentHighlight = contentHighlight.substring(0, 200) + '...';
|
||||
}
|
||||
|
||||
return '<a href="' + hit.url + '" class="ts-result-item">' +
|
||||
'<div class="ts-result-title">' + titleHighlight + '</div>' +
|
||||
'<div class="ts-result-content">' + contentHighlight + '</div>' +
|
||||
'</a>';
|
||||
}
|
||||
}
|
||||
}),
|
||||
instantsearch.widgets.pagination({
|
||||
container: '#pagination',
|
||||
padding: 2,
|
||||
showFirst: false,
|
||||
showLast: false
|
||||
})
|
||||
]);
|
||||
|
||||
searchInstance.start();
|
||||
isInitialized = true;
|
||||
|
||||
console.log('✅ Typesense 初始化成功!');
|
||||
|
||||
searchInstance.on('render', function() {
|
||||
if (isSearchOpen) {
|
||||
const input = document.querySelector('.ais-SearchBox-input');
|
||||
if (input && document.activeElement !== input) {
|
||||
input.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 初始化失败:', error);
|
||||
showErrorMessage();
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 初始化
|
||||
// ============================================================================
|
||||
function init() {
|
||||
console.log('🔍 Typesense 搜索已准备就绪');
|
||||
console.log('💡 快捷键: Ctrl/Cmd + K');
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 全局接口
|
||||
// ============================================================================
|
||||
window.TypesenseSearch = {
|
||||
open: openSearch,
|
||||
close: closeSearch,
|
||||
isOpen: function() { return isSearchOpen; },
|
||||
getInstance: function() { return searchInstance; }
|
||||
};
|
||||
|
||||
})();
|
||||
@@ -0,0 +1,338 @@
|
||||
(() => {
|
||||
const btfFn = {
|
||||
debounce: (func, wait = 0, immediate = false) => {
|
||||
let timeout
|
||||
return (...args) => {
|
||||
const later = () => {
|
||||
timeout = null
|
||||
if (!immediate) func(...args)
|
||||
}
|
||||
const callNow = immediate && !timeout
|
||||
clearTimeout(timeout)
|
||||
timeout = setTimeout(later, wait)
|
||||
if (callNow) func(...args)
|
||||
}
|
||||
},
|
||||
|
||||
throttle: (func, wait, options = {}) => {
|
||||
let timeout, args
|
||||
let previous = 0
|
||||
|
||||
const later = () => {
|
||||
previous = options.leading === false ? 0 : new Date().getTime()
|
||||
timeout = null
|
||||
func(...args)
|
||||
if (!timeout) args = null
|
||||
}
|
||||
|
||||
return (...params) => {
|
||||
const now = new Date().getTime()
|
||||
if (!previous && options.leading === false) previous = now
|
||||
const remaining = wait - (now - previous)
|
||||
args = params
|
||||
|
||||
if (remaining <= 0 || remaining > wait) {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout)
|
||||
timeout = null
|
||||
}
|
||||
previous = now
|
||||
func(...args)
|
||||
if (!timeout) args = null
|
||||
} else if (!timeout && options.trailing !== false) {
|
||||
timeout = setTimeout(later, remaining)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
overflowPaddingR: {
|
||||
add: () => {
|
||||
const paddingRight = window.innerWidth - document.body.clientWidth
|
||||
|
||||
if (paddingRight > 0) {
|
||||
document.body.style.paddingRight = `${paddingRight}px`
|
||||
document.body.style.overflow = 'hidden'
|
||||
const menuElement = document.querySelector('#page-header.nav-fixed #menus')
|
||||
if (menuElement) {
|
||||
menuElement.style.paddingRight = `${paddingRight}px`
|
||||
}
|
||||
}
|
||||
},
|
||||
remove: () => {
|
||||
document.body.style.paddingRight = ''
|
||||
document.body.style.overflow = ''
|
||||
const menuElement = document.querySelector('#page-header.nav-fixed #menus')
|
||||
if (menuElement) {
|
||||
menuElement.style.paddingRight = ''
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
snackbarShow: (text, showAction = false, duration = 2000) => {
|
||||
const { position, bgLight, bgDark } = GLOBAL_CONFIG.Snackbar
|
||||
const bg = document.documentElement.getAttribute('data-theme') === 'light' ? bgLight : bgDark
|
||||
Snackbar.show({
|
||||
text,
|
||||
backgroundColor: bg,
|
||||
showAction,
|
||||
duration,
|
||||
pos: position,
|
||||
customClass: 'snackbar-css'
|
||||
})
|
||||
},
|
||||
|
||||
diffDate: (inputDate, more = false) => {
|
||||
const dateNow = new Date()
|
||||
const datePost = new Date(inputDate)
|
||||
const diffMs = dateNow - datePost
|
||||
const diffSec = diffMs / 1000
|
||||
const diffMin = diffSec / 60
|
||||
const diffHour = diffMin / 60
|
||||
const diffDay = diffHour / 24
|
||||
const diffMonth = diffDay / 30
|
||||
const { dateSuffix } = GLOBAL_CONFIG
|
||||
|
||||
if (!more) return Math.floor(diffDay)
|
||||
|
||||
if (diffMonth > 12) return datePost.toISOString().slice(0, 10)
|
||||
if (diffMonth >= 1) return `${Math.floor(diffMonth)} ${dateSuffix.month}`
|
||||
if (diffDay >= 1) return `${Math.floor(diffDay)} ${dateSuffix.day}`
|
||||
if (diffHour >= 1) return `${Math.floor(diffHour)} ${dateSuffix.hour}`
|
||||
if (diffMin >= 1) return `${Math.floor(diffMin)} ${dateSuffix.min}`
|
||||
return dateSuffix.just
|
||||
},
|
||||
|
||||
loadComment: (dom, callback) => {
|
||||
if ('IntersectionObserver' in window) {
|
||||
const observerItem = new IntersectionObserver(entries => {
|
||||
if (entries[0].isIntersecting) {
|
||||
callback()
|
||||
observerItem.disconnect()
|
||||
}
|
||||
}, { threshold: [0] })
|
||||
observerItem.observe(dom)
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
|
||||
scrollToDest: (pos, time = 500) => {
|
||||
const currentPos = window.scrollY
|
||||
const isNavFixed = document.getElementById('page-header').classList.contains('fixed')
|
||||
if (currentPos > pos || isNavFixed) pos = pos - 70
|
||||
|
||||
if ('scrollBehavior' in document.documentElement.style) {
|
||||
window.scrollTo({
|
||||
top: pos,
|
||||
behavior: 'smooth'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const startTime = performance.now()
|
||||
const animate = currentTime => {
|
||||
const timeElapsed = currentTime - startTime
|
||||
const progress = Math.min(timeElapsed / time, 1)
|
||||
window.scrollTo(0, currentPos + (pos - currentPos) * progress)
|
||||
if (progress < 1) {
|
||||
requestAnimationFrame(animate)
|
||||
}
|
||||
}
|
||||
requestAnimationFrame(animate)
|
||||
},
|
||||
|
||||
animateIn: (ele, animation) => {
|
||||
ele.style.display = 'block'
|
||||
ele.style.animation = animation
|
||||
},
|
||||
|
||||
animateOut: (ele, animation) => {
|
||||
const handleAnimationEnd = () => {
|
||||
ele.style.display = ''
|
||||
ele.style.animation = ''
|
||||
ele.removeEventListener('animationend', handleAnimationEnd)
|
||||
}
|
||||
ele.addEventListener('animationend', handleAnimationEnd)
|
||||
ele.style.animation = animation
|
||||
},
|
||||
|
||||
wrap: (selector, eleType, options) => {
|
||||
const createEle = document.createElement(eleType)
|
||||
for (const [key, value] of Object.entries(options)) {
|
||||
createEle.setAttribute(key, value)
|
||||
}
|
||||
selector.parentNode.insertBefore(createEle, selector)
|
||||
createEle.appendChild(selector)
|
||||
},
|
||||
|
||||
isHidden: ele => ele.offsetHeight === 0 && ele.offsetWidth === 0,
|
||||
|
||||
getEleTop: ele => ele.getBoundingClientRect().top + window.scrollY,
|
||||
|
||||
loadLightbox: ele => {
|
||||
const service = GLOBAL_CONFIG.lightbox
|
||||
|
||||
if (service === 'medium_zoom') {
|
||||
mediumZoom(ele, { background: 'var(--zoom-bg)' })
|
||||
return
|
||||
}
|
||||
|
||||
if (service === 'fancybox') {
|
||||
ele.forEach(i => {
|
||||
if (i.parentNode.tagName !== 'A') {
|
||||
const dataSrc = i.dataset.lazySrc || i.src
|
||||
const dataCaption = i.title || i.alt || ''
|
||||
btf.wrap(i, 'a', { href: dataSrc, 'data-fancybox': 'gallery', 'data-caption': dataCaption, 'data-thumb': dataSrc })
|
||||
}
|
||||
})
|
||||
|
||||
if (!window.fancyboxRun) {
|
||||
let options = ''
|
||||
if (Fancybox.version < '6') {
|
||||
options = {
|
||||
Hash: false,
|
||||
Thumbs: {
|
||||
showOnStart: false
|
||||
},
|
||||
Images: {
|
||||
Panzoom: {
|
||||
maxScale: 4
|
||||
}
|
||||
},
|
||||
Carousel: {
|
||||
transition: 'slide'
|
||||
},
|
||||
Toolbar: {
|
||||
display: {
|
||||
left: ['infobar'],
|
||||
middle: [
|
||||
'zoomIn',
|
||||
'zoomOut',
|
||||
'toggle1to1',
|
||||
'rotateCCW',
|
||||
'rotateCW',
|
||||
'flipX',
|
||||
'flipY'
|
||||
],
|
||||
right: ['slideshow', 'thumbs', 'close']
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
options = {
|
||||
Hash: false,
|
||||
Carousel: {
|
||||
transition: 'slide',
|
||||
Thumbs: {
|
||||
showOnStart: false
|
||||
},
|
||||
Toolbar: {
|
||||
display: {
|
||||
left: ['counter'],
|
||||
middle: [
|
||||
'zoomIn',
|
||||
'zoomOut',
|
||||
'toggle1to1',
|
||||
'rotateCCW',
|
||||
'rotateCW',
|
||||
'flipX',
|
||||
'flipY',
|
||||
'reset'
|
||||
],
|
||||
right: ['autoplay', 'thumbs', 'close']
|
||||
}
|
||||
},
|
||||
Zoomable: {
|
||||
Panzoom: {
|
||||
maxScale: 4
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Fancybox.bind('[data-fancybox]', options)
|
||||
window.fancyboxRun = true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
setLoading: {
|
||||
add: ele => {
|
||||
const html = `
|
||||
<div class="loading-container">
|
||||
<div class="loading-item">
|
||||
<div></div><div></div><div></div><div></div><div></div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
ele.insertAdjacentHTML('afterend', html)
|
||||
},
|
||||
remove: ele => {
|
||||
ele.nextElementSibling.remove()
|
||||
}
|
||||
},
|
||||
|
||||
updateAnchor: anchor => {
|
||||
if (anchor !== window.location.hash) {
|
||||
if (!anchor) anchor = location.pathname
|
||||
const title = GLOBAL_CONFIG_SITE.title
|
||||
window.history.replaceState({
|
||||
url: location.href,
|
||||
title
|
||||
}, title, anchor)
|
||||
}
|
||||
},
|
||||
|
||||
getScrollPercent: (() => {
|
||||
let docHeight, winHeight, headerHeight, contentMath
|
||||
|
||||
return (currentTop, ele) => {
|
||||
if (!docHeight || ele.clientHeight !== docHeight) {
|
||||
docHeight = ele.clientHeight
|
||||
winHeight = window.innerHeight
|
||||
headerHeight = ele.offsetTop
|
||||
contentMath = Math.max(docHeight - winHeight, document.documentElement.scrollHeight - winHeight)
|
||||
}
|
||||
|
||||
const scrollPercent = (currentTop - headerHeight) / contentMath
|
||||
return Math.max(0, Math.min(100, Math.round(scrollPercent * 100)))
|
||||
}
|
||||
})(),
|
||||
|
||||
addEventListenerPjax: (ele, event, fn, option = false) => {
|
||||
ele.addEventListener(event, fn, option)
|
||||
btf.addGlobalFn('pjaxSendOnce', () => {
|
||||
ele.removeEventListener(event, fn, option)
|
||||
})
|
||||
},
|
||||
|
||||
removeGlobalFnEvent: (key, parent = window) => {
|
||||
const globalFn = parent.globalFn || {}
|
||||
const keyObj = globalFn[key]
|
||||
if (!keyObj) return
|
||||
|
||||
Object.keys(keyObj).forEach(i => keyObj[i]())
|
||||
|
||||
delete globalFn[key]
|
||||
},
|
||||
|
||||
switchComments: (el = document, path) => {
|
||||
const switchBtn = el.querySelector('#switch-btn')
|
||||
if (!switchBtn) return
|
||||
|
||||
let switchDone = false
|
||||
const postComment = el.querySelector('#post-comment')
|
||||
const handleSwitchBtn = () => {
|
||||
postComment.classList.toggle('move')
|
||||
if (!switchDone && typeof loadOtherComment === 'function') {
|
||||
switchDone = true
|
||||
loadOtherComment(el, path)
|
||||
}
|
||||
}
|
||||
btf.addEventListenerPjax(switchBtn, 'click', handleSwitchBtn)
|
||||
}
|
||||
}
|
||||
|
||||
window.btf = { ...window.btf, ...btfFn }
|
||||
})()
|
||||