Site updated: 2024-06-02 18:15:05
This commit is contained in:
parent
3882a20674
commit
4ca5d543ca
|
@ -1,205 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
|
||||
<title>Hello World | Hexo</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub. Quick">
|
||||
<meta property="og:type" content="article">
|
||||
<meta property="og:title" content="Hello World">
|
||||
<meta property="og:url" content="http://example.com/2024/05/21/hello-world/index.html">
|
||||
<meta property="og:site_name" content="Hexo">
|
||||
<meta property="og:description" content="Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub. Quick">
|
||||
<meta property="og:locale" content="en_US">
|
||||
<meta property="article:published_time" content="2024-05-21T14:13:21.778Z">
|
||||
<meta property="article:modified_time" content="2024-05-21T14:13:21.778Z">
|
||||
<meta property="article:author" content="John Doe">
|
||||
<meta name="twitter:card" content="summary">
|
||||
|
||||
<link rel="alternate" href="/atom.xml" title="Hexo" type="application/atom+xml">
|
||||
|
||||
|
||||
<link rel="shortcut icon" href="/favicon.png">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="/fancybox/jquery.fancybox.min.css">
|
||||
|
||||
|
||||
|
||||
<meta name="generator" content="Hexo 7.2.0"></head>
|
||||
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="wrap">
|
||||
<header id="header">
|
||||
<div id="banner"></div>
|
||||
<div id="header-outer" class="outer">
|
||||
<div id="header-title" class="inner">
|
||||
<h1 id="logo-wrap">
|
||||
<a href="/" id="logo">Hexo</a>
|
||||
</h1>
|
||||
|
||||
</div>
|
||||
<div id="header-inner" class="inner">
|
||||
<nav id="main-nav">
|
||||
<a id="main-nav-toggle" class="nav-icon"><span class="fa fa-bars"></span></a>
|
||||
|
||||
<a class="main-nav-link" href="/">Home</a>
|
||||
|
||||
<a class="main-nav-link" href="/archives">Archives</a>
|
||||
|
||||
</nav>
|
||||
<nav id="sub-nav">
|
||||
|
||||
|
||||
<a class="nav-icon" href="/atom.xml" title="RSS Feed"><span class="fa fa-rss"></span></a>
|
||||
|
||||
<a class="nav-icon nav-search-btn" title="Search"><span class="fa fa-search"></span></a>
|
||||
</nav>
|
||||
<div id="search-form-wrap">
|
||||
<form action="//google.com/search" method="get" accept-charset="UTF-8" class="search-form"><input type="search" name="q" class="search-form-input" placeholder="Search"><button type="submit" class="search-form-submit"></button><input type="hidden" name="sitesearch" value="http://example.com"></form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="outer">
|
||||
<section id="main"><article id="post-hello-world" class="h-entry article article-type-post" itemprop="blogPost" itemscope itemtype="https://schema.org/BlogPosting">
|
||||
<div class="article-meta">
|
||||
<a href="/2024/05/21/hello-world/" class="article-date">
|
||||
<time class="dt-published" datetime="2024-05-21T14:13:21.778Z" itemprop="datePublished">2024-05-21</time>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
<div class="article-inner">
|
||||
|
||||
|
||||
<header class="article-header">
|
||||
|
||||
|
||||
<h1 class="p-name article-title" itemprop="headline name">
|
||||
Hello World
|
||||
</h1>
|
||||
|
||||
|
||||
</header>
|
||||
|
||||
<div class="e-content article-entry" itemprop="articleBody">
|
||||
|
||||
<p>Welcome to <a target="_blank" rel="noopener" href="https://hexo.io/">Hexo</a>! This is your very first post. Check <a target="_blank" rel="noopener" href="https://hexo.io/docs/">documentation</a> for more info. If you get any problems when using Hexo, you can find the answer in <a target="_blank" rel="noopener" href="https://hexo.io/docs/troubleshooting.html">troubleshooting</a> or you can ask me on <a target="_blank" rel="noopener" href="https://github.com/hexojs/hexo/issues">GitHub</a>.</p>
|
||||
<h2 id="Quick-Start"><a href="#Quick-Start" class="headerlink" title="Quick Start"></a>Quick Start</h2><h3 id="Create-a-new-post"><a href="#Create-a-new-post" class="headerlink" title="Create a new post"></a>Create a new post</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo new <span class="string">"My New Post"</span></span><br></pre></td></tr></table></figure>
|
||||
|
||||
<p>More info: <a target="_blank" rel="noopener" href="https://hexo.io/docs/writing.html">Writing</a></p>
|
||||
<h3 id="Run-server"><a href="#Run-server" class="headerlink" title="Run server"></a>Run server</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo server</span><br></pre></td></tr></table></figure>
|
||||
|
||||
<p>More info: <a target="_blank" rel="noopener" href="https://hexo.io/docs/server.html">Server</a></p>
|
||||
<h3 id="Generate-static-files"><a href="#Generate-static-files" class="headerlink" title="Generate static files"></a>Generate static files</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo generate</span><br></pre></td></tr></table></figure>
|
||||
|
||||
<p>More info: <a target="_blank" rel="noopener" href="https://hexo.io/docs/generating.html">Generating</a></p>
|
||||
<h3 id="Deploy-to-remote-sites"><a href="#Deploy-to-remote-sites" class="headerlink" title="Deploy to remote sites"></a>Deploy to remote sites</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo deploy</span><br></pre></td></tr></table></figure>
|
||||
|
||||
<p>More info: <a target="_blank" rel="noopener" href="https://hexo.io/docs/one-command-deployment.html">Deployment</a></p>
|
||||
|
||||
|
||||
</div>
|
||||
<footer class="article-footer">
|
||||
<a data-url="http://example.com/2024/05/21/hello-world/" data-id="clwxbtdqr0000bdn93zqx3s3d" data-title="Hello World" class="article-share-link"><span class="fa fa-share">Share</span></a>
|
||||
|
||||
|
||||
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</article>
|
||||
|
||||
|
||||
</section>
|
||||
|
||||
<aside id="sidebar">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="widget-wrap">
|
||||
<h3 class="widget-title">Archives</h3>
|
||||
<div class="widget">
|
||||
<ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/archives/2024/05/">May 2024</a></li></ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="widget-wrap">
|
||||
<h3 class="widget-title">Recent Posts</h3>
|
||||
<div class="widget">
|
||||
<ul>
|
||||
|
||||
<li>
|
||||
<a href="/2024/05/21/hello-world/">Hello World</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</aside>
|
||||
|
||||
</div>
|
||||
<footer id="footer">
|
||||
|
||||
<div class="outer">
|
||||
<div id="footer-info" class="inner">
|
||||
|
||||
© 2024 John Doe<br>
|
||||
Powered by <a href="https://hexo.io/" target="_blank">Hexo</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
<nav id="mobile-nav">
|
||||
|
||||
<a href="/" class="mobile-nav-link">Home</a>
|
||||
|
||||
<a href="/archives" class="mobile-nav-link">Archives</a>
|
||||
|
||||
</nav>
|
||||
|
||||
|
||||
|
||||
<script src="/js/jquery-3.6.4.min.js"></script>
|
||||
|
||||
|
||||
|
||||
|
||||
<script src="/fancybox/jquery.fancybox.min.js"></script>
|
||||
|
||||
|
||||
|
||||
|
||||
<script src="/js/script.js"></script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,185 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
|
||||
<title>Archives: 2024/5 | Hexo</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="Hexo">
|
||||
<meta property="og:url" content="http://example.com/archives/2024/05/index.html">
|
||||
<meta property="og:site_name" content="Hexo">
|
||||
<meta property="og:locale" content="en_US">
|
||||
<meta property="article:author" content="John Doe">
|
||||
<meta name="twitter:card" content="summary">
|
||||
|
||||
<link rel="alternate" href="/atom.xml" title="Hexo" type="application/atom+xml">
|
||||
|
||||
|
||||
<link rel="shortcut icon" href="/favicon.png">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="/fancybox/jquery.fancybox.min.css">
|
||||
|
||||
|
||||
|
||||
<meta name="generator" content="Hexo 7.2.0"></head>
|
||||
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="wrap">
|
||||
<header id="header">
|
||||
<div id="banner"></div>
|
||||
<div id="header-outer" class="outer">
|
||||
<div id="header-title" class="inner">
|
||||
<h1 id="logo-wrap">
|
||||
<a href="/" id="logo">Hexo</a>
|
||||
</h1>
|
||||
|
||||
</div>
|
||||
<div id="header-inner" class="inner">
|
||||
<nav id="main-nav">
|
||||
<a id="main-nav-toggle" class="nav-icon"><span class="fa fa-bars"></span></a>
|
||||
|
||||
<a class="main-nav-link" href="/">Home</a>
|
||||
|
||||
<a class="main-nav-link" href="/archives">Archives</a>
|
||||
|
||||
</nav>
|
||||
<nav id="sub-nav">
|
||||
|
||||
|
||||
<a class="nav-icon" href="/atom.xml" title="RSS Feed"><span class="fa fa-rss"></span></a>
|
||||
|
||||
<a class="nav-icon nav-search-btn" title="Search"><span class="fa fa-search"></span></a>
|
||||
</nav>
|
||||
<div id="search-form-wrap">
|
||||
<form action="//google.com/search" method="get" accept-charset="UTF-8" class="search-form"><input type="search" name="q" class="search-form-input" placeholder="Search"><button type="submit" class="search-form-submit"></button><input type="hidden" name="sitesearch" value="http://example.com"></form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="outer">
|
||||
<section id="main">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section class="archives-wrap">
|
||||
<div class="archive-year-wrap">
|
||||
<a href="/archives/2024" class="archive-year">2024</a>
|
||||
</div>
|
||||
<div class="archives">
|
||||
|
||||
<article class="archive-article archive-type-post">
|
||||
<div class="archive-article-inner">
|
||||
<header class="archive-article-header">
|
||||
<a href="/2024/05/21/hello-world/" class="archive-article-date">
|
||||
<time class="dt-published" datetime="2024-05-21T14:13:21.778Z" itemprop="datePublished">May 21</time>
|
||||
</a>
|
||||
|
||||
|
||||
<h1 itemprop="name">
|
||||
<a class="archive-article-title" href="/2024/05/21/hello-world/">Hello World</a>
|
||||
</h1>
|
||||
|
||||
|
||||
</header>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
|
||||
</div></section>
|
||||
|
||||
|
||||
|
||||
</section>
|
||||
|
||||
<aside id="sidebar">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="widget-wrap">
|
||||
<h3 class="widget-title">Archives</h3>
|
||||
<div class="widget">
|
||||
<ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/archives/2024/05/">May 2024</a></li></ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="widget-wrap">
|
||||
<h3 class="widget-title">Recent Posts</h3>
|
||||
<div class="widget">
|
||||
<ul>
|
||||
|
||||
<li>
|
||||
<a href="/2024/05/21/hello-world/">Hello World</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</aside>
|
||||
|
||||
</div>
|
||||
<footer id="footer">
|
||||
|
||||
<div class="outer">
|
||||
<div id="footer-info" class="inner">
|
||||
|
||||
© 2024 John Doe<br>
|
||||
Powered by <a href="https://hexo.io/" target="_blank">Hexo</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
<nav id="mobile-nav">
|
||||
|
||||
<a href="/" class="mobile-nav-link">Home</a>
|
||||
|
||||
<a href="/archives" class="mobile-nav-link">Archives</a>
|
||||
|
||||
</nav>
|
||||
|
||||
|
||||
|
||||
<script src="/js/jquery-3.6.4.min.js"></script>
|
||||
|
||||
|
||||
|
||||
|
||||
<script src="/fancybox/jquery.fancybox.min.js"></script>
|
||||
|
||||
|
||||
|
||||
|
||||
<script src="/js/script.js"></script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,185 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
|
||||
<title>Archives: 2024 | Hexo</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="Hexo">
|
||||
<meta property="og:url" content="http://example.com/archives/2024/index.html">
|
||||
<meta property="og:site_name" content="Hexo">
|
||||
<meta property="og:locale" content="en_US">
|
||||
<meta property="article:author" content="John Doe">
|
||||
<meta name="twitter:card" content="summary">
|
||||
|
||||
<link rel="alternate" href="/atom.xml" title="Hexo" type="application/atom+xml">
|
||||
|
||||
|
||||
<link rel="shortcut icon" href="/favicon.png">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="/fancybox/jquery.fancybox.min.css">
|
||||
|
||||
|
||||
|
||||
<meta name="generator" content="Hexo 7.2.0"></head>
|
||||
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="wrap">
|
||||
<header id="header">
|
||||
<div id="banner"></div>
|
||||
<div id="header-outer" class="outer">
|
||||
<div id="header-title" class="inner">
|
||||
<h1 id="logo-wrap">
|
||||
<a href="/" id="logo">Hexo</a>
|
||||
</h1>
|
||||
|
||||
</div>
|
||||
<div id="header-inner" class="inner">
|
||||
<nav id="main-nav">
|
||||
<a id="main-nav-toggle" class="nav-icon"><span class="fa fa-bars"></span></a>
|
||||
|
||||
<a class="main-nav-link" href="/">Home</a>
|
||||
|
||||
<a class="main-nav-link" href="/archives">Archives</a>
|
||||
|
||||
</nav>
|
||||
<nav id="sub-nav">
|
||||
|
||||
|
||||
<a class="nav-icon" href="/atom.xml" title="RSS Feed"><span class="fa fa-rss"></span></a>
|
||||
|
||||
<a class="nav-icon nav-search-btn" title="Search"><span class="fa fa-search"></span></a>
|
||||
</nav>
|
||||
<div id="search-form-wrap">
|
||||
<form action="//google.com/search" method="get" accept-charset="UTF-8" class="search-form"><input type="search" name="q" class="search-form-input" placeholder="Search"><button type="submit" class="search-form-submit"></button><input type="hidden" name="sitesearch" value="http://example.com"></form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="outer">
|
||||
<section id="main">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section class="archives-wrap">
|
||||
<div class="archive-year-wrap">
|
||||
<a href="/archives/2024" class="archive-year">2024</a>
|
||||
</div>
|
||||
<div class="archives">
|
||||
|
||||
<article class="archive-article archive-type-post">
|
||||
<div class="archive-article-inner">
|
||||
<header class="archive-article-header">
|
||||
<a href="/2024/05/21/hello-world/" class="archive-article-date">
|
||||
<time class="dt-published" datetime="2024-05-21T14:13:21.778Z" itemprop="datePublished">May 21</time>
|
||||
</a>
|
||||
|
||||
|
||||
<h1 itemprop="name">
|
||||
<a class="archive-article-title" href="/2024/05/21/hello-world/">Hello World</a>
|
||||
</h1>
|
||||
|
||||
|
||||
</header>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
|
||||
</div></section>
|
||||
|
||||
|
||||
|
||||
</section>
|
||||
|
||||
<aside id="sidebar">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="widget-wrap">
|
||||
<h3 class="widget-title">Archives</h3>
|
||||
<div class="widget">
|
||||
<ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/archives/2024/05/">May 2024</a></li></ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="widget-wrap">
|
||||
<h3 class="widget-title">Recent Posts</h3>
|
||||
<div class="widget">
|
||||
<ul>
|
||||
|
||||
<li>
|
||||
<a href="/2024/05/21/hello-world/">Hello World</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</aside>
|
||||
|
||||
</div>
|
||||
<footer id="footer">
|
||||
|
||||
<div class="outer">
|
||||
<div id="footer-info" class="inner">
|
||||
|
||||
© 2024 John Doe<br>
|
||||
Powered by <a href="https://hexo.io/" target="_blank">Hexo</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
<nav id="mobile-nav">
|
||||
|
||||
<a href="/" class="mobile-nav-link">Home</a>
|
||||
|
||||
<a href="/archives" class="mobile-nav-link">Archives</a>
|
||||
|
||||
</nav>
|
||||
|
||||
|
||||
|
||||
<script src="/js/jquery-3.6.4.min.js"></script>
|
||||
|
||||
|
||||
|
||||
|
||||
<script src="/fancybox/jquery.fancybox.min.js"></script>
|
||||
|
||||
|
||||
|
||||
|
||||
<script src="/js/script.js"></script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,185 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
|
||||
<title>Archives | Hexo</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="Hexo">
|
||||
<meta property="og:url" content="http://example.com/archives/index.html">
|
||||
<meta property="og:site_name" content="Hexo">
|
||||
<meta property="og:locale" content="en_US">
|
||||
<meta property="article:author" content="John Doe">
|
||||
<meta name="twitter:card" content="summary">
|
||||
|
||||
<link rel="alternate" href="/atom.xml" title="Hexo" type="application/atom+xml">
|
||||
|
||||
|
||||
<link rel="shortcut icon" href="/favicon.png">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="/fancybox/jquery.fancybox.min.css">
|
||||
|
||||
|
||||
|
||||
<meta name="generator" content="Hexo 7.2.0"></head>
|
||||
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="wrap">
|
||||
<header id="header">
|
||||
<div id="banner"></div>
|
||||
<div id="header-outer" class="outer">
|
||||
<div id="header-title" class="inner">
|
||||
<h1 id="logo-wrap">
|
||||
<a href="/" id="logo">Hexo</a>
|
||||
</h1>
|
||||
|
||||
</div>
|
||||
<div id="header-inner" class="inner">
|
||||
<nav id="main-nav">
|
||||
<a id="main-nav-toggle" class="nav-icon"><span class="fa fa-bars"></span></a>
|
||||
|
||||
<a class="main-nav-link" href="/">Home</a>
|
||||
|
||||
<a class="main-nav-link" href="/archives">Archives</a>
|
||||
|
||||
</nav>
|
||||
<nav id="sub-nav">
|
||||
|
||||
|
||||
<a class="nav-icon" href="/atom.xml" title="RSS Feed"><span class="fa fa-rss"></span></a>
|
||||
|
||||
<a class="nav-icon nav-search-btn" title="Search"><span class="fa fa-search"></span></a>
|
||||
</nav>
|
||||
<div id="search-form-wrap">
|
||||
<form action="//google.com/search" method="get" accept-charset="UTF-8" class="search-form"><input type="search" name="q" class="search-form-input" placeholder="Search"><button type="submit" class="search-form-submit"></button><input type="hidden" name="sitesearch" value="http://example.com"></form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="outer">
|
||||
<section id="main">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section class="archives-wrap">
|
||||
<div class="archive-year-wrap">
|
||||
<a href="/archives/2024" class="archive-year">2024</a>
|
||||
</div>
|
||||
<div class="archives">
|
||||
|
||||
<article class="archive-article archive-type-post">
|
||||
<div class="archive-article-inner">
|
||||
<header class="archive-article-header">
|
||||
<a href="/2024/05/21/hello-world/" class="archive-article-date">
|
||||
<time class="dt-published" datetime="2024-05-21T14:13:21.778Z" itemprop="datePublished">May 21</time>
|
||||
</a>
|
||||
|
||||
|
||||
<h1 itemprop="name">
|
||||
<a class="archive-article-title" href="/2024/05/21/hello-world/">Hello World</a>
|
||||
</h1>
|
||||
|
||||
|
||||
</header>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
|
||||
</div></section>
|
||||
|
||||
|
||||
|
||||
</section>
|
||||
|
||||
<aside id="sidebar">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="widget-wrap">
|
||||
<h3 class="widget-title">Archives</h3>
|
||||
<div class="widget">
|
||||
<ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/archives/2024/05/">May 2024</a></li></ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="widget-wrap">
|
||||
<h3 class="widget-title">Recent Posts</h3>
|
||||
<div class="widget">
|
||||
<ul>
|
||||
|
||||
<li>
|
||||
<a href="/2024/05/21/hello-world/">Hello World</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</aside>
|
||||
|
||||
</div>
|
||||
<footer id="footer">
|
||||
|
||||
<div class="outer">
|
||||
<div id="footer-info" class="inner">
|
||||
|
||||
© 2024 John Doe<br>
|
||||
Powered by <a href="https://hexo.io/" target="_blank">Hexo</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
<nav id="mobile-nav">
|
||||
|
||||
<a href="/" class="mobile-nav-link">Home</a>
|
||||
|
||||
<a href="/archives" class="mobile-nav-link">Archives</a>
|
||||
|
||||
</nav>
|
||||
|
||||
|
||||
|
||||
<script src="/js/jquery-3.6.4.min.js"></script>
|
||||
|
||||
|
||||
|
||||
|
||||
<script src="/fancybox/jquery.fancybox.min.js"></script>
|
||||
|
||||
|
||||
|
||||
|
||||
<script src="/js/script.js"></script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
6136
css/index.css
Normal file
6136
css/index.css
Normal file
File diff suppressed because it is too large
Load Diff
169
css/style.css
169
css/style.css
|
@ -121,7 +121,7 @@ body {
|
|||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
.outer {
|
||||
max-width: 1220px;
|
||||
max-width: 920px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
@ -136,8 +136,8 @@ body {
|
|||
.inner {
|
||||
display: inline;
|
||||
float: left;
|
||||
width: 98.33333333333333%;
|
||||
margin: 0 0.833333333333333%;
|
||||
width: 97.77777777777777%;
|
||||
margin: 0 1.111111111111111%;
|
||||
}
|
||||
.left,
|
||||
.alignleft {
|
||||
|
@ -172,18 +172,9 @@ body {
|
|||
.mobile-nav-on #wrap {
|
||||
left: 280px;
|
||||
}
|
||||
@media screen and (min-width: 768px) {
|
||||
#main {
|
||||
display: inline;
|
||||
float: left;
|
||||
width: 73.33333333333333%;
|
||||
margin: 0 0.833333333333333%;
|
||||
}
|
||||
}
|
||||
.article-date,
|
||||
.article-category-link,
|
||||
.archive-year,
|
||||
.widget-title {
|
||||
.archive-year {
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
|
@ -202,102 +193,75 @@ body {
|
|||
border: 1px solid #ddd;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.article-entry h1,
|
||||
.widget h1 {
|
||||
.article-entry h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
.article-entry h2,
|
||||
.widget h2 {
|
||||
.article-entry h2 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
.article-entry h3,
|
||||
.widget h3 {
|
||||
.article-entry h3 {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
.article-entry h4,
|
||||
.widget h4 {
|
||||
.article-entry h4 {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
.article-entry h5,
|
||||
.widget h5 {
|
||||
.article-entry h5 {
|
||||
font-size: 1em;
|
||||
}
|
||||
.article-entry h6,
|
||||
.widget h6 {
|
||||
.article-entry h6 {
|
||||
font-size: 1em;
|
||||
color: #999;
|
||||
}
|
||||
.article-entry hr,
|
||||
.widget hr {
|
||||
.article-entry hr {
|
||||
border: 1px dashed #ddd;
|
||||
}
|
||||
.article-entry strong,
|
||||
.widget strong {
|
||||
.article-entry strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
.article-entry em,
|
||||
.widget em,
|
||||
.article-entry cite,
|
||||
.widget cite {
|
||||
.article-entry cite {
|
||||
font-style: italic;
|
||||
}
|
||||
.article-entry sup,
|
||||
.widget sup,
|
||||
.article-entry sub,
|
||||
.widget sub {
|
||||
.article-entry sub {
|
||||
font-size: 0.75em;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
.article-entry sup,
|
||||
.widget sup {
|
||||
.article-entry sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
.article-entry sub,
|
||||
.widget sub {
|
||||
.article-entry sub {
|
||||
bottom: -0.2em;
|
||||
}
|
||||
.article-entry small,
|
||||
.widget small {
|
||||
.article-entry small {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
.article-entry acronym,
|
||||
.widget acronym,
|
||||
.article-entry abbr,
|
||||
.widget abbr {
|
||||
.article-entry abbr {
|
||||
border-bottom: 1px dotted;
|
||||
}
|
||||
.article-entry ul,
|
||||
.widget ul,
|
||||
.article-entry ol,
|
||||
.widget ol,
|
||||
.article-entry dl,
|
||||
.widget dl {
|
||||
.article-entry dl {
|
||||
margin: 0 20px;
|
||||
line-height: 1.6em;
|
||||
}
|
||||
.article-entry ul ul,
|
||||
.widget ul ul,
|
||||
.article-entry ol ul,
|
||||
.widget ol ul,
|
||||
.article-entry ul ol,
|
||||
.widget ul ol,
|
||||
.article-entry ol ol,
|
||||
.widget ol ol {
|
||||
.article-entry ol ol {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.article-entry ul,
|
||||
.widget ul {
|
||||
.article-entry ul {
|
||||
list-style: disc;
|
||||
}
|
||||
.article-entry ol,
|
||||
.widget ol {
|
||||
.article-entry ol {
|
||||
list-style: decimal;
|
||||
}
|
||||
.article-entry dt,
|
||||
.widget dt {
|
||||
.article-entry dt {
|
||||
font-weight: bold;
|
||||
}
|
||||
#header {
|
||||
|
@ -341,7 +305,7 @@ body {
|
|||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: url("images/banner.jpg") center #000;
|
||||
background: url("") center #000;
|
||||
background-size: cover;
|
||||
z-index: -1;
|
||||
}
|
||||
|
@ -1259,88 +1223,3 @@ pre .javascript .function {
|
|||
color: #fff;
|
||||
}
|
||||
}
|
||||
@media screen and (min-width: 768px) {
|
||||
#sidebar {
|
||||
display: inline;
|
||||
float: left;
|
||||
width: 23.333333333333332%;
|
||||
margin: 0 0.833333333333333%;
|
||||
}
|
||||
}
|
||||
.widget-wrap {
|
||||
margin: 50px 0;
|
||||
}
|
||||
.widget {
|
||||
color: #777;
|
||||
text-shadow: 0 1px #fff;
|
||||
background: #ddd;
|
||||
-webkit-box-shadow: 0 -1px 4px #ccc inset;
|
||||
box-shadow: 0 -1px 4px #ccc inset;
|
||||
border: 1px solid #ccc;
|
||||
padding: 15px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.widget a {
|
||||
color: #258fb8;
|
||||
text-decoration: none;
|
||||
}
|
||||
.widget a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.widget ul ul,
|
||||
.widget ol ul,
|
||||
.widget dl ul,
|
||||
.widget ul ol,
|
||||
.widget ol ol,
|
||||
.widget dl ol,
|
||||
.widget ul dl,
|
||||
.widget ol dl,
|
||||
.widget dl dl {
|
||||
margin-left: 15px;
|
||||
list-style: disc;
|
||||
}
|
||||
.widget {
|
||||
line-height: 1.6em;
|
||||
word-wrap: break-word;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.widget ul,
|
||||
.widget ol {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
}
|
||||
.widget ul ul,
|
||||
.widget ol ul,
|
||||
.widget ul ol,
|
||||
.widget ol ol {
|
||||
margin: 0 20px;
|
||||
}
|
||||
.widget ul ul,
|
||||
.widget ol ul {
|
||||
list-style: disc;
|
||||
}
|
||||
.widget ul ol,
|
||||
.widget ol ol {
|
||||
list-style: decimal;
|
||||
}
|
||||
.category-list-count,
|
||||
.tag-list-count,
|
||||
.archive-list-count {
|
||||
padding-left: 5px;
|
||||
color: #999;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
.category-list-count:before,
|
||||
.tag-list-count:before,
|
||||
.archive-list-count:before {
|
||||
content: "(";
|
||||
}
|
||||
.category-list-count:after,
|
||||
.tag-list-count:after,
|
||||
.archive-list-count:after {
|
||||
content: ")";
|
||||
}
|
||||
.tagcloud a {
|
||||
margin-right: 5px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
|
0
css/var.css
Normal file
0
css/var.css
Normal file
BIN
img/404.jpg
Normal file
BIN
img/404.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
BIN
img/favicon.png
Normal file
BIN
img/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 323 B |
BIN
img/friend_404.gif
Normal file
BIN
img/friend_404.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 64 KiB |
205
index.html
205
index.html
|
@ -1,205 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
|
||||
<title>Hexo</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="Hexo">
|
||||
<meta property="og:url" content="http://example.com/index.html">
|
||||
<meta property="og:site_name" content="Hexo">
|
||||
<meta property="og:locale" content="en_US">
|
||||
<meta property="article:author" content="John Doe">
|
||||
<meta name="twitter:card" content="summary">
|
||||
|
||||
<link rel="alternate" href="/atom.xml" title="Hexo" type="application/atom+xml">
|
||||
|
||||
|
||||
<link rel="shortcut icon" href="/favicon.png">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="/fancybox/jquery.fancybox.min.css">
|
||||
|
||||
|
||||
|
||||
<meta name="generator" content="Hexo 7.2.0"></head>
|
||||
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="wrap">
|
||||
<header id="header">
|
||||
<div id="banner"></div>
|
||||
<div id="header-outer" class="outer">
|
||||
<div id="header-title" class="inner">
|
||||
<h1 id="logo-wrap">
|
||||
<a href="/" id="logo">Hexo</a>
|
||||
</h1>
|
||||
|
||||
</div>
|
||||
<div id="header-inner" class="inner">
|
||||
<nav id="main-nav">
|
||||
<a id="main-nav-toggle" class="nav-icon"><span class="fa fa-bars"></span></a>
|
||||
|
||||
<a class="main-nav-link" href="/">Home</a>
|
||||
|
||||
<a class="main-nav-link" href="/archives">Archives</a>
|
||||
|
||||
</nav>
|
||||
<nav id="sub-nav">
|
||||
|
||||
|
||||
<a class="nav-icon" href="/atom.xml" title="RSS Feed"><span class="fa fa-rss"></span></a>
|
||||
|
||||
<a class="nav-icon nav-search-btn" title="Search"><span class="fa fa-search"></span></a>
|
||||
</nav>
|
||||
<div id="search-form-wrap">
|
||||
<form action="//google.com/search" method="get" accept-charset="UTF-8" class="search-form"><input type="search" name="q" class="search-form-input" placeholder="Search"><button type="submit" class="search-form-submit"></button><input type="hidden" name="sitesearch" value="http://example.com"></form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="outer">
|
||||
<section id="main">
|
||||
|
||||
<article id="post-hello-world" class="h-entry article article-type-post" itemprop="blogPost" itemscope itemtype="https://schema.org/BlogPosting">
|
||||
<div class="article-meta">
|
||||
<a href="/2024/05/21/hello-world/" class="article-date">
|
||||
<time class="dt-published" datetime="2024-05-21T14:13:21.778Z" itemprop="datePublished">2024-05-21</time>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
<div class="article-inner">
|
||||
|
||||
|
||||
<header class="article-header">
|
||||
|
||||
|
||||
<h1 itemprop="name">
|
||||
<a class="p-name article-title" href="/2024/05/21/hello-world/">Hello World</a>
|
||||
</h1>
|
||||
|
||||
|
||||
</header>
|
||||
|
||||
<div class="e-content article-entry" itemprop="articleBody">
|
||||
|
||||
<p>Welcome to <a target="_blank" rel="noopener" href="https://hexo.io/">Hexo</a>! This is your very first post. Check <a target="_blank" rel="noopener" href="https://hexo.io/docs/">documentation</a> for more info. If you get any problems when using Hexo, you can find the answer in <a target="_blank" rel="noopener" href="https://hexo.io/docs/troubleshooting.html">troubleshooting</a> or you can ask me on <a target="_blank" rel="noopener" href="https://github.com/hexojs/hexo/issues">GitHub</a>.</p>
|
||||
<h2 id="Quick-Start"><a href="#Quick-Start" class="headerlink" title="Quick Start"></a>Quick Start</h2><h3 id="Create-a-new-post"><a href="#Create-a-new-post" class="headerlink" title="Create a new post"></a>Create a new post</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo new <span class="string">"My New Post"</span></span><br></pre></td></tr></table></figure>
|
||||
|
||||
<p>More info: <a target="_blank" rel="noopener" href="https://hexo.io/docs/writing.html">Writing</a></p>
|
||||
<h3 id="Run-server"><a href="#Run-server" class="headerlink" title="Run server"></a>Run server</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo server</span><br></pre></td></tr></table></figure>
|
||||
|
||||
<p>More info: <a target="_blank" rel="noopener" href="https://hexo.io/docs/server.html">Server</a></p>
|
||||
<h3 id="Generate-static-files"><a href="#Generate-static-files" class="headerlink" title="Generate static files"></a>Generate static files</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo generate</span><br></pre></td></tr></table></figure>
|
||||
|
||||
<p>More info: <a target="_blank" rel="noopener" href="https://hexo.io/docs/generating.html">Generating</a></p>
|
||||
<h3 id="Deploy-to-remote-sites"><a href="#Deploy-to-remote-sites" class="headerlink" title="Deploy to remote sites"></a>Deploy to remote sites</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo deploy</span><br></pre></td></tr></table></figure>
|
||||
|
||||
<p>More info: <a target="_blank" rel="noopener" href="https://hexo.io/docs/one-command-deployment.html">Deployment</a></p>
|
||||
|
||||
|
||||
</div>
|
||||
<footer class="article-footer">
|
||||
<a data-url="http://example.com/2024/05/21/hello-world/" data-id="clwxbtdqr0000bdn93zqx3s3d" data-title="Hello World" class="article-share-link"><span class="fa fa-share">Share</span></a>
|
||||
|
||||
|
||||
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
</article>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</section>
|
||||
|
||||
<aside id="sidebar">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="widget-wrap">
|
||||
<h3 class="widget-title">Archives</h3>
|
||||
<div class="widget">
|
||||
<ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/archives/2024/05/">May 2024</a></li></ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="widget-wrap">
|
||||
<h3 class="widget-title">Recent Posts</h3>
|
||||
<div class="widget">
|
||||
<ul>
|
||||
|
||||
<li>
|
||||
<a href="/2024/05/21/hello-world/">Hello World</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</aside>
|
||||
|
||||
</div>
|
||||
<footer id="footer">
|
||||
|
||||
<div class="outer">
|
||||
<div id="footer-info" class="inner">
|
||||
|
||||
© 2024 John Doe<br>
|
||||
Powered by <a href="https://hexo.io/" target="_blank">Hexo</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
<nav id="mobile-nav">
|
||||
|
||||
<a href="/" class="mobile-nav-link">Home</a>
|
||||
|
||||
<a href="/archives" class="mobile-nav-link">Archives</a>
|
||||
|
||||
</nav>
|
||||
|
||||
|
||||
|
||||
<script src="/js/jquery-3.6.4.min.js"></script>
|
||||
|
||||
|
||||
|
||||
|
||||
<script src="/fancybox/jquery.fancybox.min.js"></script>
|
||||
|
||||
|
||||
|
||||
|
||||
<script src="/js/script.js"></script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
878
js/main.js
Normal file
878
js/main.js
Normal file
|
@ -0,0 +1,878 @@
|
|||
document.addEventListener('DOMContentLoaded', function () {
|
||||
let headerContentWidth, $nav
|
||||
let mobileSidebarOpen = false
|
||||
|
||||
const adjustMenu = init => {
|
||||
const getAllWidth = ele => {
|
||||
return 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.sidebarPaddingR()
|
||||
document.body.style.overflow = 'hidden'
|
||||
btf.animateIn(document.getElementById('menu-mask'), 'to_show 0.5s')
|
||||
document.getElementById('sidebar-menus').classList.add('open')
|
||||
mobileSidebarOpen = true
|
||||
},
|
||||
close: () => {
|
||||
const $body = document.body
|
||||
$body.style.overflow = ''
|
||||
$body.style.paddingRight = ''
|
||||
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, plugin } = highLight
|
||||
const isHighlightShrink = GLOBAL_CONFIG_SITE.isHighlightShrink
|
||||
const isShowTool = highlightCopy || highlightLang || isHighlightShrink !== undefined
|
||||
const $figureHighlight = plugin === 'highlight.js' ? document.querySelectorAll('figure.highlight') : document.querySelectorAll('pre[class*="language-"]')
|
||||
|
||||
if (!((isShowTool || highlightHeightLimit) && $figureHighlight.length)) return
|
||||
|
||||
const isPrismjs = plugin === 'prismjs'
|
||||
const highlightShrinkClass = isHighlightShrink === true ? 'closed' : ''
|
||||
const highlightShrinkEle = isHighlightShrink !== undefined ? '<i class="fas fa-angle-down expand"></i>' : ''
|
||||
const highlightCopyEle = highlightCopy ? '<div class="copy-notice"></div><i class="fas fa-paste copy-button"></i>' : ''
|
||||
|
||||
const alertInfo = (ele, text) => {
|
||||
if (GLOBAL_CONFIG.Snackbar !== undefined) {
|
||||
btf.snackbarShow(text)
|
||||
} else {
|
||||
const prevEle = ele.previousElementSibling
|
||||
prevEle.textContent = text
|
||||
prevEle.style.opacity = 1
|
||||
setTimeout(() => { prevEle.style.opacity = 0 }, 800)
|
||||
}
|
||||
}
|
||||
|
||||
const copy = ctx => {
|
||||
if (document.queryCommandSupported && document.queryCommandSupported('copy')) {
|
||||
document.execCommand('copy')
|
||||
alertInfo(ctx, GLOBAL_CONFIG.copy.success)
|
||||
} else {
|
||||
alertInfo(ctx, GLOBAL_CONFIG.copy.noSupport)
|
||||
}
|
||||
}
|
||||
|
||||
// click events
|
||||
const highlightCopyFn = ele => {
|
||||
const $buttonParent = ele.parentNode
|
||||
$buttonParent.classList.add('copy-true')
|
||||
const selection = window.getSelection()
|
||||
const range = document.createRange()
|
||||
const preCodeSelector = isPrismjs ? 'pre code' : 'table .code pre'
|
||||
range.selectNodeContents($buttonParent.querySelectorAll(`${preCodeSelector}`)[0])
|
||||
selection.removeAllRanges()
|
||||
selection.addRange(range)
|
||||
copy(ele.lastChild)
|
||||
selection.removeAllRanges()
|
||||
$buttonParent.classList.remove('copy-true')
|
||||
}
|
||||
|
||||
const highlightShrinkFn = ele => {
|
||||
ele.classList.toggle('closed')
|
||||
}
|
||||
|
||||
const highlightToolsFn = function (e) {
|
||||
const $target = e.target.classList
|
||||
if ($target.contains('expand')) highlightShrinkFn(this)
|
||||
else if ($target.contains('copy-button')) highlightCopyFn(this)
|
||||
}
|
||||
|
||||
const expandCode = function () {
|
||||
this.classList.toggle('expand-done')
|
||||
}
|
||||
|
||||
const createEle = (lang, item, service) => {
|
||||
const fragment = document.createDocumentFragment()
|
||||
|
||||
if (isShowTool) {
|
||||
const hlTools = document.createElement('div')
|
||||
hlTools.className = `highlight-tools ${highlightShrinkClass}`
|
||||
hlTools.innerHTML = highlightShrinkEle + lang + highlightCopyEle
|
||||
btf.addEventListenerPjax(hlTools, 'click', highlightToolsFn)
|
||||
fragment.appendChild(hlTools)
|
||||
}
|
||||
|
||||
if (highlightHeightLimit && item.offsetHeight > 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)
|
||||
}
|
||||
|
||||
if (service === 'hl') {
|
||||
item.insertBefore(fragment, item.firstChild)
|
||||
} else {
|
||||
item.parentNode.insertBefore(fragment, item)
|
||||
}
|
||||
}
|
||||
|
||||
if (isPrismjs) {
|
||||
$figureHighlight.forEach(item => {
|
||||
if (highlightLang) {
|
||||
const langName = item.getAttribute('data-language') || 'Code'
|
||||
const highlightLangEle = `<div class="code-lang">${langName}</div>`
|
||||
btf.wrap(item, 'figure', { class: 'highlight' })
|
||||
createEle(highlightLangEle, item)
|
||||
} else {
|
||||
btf.wrap(item, 'figure', { class: 'highlight' })
|
||||
createEle('', item)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
$figureHighlight.forEach(item => {
|
||||
if (highlightLang) {
|
||||
let langName = item.getAttribute('class').split(' ')[1]
|
||||
if (langName === 'plain' || langName === undefined) langName = 'Code'
|
||||
const highlightLangEle = `<div class="code-lang">${langName}</div>`
|
||||
createEle(highlightLangEle, item, 'hl')
|
||||
} else {
|
||||
createEle('', item, 'hl')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PhotoFigcaption
|
||||
*/
|
||||
const addPhotoFigcaption = () => {
|
||||
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 is-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) => {
|
||||
const response = await fetch(url)
|
||||
return await response.json()
|
||||
}
|
||||
|
||||
const runJustifiedGallery = (item, data, isButton = false, tabs) => {
|
||||
const dataLength = data.length
|
||||
|
||||
const ig = new InfiniteGrid.JustifiedInfiniteGrid(item, {
|
||||
gap: 5,
|
||||
isConstantSize: true,
|
||||
sizeRange: [150, 600],
|
||||
useResizeObserver: true,
|
||||
observeChildren: true,
|
||||
useTransform: true
|
||||
// useRecycle: false
|
||||
})
|
||||
|
||||
if (tabs) {
|
||||
btf.addGlobalFn('igOfTabs', () => { ig.destroy() }, false, tabs)
|
||||
}
|
||||
|
||||
const replaceDq = str => str.replace(/"/g, '"') // replace double quotes to "
|
||||
|
||||
const getItems = (nextGroupKey, count) => {
|
||||
const nextItems = []
|
||||
const startCount = (nextGroupKey - 1) * count
|
||||
|
||||
for (let i = 0; i < count; ++i) {
|
||||
const num = startCount + i
|
||||
if (num >= dataLength) {
|
||||
break
|
||||
}
|
||||
|
||||
const item = data[num]
|
||||
const alt = item.alt ? `alt="${replaceDq(item.alt)}"` : ''
|
||||
const title = item.title ? `title="${replaceDq(item.title)}"` : ''
|
||||
|
||||
nextItems.push(`<div class="item ">
|
||||
<img src="${item.url}" data-grid-maintained-target="true" ${alt + title} />
|
||||
</div>`)
|
||||
}
|
||||
return nextItems
|
||||
}
|
||||
|
||||
const buttonText = GLOBAL_CONFIG.infinitegrid.buttonText
|
||||
const addButton = item => {
|
||||
const button = document.createElement('button')
|
||||
button.textContent = buttonText
|
||||
|
||||
const buttonFn = e => {
|
||||
e.target.removeEventListener('click', buttonFn)
|
||||
e.target.remove()
|
||||
btf.setLoading.add(item)
|
||||
appendItem(ig.getGroups().length + 1, 10)
|
||||
}
|
||||
|
||||
button.addEventListener('click', buttonFn)
|
||||
item.insertAdjacentElement('afterend', button)
|
||||
}
|
||||
|
||||
const appendItem = (nextGroupKey, count) => {
|
||||
ig.append(getItems(nextGroupKey, count), nextGroupKey)
|
||||
}
|
||||
|
||||
const maxGroupKey = Math.ceil(dataLength / 10)
|
||||
|
||||
const completeFn = e => {
|
||||
const { updated, isResize, mounted } = e
|
||||
if (!updated.length || !mounted.length || isResize) {
|
||||
return
|
||||
}
|
||||
|
||||
btf.loadLightbox(item.querySelectorAll('img:not(.medium-zoom-image)'))
|
||||
|
||||
if (ig.getGroups().length === maxGroupKey) {
|
||||
btf.setLoading.remove(item)
|
||||
ig.off('renderComplete', completeFn)
|
||||
return
|
||||
}
|
||||
|
||||
if (isButton) {
|
||||
btf.setLoading.remove(item)
|
||||
addButton(item)
|
||||
}
|
||||
}
|
||||
|
||||
const requestAppendFn = btf.debounce(e => {
|
||||
const nextGroupKey = (+e.groupKey || 0) + 1
|
||||
appendItem(nextGroupKey, 10)
|
||||
|
||||
if (nextGroupKey === maxGroupKey) {
|
||||
ig.off('requestAppend', requestAppendFn)
|
||||
}
|
||||
}, 300)
|
||||
|
||||
btf.setLoading.add(item)
|
||||
ig.on('renderComplete', completeFn)
|
||||
|
||||
if (isButton) {
|
||||
appendItem(1, 10)
|
||||
} else {
|
||||
ig.on('requestAppend', requestAppendFn)
|
||||
ig.renderItems()
|
||||
}
|
||||
|
||||
btf.addGlobalFn('justifiedGallery', () => { ig.destroy() })
|
||||
}
|
||||
|
||||
const addJustifiedGallery = async (ele, tabs = false) => {
|
||||
const init = async () => {
|
||||
for (const item of ele) {
|
||||
if (btf.isHidden(item)) continue
|
||||
if (tabs && item.classList.contains('loaded')) {
|
||||
item.querySelector('.gallery-items').innerHTML = ''
|
||||
const button = item.querySelector(':scope > button')
|
||||
const loadingContainer = item.querySelector(':scope > .loading-container')
|
||||
button && button.remove()
|
||||
loadingContainer && loadingContainer.remove()
|
||||
}
|
||||
|
||||
const isButton = item.getAttribute('data-button') === 'true'
|
||||
const text = item.firstElementChild.textContent
|
||||
item.classList.add('loaded')
|
||||
const content = item.getAttribute('data-type') === 'url' ? await fetchUrl(text) : JSON.parse(text)
|
||||
runJustifiedGallery(item.lastElementChild, content, isButton, tabs)
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof InfiniteGrid === 'function') {
|
||||
init()
|
||||
} else {
|
||||
await getScript(`${GLOBAL_CONFIG.infinitegrid.js}`)
|
||||
init()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
// 當滾動條小于 56 的時候
|
||||
if (document.body.scrollHeight <= innerHeight) {
|
||||
$rightside.classList.add('rightside-show')
|
||||
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)
|
||||
|
||||
if (document.body.scrollHeight <= innerHeight) {
|
||||
$rightside.classList.add('rightside-show')
|
||||
}
|
||||
}, 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 activePosition = item.getBoundingClientRect().top
|
||||
const sidebarScrollTop = $cardToc.scrollTop
|
||||
if (activePosition > (document.documentElement.clientHeight - 100)) {
|
||||
$cardToc.scrollTop = sidebarScrollTop + 150
|
||||
}
|
||||
if (activePosition < 100) {
|
||||
$cardToc.scrollTop = sidebarScrollTop - 150
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// find head position & add active class
|
||||
const $articleList = $article.querySelectorAll('h1,h2,h3,h4,h5,h6')
|
||||
let detectItem = ''
|
||||
const findHeadPosition = top => {
|
||||
if (top === 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
let currentId = ''
|
||||
let currentIndex = ''
|
||||
|
||||
$articleList.forEach((ele, index) => {
|
||||
if (top > btf.getEleTop(ele) - 80) {
|
||||
const id = ele.id
|
||||
currentId = id ? '#' + encodeURI(id) : ''
|
||||
currentIndex = index
|
||||
}
|
||||
})
|
||||
|
||||
if (detectItem === currentIndex) return
|
||||
|
||||
if (isAnchor) btf.updateAnchor(currentId)
|
||||
|
||||
detectItem = currentIndex
|
||||
|
||||
if (isToc) {
|
||||
$cardToc.querySelectorAll('.active').forEach(i => { i.classList.remove('active') })
|
||||
|
||||
if (currentId === '') {
|
||||
return
|
||||
}
|
||||
|
||||
const currentActive = $tocLink[currentIndex]
|
||||
currentActive.classList.add('active')
|
||||
|
||||
setTimeout(() => {
|
||||
autoScrollToc(currentActive)
|
||||
}, 0)
|
||||
|
||||
if (isExpand) return
|
||||
let parent = currentActive.parentNode
|
||||
|
||||
for (; !parent.matches('.toc'); parent = parent.parentNode) {
|
||||
if (parent.matches('li')) parent.classList.add('active')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
$body.classList.add('read-mode')
|
||||
const newEle = document.createElement('button')
|
||||
newEle.type = 'button'
|
||||
newEle.className = 'fas fa-sign-out-alt exit-readmode'
|
||||
$body.appendChild(newEle)
|
||||
|
||||
const clickFn = () => {
|
||||
$body.classList.remove('read-mode')
|
||||
newEle.remove()
|
||||
newEle.removeEventListener('click', clickFn)
|
||||
}
|
||||
|
||||
newEle.addEventListener('click', clickFn)
|
||||
},
|
||||
darkmode: () => { // switch between light and dark mode
|
||||
const willChangeMode = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark'
|
||||
if (willChangeMode === 'dark') {
|
||||
activateDarkMode()
|
||||
GLOBAL_CONFIG.Snackbar !== undefined && btf.snackbarShow(GLOBAL_CONFIG.Snackbar.day_to_night)
|
||||
} else {
|
||||
activateLightMode()
|
||||
GLOBAL_CONFIG.Snackbar !== undefined && btf.snackbarShow(GLOBAL_CONFIG.Snackbar.night_to_day)
|
||||
}
|
||||
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'
|
||||
saveToLocal.set('aside-status', saveStatus, 2)
|
||||
$htmlDom.toggle('hide-aside')
|
||||
},
|
||||
'mobile-toc-button': item => { // Show mobile toc
|
||||
const tocEle = document.getElementById('card-toc')
|
||||
tocEle.style.transition = 'transform 0.3s ease-in-out'
|
||||
tocEle.classList.toggle('open')
|
||||
tocEle.addEventListener('transitionend', () => {
|
||||
tocEle.style.transition = ''
|
||||
}, { once: true })
|
||||
},
|
||||
'chat-btn': () => { // Show chat
|
||||
window.chatBtnFn()
|
||||
},
|
||||
translateLink: () => { // switch between traditional and simplified chinese
|
||||
window.translateFn.translatePage()
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('rightside').addEventListener('click', function (e) {
|
||||
const $target = e.target.closest('[id]')
|
||||
if ($target && rightSideFn[$target.id]) {
|
||||
rightSideFn[$target.id](this)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* menu
|
||||
* 側邊欄sub-menu 展開/收縮
|
||||
*/
|
||||
const clickFnOfSubMenu = () => {
|
||||
const handleClickOfSubMenu = e => {
|
||||
const target = e.target.closest('.site-page.group')
|
||||
if (!target) return
|
||||
target.classList.toggle('hide')
|
||||
}
|
||||
|
||||
document.querySelector('#sidebar-menus .menus_items').addEventListener('click', handleClickOfSubMenu)
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机端目录点击
|
||||
*/
|
||||
const openMobileMenu = () => {
|
||||
const handleClick = () => { sidebarFn.open() }
|
||||
btf.addEventListenerPjax(document.getElementById('toggle-menu'), 'click', handleClick)
|
||||
}
|
||||
|
||||
/**
|
||||
* 複製時加上版權信息
|
||||
*/
|
||||
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
|
||||
const handleClick = function (e) {
|
||||
const $this = this
|
||||
$this.classList.add('open')
|
||||
const $fjGallery = $this.nextElementSibling.querySelectorAll('.gallery-container')
|
||||
$fjGallery.length && addJustifiedGallery($fjGallery)
|
||||
}
|
||||
|
||||
hideButtons.forEach(item => {
|
||||
item.addEventListener('click', handleClick, { once: true })
|
||||
})
|
||||
}
|
||||
|
||||
const tabsFn = () => {
|
||||
const navTabsElement = document.querySelectorAll('#article-container .tabs')
|
||||
if (!navTabsElement.length) return
|
||||
|
||||
const removeAndAddActiveClass = (elements, detect) => {
|
||||
Array.from(elements).forEach(element => {
|
||||
element.classList.remove('active')
|
||||
if (element === detect || element.id === detect) {
|
||||
element.classList.add('active')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const addTabNavEventListener = (item, isJustifiedGallery) => {
|
||||
const navClickHandler = function (e) {
|
||||
const target = e.target.closest('button')
|
||||
if (target.classList.contains('active')) return
|
||||
removeAndAddActiveClass(this.children, target)
|
||||
this.classList.remove('no-default')
|
||||
const tabId = target.getAttribute('data-href')
|
||||
const tabContent = this.nextElementSibling
|
||||
removeAndAddActiveClass(tabContent.children, tabId)
|
||||
if (isJustifiedGallery) {
|
||||
btf.removeGlobalFnEvent('igOfTabs', this)
|
||||
const justifiedGalleryItems = tabContent.querySelectorAll(`:scope > #${tabId} .gallery-container`)
|
||||
justifiedGalleryItems.length && addJustifiedGallery(justifiedGalleryItems, this)
|
||||
}
|
||||
}
|
||||
btf.addEventListenerPjax(item.firstElementChild, 'click', navClickHandler)
|
||||
}
|
||||
|
||||
const addTabToTopEventListener = item => {
|
||||
const btnClickHandler = (e) => {
|
||||
const target = e.target.closest('button')
|
||||
if (!target) return
|
||||
btf.scrollToDest(btf.getEleTop(item), 300)
|
||||
}
|
||||
btf.addEventListenerPjax(item.lastElementChild, 'click', btnClickHandler)
|
||||
}
|
||||
|
||||
navTabsElement.forEach(item => {
|
||||
const isJustifiedGallery = !!item.querySelectorAll('.gallery-container')
|
||||
addTabNavEventListener(item, isJustifiedGallery)
|
||||
addTabToTopEventListener(item)
|
||||
})
|
||||
}
|
||||
|
||||
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 switchComments = () => {
|
||||
const switchBtn = document.getElementById('switch-btn')
|
||||
if (!switchBtn) return
|
||||
let switchDone = false
|
||||
const commentContainer = document.getElementById('post-comment')
|
||||
const handleSwitchBtn = () => {
|
||||
commentContainer.classList.toggle('move')
|
||||
if (!switchDone && typeof loadOtherComment === 'function') {
|
||||
switchDone = true
|
||||
loadOtherComment()
|
||||
}
|
||||
}
|
||||
btf.addEventListenerPjax(switchBtn, 'click', handleSwitchBtn)
|
||||
}
|
||||
|
||||
const addPostOutdateNotice = () => {
|
||||
const { limitDay, messagePrev, messageNext, position } = GLOBAL_CONFIG.noticeOutdate
|
||||
const diffDay = btf.diffDate(GLOBAL_CONFIG_SITE.postUpdate)
|
||||
if (diffDay >= limitDay) {
|
||||
const ele = document.createElement('div')
|
||||
ele.className = 'post-outdate-notice'
|
||||
ele.textContent = `${messagePrev} ${diffDay} ${messageNext}`
|
||||
const $targetEle = document.getElementById('article-container')
|
||||
if (position === 'top') {
|
||||
$targetEle.insertBefore(ele, $targetEle.firstChild)
|
||||
} else {
|
||||
$targetEle.appendChild(ele)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const lazyloadImg = () => {
|
||||
window.lazyLoadInstance = new LazyLoad({
|
||||
elements_selector: 'img',
|
||||
threshold: 0,
|
||||
data_src: 'lazy-src'
|
||||
})
|
||||
}
|
||||
|
||||
const relativeDate = function (selector) {
|
||||
selector.forEach(item => {
|
||||
const timeVal = item.getAttribute('datetime')
|
||||
item.textContent = btf.diffDate(timeVal, true)
|
||||
item.style.display = 'inline'
|
||||
})
|
||||
}
|
||||
|
||||
const unRefreshFn = function () {
|
||||
window.addEventListener('resize', () => {
|
||||
adjustMenu(false)
|
||||
mobileSidebarOpen && btf.isHidden(document.getElementById('toggle-menu')) && sidebarFn.close()
|
||||
})
|
||||
|
||||
document.getElementById('menu-mask').addEventListener('click', e => { sidebarFn.close() })
|
||||
|
||||
clickFnOfSubMenu()
|
||||
GLOBAL_CONFIG.islazyload && lazyloadImg()
|
||||
GLOBAL_CONFIG.copyright !== undefined && addCopyright()
|
||||
|
||||
if (GLOBAL_CONFIG.autoDarkmode) {
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
|
||||
if (saveToLocal.get('theme') !== undefined) return
|
||||
e.matches ? handleThemeChange('dark') : handleThemeChange('light')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
window.refreshFn = function () {
|
||||
initAdjust()
|
||||
|
||||
if (GLOBAL_CONFIG_SITE.isPost) {
|
||||
GLOBAL_CONFIG.noticeOutdate !== undefined && 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()
|
||||
}
|
||||
|
||||
scrollFnToDo()
|
||||
GLOBAL_CONFIG_SITE.isHome && scrollDownInIndex()
|
||||
addHighlightTool()
|
||||
GLOBAL_CONFIG.isPhotoFigcaption && addPhotoFigcaption()
|
||||
scrollFn()
|
||||
|
||||
btf.removeGlobalFnEvent('justifiedGallery')
|
||||
const galleryContainer = document.querySelectorAll('#article-container .gallery-container')
|
||||
galleryContainer.length && addJustifiedGallery(galleryContainer)
|
||||
|
||||
runLightbox()
|
||||
addTableWrap()
|
||||
clickFnOfTagHide()
|
||||
tabsFn()
|
||||
switchComments()
|
||||
openMobileMenu()
|
||||
}
|
||||
|
||||
refreshFn()
|
||||
unRefreshFn()
|
||||
})
|
177
js/search/algolia.js
Normal file
177
js/search/algolia.js
Normal file
|
@ -0,0 +1,177 @@
|
|||
window.addEventListener('load', () => {
|
||||
const $searchMask = document.getElementById('search-mask')
|
||||
const $searchDialog = document.querySelector('#algolia-search .search-dialog')
|
||||
|
||||
const openSearch = () => {
|
||||
const bodyStyle = document.body.style
|
||||
bodyStyle.width = '100%'
|
||||
bodyStyle.overflow = 'hidden'
|
||||
btf.animateIn($searchMask, 'to_show 0.5s')
|
||||
btf.animateIn($searchDialog, 'titleScale 0.5s')
|
||||
setTimeout(() => { document.querySelector('#algolia-search .ais-SearchBox-input').focus() }, 100)
|
||||
|
||||
// shortcut: ESC
|
||||
document.addEventListener('keydown', function f (event) {
|
||||
if (event.code === 'Escape') {
|
||||
closeSearch()
|
||||
document.removeEventListener('keydown', f)
|
||||
}
|
||||
})
|
||||
|
||||
fixSafariHeight()
|
||||
window.addEventListener('resize', fixSafariHeight)
|
||||
}
|
||||
|
||||
const closeSearch = () => {
|
||||
const bodyStyle = document.body.style
|
||||
bodyStyle.width = ''
|
||||
bodyStyle.overflow = ''
|
||||
btf.animateOut($searchDialog, 'search_close .5s')
|
||||
btf.animateOut($searchMask, 'to_hide 0.5s')
|
||||
window.removeEventListener('resize', fixSafariHeight)
|
||||
}
|
||||
|
||||
// fix safari
|
||||
const fixSafariHeight = () => {
|
||||
if (window.innerWidth < 768) {
|
||||
$searchDialog.style.setProperty('--search-height', window.innerHeight + 'px')
|
||||
}
|
||||
}
|
||||
|
||||
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 ''
|
||||
|
||||
const firstOccur = content.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 > content.length) {
|
||||
end = content.length
|
||||
} else {
|
||||
post = '...'
|
||||
}
|
||||
|
||||
const matchContent = pre + content.substring(start, end) + post
|
||||
return matchContent
|
||||
}
|
||||
|
||||
const algolia = GLOBAL_CONFIG.algolia
|
||||
const isAlgoliaValid = algolia.appId && algolia.apiKey && algolia.indexName
|
||||
if (!isAlgoliaValid) {
|
||||
return console.error('Algolia setting is invalid!')
|
||||
}
|
||||
|
||||
const search = instantsearch({
|
||||
indexName: algolia.indexName,
|
||||
/* global algoliasearch */
|
||||
searchClient: algoliasearch(algolia.appId, algolia.apiKey),
|
||||
searchFunction (helper) {
|
||||
helper.state.query && helper.search()
|
||||
}
|
||||
})
|
||||
|
||||
const configure = instantsearch.widgets.configure({
|
||||
hitsPerPage: 5
|
||||
})
|
||||
|
||||
const searchBox = instantsearch.widgets.searchBox({
|
||||
container: '#algolia-search-input',
|
||||
showReset: false,
|
||||
showSubmit: false,
|
||||
placeholder: GLOBAL_CONFIG.algolia.languages.input_placeholder,
|
||||
showLoadingIndicator: true
|
||||
})
|
||||
|
||||
const hits = instantsearch.widgets.hits({
|
||||
container: '#algolia-hits',
|
||||
templates: {
|
||||
item (data) {
|
||||
const link = data.permalink ? data.permalink : (GLOBAL_CONFIG.root + data.path)
|
||||
const result = data._highlightResult
|
||||
const content = result.contentStripTruncate
|
||||
? cutContent(result.contentStripTruncate.value)
|
||||
: result.contentStrip
|
||||
? cutContent(result.contentStrip.value)
|
||||
: result.content
|
||||
? cutContent(result.content.value)
|
||||
: ''
|
||||
return `
|
||||
<a href="${link}" class="algolia-hit-item-link">
|
||||
<span class="algolia-hits-item-title">${result.title.value || 'no-title'}</span>
|
||||
<p class="algolia-hit-item-content">${content}</p>
|
||||
</a>`
|
||||
},
|
||||
empty: function (data) {
|
||||
return (
|
||||
'<div id="algolia-hits-empty">' +
|
||||
GLOBAL_CONFIG.algolia.languages.hits_empty.replace(/\$\{query}/, data.query) +
|
||||
'</div>'
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const stats = instantsearch.widgets.stats({
|
||||
container: '#algolia-info > .algolia-stats',
|
||||
templates: {
|
||||
text: function (data) {
|
||||
const stats = GLOBAL_CONFIG.algolia.languages.hits_stats
|
||||
.replace(/\$\{hits}/, data.nbHits)
|
||||
.replace(/\$\{time}/, data.processingTimeMS)
|
||||
return (
|
||||
`<hr>${stats}`
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const powerBy = instantsearch.widgets.poweredBy({
|
||||
container: '#algolia-info > .algolia-poweredBy'
|
||||
})
|
||||
|
||||
const pagination = instantsearch.widgets.pagination({
|
||||
container: '#algolia-pagination',
|
||||
totalPages: 5,
|
||||
templates: {
|
||||
first: '<i class="fas fa-angle-double-left"></i>',
|
||||
last: '<i class="fas fa-angle-double-right"></i>',
|
||||
previous: '<i class="fas fa-angle-left"></i>',
|
||||
next: '<i class="fas fa-angle-right"></i>'
|
||||
}
|
||||
})
|
||||
|
||||
search.addWidgets([configure, searchBox, hits, stats, powerBy, pagination]) // add the widgets to the instantsearch instance
|
||||
|
||||
search.start()
|
||||
|
||||
searchClickFn()
|
||||
searchFnOnce()
|
||||
|
||||
window.addEventListener('pjax:complete', () => {
|
||||
!btf.isHidden($searchMask) && closeSearch()
|
||||
searchClickFn()
|
||||
})
|
||||
|
||||
window.pjax && search.on('render', () => {
|
||||
window.pjax.refresh(document.getElementById('algolia-hits'))
|
||||
})
|
||||
})
|
364
js/search/local-search.js
Normal file
364
js/search/local-search.js
Normal file
|
@ -0,0 +1,364 @@
|
|||
/**
|
||||
* 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 += `<div class="local-search-hit-item"><a href="${url.href}"><span class="search-result-title">${this.highlightKeyword(title, slicesOfTitle[0])}</span>`
|
||||
} else {
|
||||
resultItem += `<div 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></a>`
|
||||
})
|
||||
|
||||
resultItem += '</div>'
|
||||
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 } = GLOBAL_CONFIG.localSearch
|
||||
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-wrap')
|
||||
const $loadingStatus = document.getElementById('loading-status')
|
||||
const isXml = !path.endsWith('json')
|
||||
|
||||
const inputEventFunction = () => {
|
||||
if (!localSearch.isfetched) return
|
||||
let searchText = input.value.trim().toLowerCase()
|
||||
isXml && (searchText = searchText.replace(/</g, '<').replace(/>/g, '>'))
|
||||
if (searchText !== '') $loadingStatus.innerHTML = '<i class="fas fa-spinner fa-pulse"></i>'
|
||||
const keywords = searchText.split(/[-\s]+/)
|
||||
const container = document.getElementById('local-search-results')
|
||||
let resultItems = []
|
||||
if (searchText.length > 0) {
|
||||
// Perform local searching
|
||||
resultItems = localSearch.getResultItems(keywords)
|
||||
}
|
||||
if (keywords.length === 1 && keywords[0] === '') {
|
||||
container.textContent = ''
|
||||
statsItem.textContent = ''
|
||||
} else if (resultItems.length === 0) {
|
||||
container.textContent = ''
|
||||
const statsDiv = document.createElement('div')
|
||||
statsDiv.className = 'search-result-stats'
|
||||
statsDiv.textContent = languages.hits_empty.replace(/\$\{query}/, searchText)
|
||||
statsItem.innerHTML = statsDiv.outerHTML
|
||||
} else {
|
||||
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
|
||||
})
|
||||
|
||||
const stats = languages.hits_stats.replace(/\$\{hits}/, resultItems.length)
|
||||
|
||||
container.innerHTML = `<div class="search-result-list">${resultItems.map(result => result.item).join('')}</div>`
|
||||
statsItem.innerHTML = `<hr><div class="search-result-stats">${stats}</div>`
|
||||
window.pjax && window.pjax.refresh(container)
|
||||
}
|
||||
|
||||
$loadingStatus.textContent = ''
|
||||
}
|
||||
|
||||
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 = () => {
|
||||
const bodyStyle = document.body.style
|
||||
bodyStyle.width = '100%'
|
||||
bodyStyle.overflow = 'hidden'
|
||||
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 = () => {
|
||||
const bodyStyle = document.body.style
|
||||
bodyStyle.width = ''
|
||||
bodyStyle.overflow = ''
|
||||
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'))
|
||||
}
|
||||
|
||||
window.addEventListener('search:loaded', () => {
|
||||
const $loadDataItem = document.getElementById('loading-database')
|
||||
$loadDataItem.nextElementSibling.style.display = 'block'
|
||||
$loadDataItem.remove()
|
||||
})
|
||||
|
||||
searchClickFn()
|
||||
searchFnOnce()
|
||||
|
||||
// pjax
|
||||
window.addEventListener('pjax:complete', () => {
|
||||
!btf.isHidden($searchMask) && closeSearch()
|
||||
localSearch.highlightSearchWords(document.getElementById('article-container'))
|
||||
searchClickFn()
|
||||
})
|
||||
})
|
122
js/tw_cn.js
Normal file
122
js/tw_cn.js
Normal file
File diff suppressed because one or more lines are too long
296
js/utils.js
Normal file
296
js/utils.js
Normal file
|
@ -0,0 +1,296 @@
|
|||
const btf = {
|
||||
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: function (func, wait, options = {}) {
|
||||
let timeout, context, args
|
||||
let previous = 0
|
||||
|
||||
const later = () => {
|
||||
previous = options.leading === false ? 0 : new Date().getTime()
|
||||
timeout = null
|
||||
func.apply(context, args)
|
||||
if (!timeout) context = args = null
|
||||
}
|
||||
|
||||
const throttled = (...params) => {
|
||||
const now = new Date().getTime()
|
||||
if (!previous && options.leading === false) previous = now
|
||||
const remaining = wait - (now - previous)
|
||||
context = this
|
||||
args = params
|
||||
if (remaining <= 0 || remaining > wait) {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout)
|
||||
timeout = null
|
||||
}
|
||||
previous = now
|
||||
func.apply(context, args)
|
||||
if (!timeout) context = args = null
|
||||
} else if (!timeout && options.trailing !== false) {
|
||||
timeout = setTimeout(later, remaining)
|
||||
}
|
||||
}
|
||||
|
||||
return throttled
|
||||
},
|
||||
|
||||
sidebarPaddingR: () => {
|
||||
const innerWidth = window.innerWidth
|
||||
const clientWidth = document.body.clientWidth
|
||||
const paddingRight = innerWidth - clientWidth
|
||||
if (innerWidth !== clientWidth) {
|
||||
document.body.style.paddingRight = paddingRight + 'px'
|
||||
}
|
||||
},
|
||||
|
||||
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: (d, more = false) => {
|
||||
const dateNow = new Date()
|
||||
const datePost = new Date(d)
|
||||
const dateDiff = dateNow.getTime() - datePost.getTime()
|
||||
const minute = 1000 * 60
|
||||
const hour = minute * 60
|
||||
const day = hour * 24
|
||||
const month = day * 30
|
||||
const { dateSuffix } = GLOBAL_CONFIG
|
||||
|
||||
if (!more) return parseInt(dateDiff / day)
|
||||
|
||||
const monthCount = dateDiff / month
|
||||
const dayCount = dateDiff / day
|
||||
const hourCount = dateDiff / hour
|
||||
const minuteCount = dateDiff / minute
|
||||
|
||||
if (monthCount > 12) return datePost.toISOString().slice(0, 10)
|
||||
if (monthCount >= 1) return `${parseInt(monthCount)} ${dateSuffix.month}`
|
||||
if (dayCount >= 1) return `${parseInt(dayCount)} ${dateSuffix.day}`
|
||||
if (hourCount >= 1) return `${parseInt(hourCount)} ${dateSuffix.hour}`
|
||||
if (minuteCount >= 1) return `${parseInt(minuteCount)} ${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.pageYOffset
|
||||
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
|
||||
}
|
||||
|
||||
let start = null
|
||||
pos = +pos
|
||||
window.requestAnimationFrame(function step (currentTime) {
|
||||
start = !start ? currentTime : start
|
||||
const progress = currentTime - start
|
||||
if (currentPos < pos) {
|
||||
window.scrollTo(0, ((pos - currentPos) * progress / time) + currentPos)
|
||||
} else {
|
||||
window.scrollTo(0, currentPos - ((currentPos - pos) * progress / time))
|
||||
}
|
||||
if (progress < time) {
|
||||
window.requestAnimationFrame(step)
|
||||
} else {
|
||||
window.scrollTo(0, pos)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
animateIn: (ele, text) => {
|
||||
ele.style.display = 'block'
|
||||
ele.style.animation = text
|
||||
},
|
||||
|
||||
animateOut: (ele, text) => {
|
||||
ele.addEventListener('animationend', function f () {
|
||||
ele.style.display = ''
|
||||
ele.style.animation = ''
|
||||
ele.removeEventListener('animationend', f)
|
||||
})
|
||||
ele.style.animation = text
|
||||
},
|
||||
|
||||
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 => {
|
||||
let actualTop = ele.offsetTop
|
||||
let current = ele.offsetParent
|
||||
|
||||
while (current !== null) {
|
||||
actualTop += current.offsetTop
|
||||
current = current.offsetParent
|
||||
}
|
||||
|
||||
return actualTop
|
||||
},
|
||||
|
||||
loadLightbox: ele => {
|
||||
const service = GLOBAL_CONFIG.lightbox
|
||||
|
||||
if (service === 'mediumZoom') {
|
||||
mediumZoom(ele, { background: 'var(--zoom-bg)' })
|
||||
}
|
||||
|
||||
if (service === 'fancybox') {
|
||||
Array.from(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) {
|
||||
Fancybox.bind('[data-fancybox]', {
|
||||
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']
|
||||
}
|
||||
}
|
||||
})
|
||||
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: (currentTop, ele) => {
|
||||
const docHeight = ele.clientHeight
|
||||
const winHeight = document.documentElement.clientHeight
|
||||
const headerHeight = ele.offsetTop
|
||||
const contentMath = (docHeight > winHeight) ? (docHeight - winHeight) : (document.documentElement.scrollHeight - winHeight)
|
||||
const scrollPercent = (currentTop - headerHeight) / (contentMath)
|
||||
const scrollPercentRounded = Math.round(scrollPercent * 100)
|
||||
const percentage = (scrollPercentRounded > 100) ? 100 : (scrollPercentRounded <= 0) ? 0 : scrollPercentRounded
|
||||
return percentage
|
||||
},
|
||||
|
||||
addGlobalFn: (key, fn, name = false, parent = window) => {
|
||||
const globalFn = parent.globalFn || {}
|
||||
const keyObj = globalFn[key] || {}
|
||||
|
||||
if (name && keyObj[name]) return
|
||||
|
||||
name = name || Object.keys(keyObj).length
|
||||
keyObj[name] = fn
|
||||
globalFn[key] = keyObj
|
||||
parent.globalFn = globalFn
|
||||
},
|
||||
|
||||
addEventListenerPjax: (ele, event, fn, option = false) => {
|
||||
ele.addEventListener(event, fn, option)
|
||||
btf.addGlobalFn('pjax', () => {
|
||||
ele.removeEventListener(event, fn, option)
|
||||
})
|
||||
},
|
||||
|
||||
removeGlobalFnEvent: (key, parent = window) => {
|
||||
const { globalFn = {} } = parent
|
||||
const keyObj = globalFn[key] || {}
|
||||
const keyArr = Object.keys(keyObj)
|
||||
if (!keyArr.length) return
|
||||
keyArr.forEach(i => {
|
||||
keyObj[i]()
|
||||
})
|
||||
delete parent.globalFn[key]
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user