Site updated: 2023-09-22 21:57:09
192
archives/2023/03/index.html
Normal file
192
archives/2023/03/page/2/index.html
Normal file
192
archives/2023/04/index.html
Normal file
192
archives/2023/04/page/2/index.html
Normal file
192
archives/2023/05/index.html
Normal file
192
archives/2023/05/page/2/index.html
Normal file
192
archives/2023/06/index.html
Normal file
192
archives/2023/07/index.html
Normal file
192
archives/2023/08/index.html
Normal file
192
archives/2023/09/index.html
Normal file
192
archives/2023/index.html
Normal file
192
archives/2023/page/2/index.html
Normal file
192
archives/2023/page/3/index.html
Normal file
192
archives/2023/page/4/index.html
Normal file
192
archives/2023/page/5/index.html
Normal file
192
archives/2023/page/6/index.html
Normal file
192
archives/2023/page/7/index.html
Normal file
192
archives/index.html
Normal file
192
archives/page/2/index.html
Normal file
192
archives/page/3/index.html
Normal file
192
archives/page/4/index.html
Normal file
192
archives/page/5/index.html
Normal file
192
archives/page/6/index.html
Normal file
192
archives/page/7/index.html
Normal file
283
baidusitemap.xml
Normal file
@ -0,0 +1,283 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/32696.html</loc>
|
||||
<lastmod>2023-09-22</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/24183.html</loc>
|
||||
<lastmod>2023-09-22</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/8957.html</loc>
|
||||
<lastmod>2023-09-21</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/29985.html</loc>
|
||||
<lastmod>2023-09-21</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/25154.html</loc>
|
||||
<lastmod>2023-09-19</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/39654.html</loc>
|
||||
<lastmod>2023-09-18</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/53088.html</loc>
|
||||
<lastmod>2023-09-14</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/64695.html</loc>
|
||||
<lastmod>2023-09-14</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/5727.html</loc>
|
||||
<lastmod>2023-09-12</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/19306.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/63724.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/60685.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/18459.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/28118.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/63587.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/20683.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/48020.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/21883.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/17259.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/24637.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/53306.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/50465.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/24606.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/73.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/19270.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/29367.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/50908.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/62439.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/26768.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/22654.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/56742.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/32679.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/32246.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/855.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/12929.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/38823.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/64205.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/47407.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/13813.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/54835.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/60780.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/30127.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/6932.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/1530.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/1416.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/13579.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/47003.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/51007.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/14438.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/17772.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/63333.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/1727.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/36397.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/45572.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/432.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/28687.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/60684.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/3661.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/35630.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/27166.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/46306.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/22202.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/7353.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/29250.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/31385.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/11844.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/6319.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/46317.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/40445.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://jasonsgong.gitee.io/posts/45726.html</loc>
|
||||
<lastmod>2023-09-11</lastmod>
|
||||
</url>
|
||||
</urlset>
|
194
categories/index.html
Normal file
192
categories/个人/index.html
Normal file
192
categories/前端/index.html
Normal file
192
categories/后端/index.html
Normal file
192
categories/后端/page/2/index.html
Normal file
192
categories/后端/page/3/index.html
Normal file
192
categories/后端/page/4/index.html
Normal file
192
categories/后端/page/5/index.html
Normal file
192
categories/后端/page/6/index.html
Normal file
192
categories/测试/index.html
Normal file
192
categories/运维/index.html
Normal file
192
categories/面试/index.html
Normal file
749
css/hbe.style.css
Normal file
@ -0,0 +1,749 @@
|
||||
.hbe,
|
||||
.hbe:after,
|
||||
.hbe:before {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.hbe-container{
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
.hbe-content {
|
||||
text-align: center;
|
||||
font-size: 150%;
|
||||
padding: 1em 0;
|
||||
}
|
||||
|
||||
.hbe-input {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: inline-block;
|
||||
margin: 1em;
|
||||
width: 80%;
|
||||
min-width: 200px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.hbe-input-field {
|
||||
line-height: normal;
|
||||
font-size: 100%;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
display: block;
|
||||
float: right;
|
||||
padding: 0.8em;
|
||||
width: 60%;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
background: #f0f0f0;
|
||||
color: #aaa;
|
||||
font-weight: 400;
|
||||
font-family: "Avenir Next", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
-webkit-appearance: none; /* for box shadows to show on iOS */
|
||||
}
|
||||
|
||||
.hbe-input-field:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.hbe-input-label {
|
||||
display: inline-block;
|
||||
float: right;
|
||||
padding: 0 1em;
|
||||
width: 40%;
|
||||
color: #696969;
|
||||
font-weight: bold;
|
||||
font-size: 70.25%;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.hbe-input-label-content {
|
||||
position: relative;
|
||||
display: block;
|
||||
padding: 1.6em 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.hbe-graphic {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
fill: none;
|
||||
}
|
||||
|
||||
/* hbe button in post page */
|
||||
.hbe-button {
|
||||
width: 130px;
|
||||
height: 40px;
|
||||
background: linear-gradient(to bottom, #4eb5e5 0%,#389ed5 100%); /* W3C */
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
position: relative;
|
||||
border-bottom: 4px solid #2b8bc6;
|
||||
color: #fbfbfb;
|
||||
font-weight: 600;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
text-shadow: 1px 1px 1px rgba(0,0,0,.4);
|
||||
font-size: 15px;
|
||||
text-align: left;
|
||||
text-indent: 5px;
|
||||
box-shadow: 0px 3px 0px 0px rgba(0,0,0,.2);
|
||||
cursor: pointer;
|
||||
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.hbe-button:active {
|
||||
box-shadow: 0px 2px 0px 0px rgba(0,0,0,.2);
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
.hbe-button:after {
|
||||
content: "";
|
||||
width: 0;
|
||||
height: 0;
|
||||
display: block;
|
||||
border-top: 20px solid #187dbc;
|
||||
border-bottom: 20px solid #187dbc;
|
||||
border-left: 16px solid transparent;
|
||||
border-right: 20px solid #187dbc;
|
||||
position: absolute;
|
||||
opacity: 0.6;
|
||||
right: 0;
|
||||
top: 0;
|
||||
border-radius: 0 5px 5px 0;
|
||||
}
|
||||
/* hbe button in post page */
|
||||
|
||||
/* default theme {{{ */
|
||||
.hbe-input-default {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hbe-input-field-default {
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
padding: 0.5em;
|
||||
margin-bottom: 2em;
|
||||
color: #f9f7f6;
|
||||
z-index: 100;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.hbe-input-label-default {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
text-align: left;
|
||||
padding: 0.5em 0;
|
||||
pointer-events: none;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.hbe-input-label-default::before,
|
||||
.hbe-input-label-default::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.hbe-input-label-default::before {
|
||||
height: 100%;
|
||||
background: #666666;
|
||||
top: 0;
|
||||
-webkit-transform: translate3d(0, -100%, 0);
|
||||
transform: translate3d(0, -100%, 0);
|
||||
-webkit-transition: -webkit-transform 0.2s;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.hbe-input-label-default::after {
|
||||
height: 2px;
|
||||
background: #666666;
|
||||
top: 100%;
|
||||
-webkit-transition: opacity 0.2s;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.hbe-input-label-content-default {
|
||||
padding: 0;
|
||||
-webkit-transform-origin: 0 0;
|
||||
transform-origin: 0 0;
|
||||
-webkit-transition: -webkit-transform 0.2s, color 0.2s;
|
||||
transition: transform 0.2s, color 0.2s;
|
||||
}
|
||||
|
||||
.hbe-input-field-default:focus,
|
||||
.hbe-input--filled .hbe-input-field-default {
|
||||
opacity: 1;
|
||||
-webkit-transition: opacity 0s 0.2s;
|
||||
transition: opacity 0s 0.2s;
|
||||
}
|
||||
|
||||
.hbe-input-label-default::before,
|
||||
.hbe-input-label-default::after,
|
||||
.hbe-input-label-content-default,
|
||||
.hbe-input-field-default:focus,
|
||||
.hbe-input--filled .hbe-input-field-default {
|
||||
-webkit-transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1);
|
||||
transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1);
|
||||
}
|
||||
|
||||
.hbe-input-field-default:focus + .hbe-input-label-default::before,
|
||||
.hbe-input--filled .hbe-input-label-default::before {
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
.hbe-input-field-default:focus + .hbe-input-label-default::after,
|
||||
.hbe-input--filled .hbe-input-label-default::after {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.hbe-input-field-default:focus + .hbe-input-label-default .hbe-input-label-content-default,
|
||||
.hbe-input--filled .hbe-input-label-default .hbe-input-label-content-default {
|
||||
color: #555555;
|
||||
-webkit-transform: translate3d(0, 2.1em, 0) scale3d(0.65, 0.65, 1);
|
||||
transform: translate3d(0, 2.1em, 0) scale3d(0.65, 0.65, 1);
|
||||
}
|
||||
/* default theme }}} */
|
||||
|
||||
/* up theme {{{ */
|
||||
.hbe-input-up {
|
||||
overflow: hidden;
|
||||
padding-top: 2em;
|
||||
}
|
||||
|
||||
.hbe-input-field-up {
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
opacity: 0;
|
||||
padding: 0.35em;
|
||||
z-index: 100;
|
||||
color: #837482;
|
||||
}
|
||||
|
||||
.hbe-input-label-up {
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
text-align: left;
|
||||
color: #8E9191;
|
||||
padding: 0 0.5em;
|
||||
}
|
||||
|
||||
.hbe-input-label-up::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 4em;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
background: #fff;
|
||||
border-top: 4px solid #9B9F9F;
|
||||
-webkit-transform: translate3d(0, -3px, 0);
|
||||
transform: translate3d(0, -3px, 0);
|
||||
-webkit-transition: -webkit-transform 0.4s;
|
||||
transition: transform 0.4s;
|
||||
-webkit-transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1);
|
||||
transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1);
|
||||
}
|
||||
|
||||
.hbe-input-label-content-up {
|
||||
padding: 0.5em 0;
|
||||
-webkit-transform-origin: 0% 100%;
|
||||
transform-origin: 0% 100%;
|
||||
-webkit-transition: -webkit-transform 0.4s, color 0.4s;
|
||||
transition: transform 0.4s, color 0.4s;
|
||||
-webkit-transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1);
|
||||
transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1);
|
||||
}
|
||||
|
||||
.hbe-input-field-up:focus,
|
||||
.input--filled .hbe-input-field-up {
|
||||
cursor: text;
|
||||
opacity: 1;
|
||||
-webkit-transition: opacity 0s 0.4s;
|
||||
transition: opacity 0s 0.4s;
|
||||
}
|
||||
|
||||
.hbe-input-field-up:focus + .hbe-input-label-up::before,
|
||||
.input--filled .hbe-input-label-up::before {
|
||||
-webkit-transition-delay: 0.05s;
|
||||
transition-delay: 0.05s;
|
||||
-webkit-transform: translate3d(0, -3.3em, 0);
|
||||
transform: translate3d(0, -3.3em, 0);
|
||||
}
|
||||
|
||||
.hbe-input-field-up:focus + .hbe-input-label-up .hbe-input-label-content-up,
|
||||
.input--filled .hbe-input-label-content-up {
|
||||
color: #6B6E6E;
|
||||
-webkit-transform: translate3d(0, -3.3em, 0) scale3d(0.81, 0.81, 1);
|
||||
transform: translate3d(0, -3.3em, 0) scale3d(0.81, 0.81, 1);
|
||||
}
|
||||
/* up theme }}} */
|
||||
|
||||
/* wave theme {{{ */
|
||||
.hbe-input-wave {
|
||||
overflow: hidden;
|
||||
padding-top: 1em;
|
||||
}
|
||||
|
||||
.hbe-input-field-wave {
|
||||
padding: 0.5em 0em 0.25em;
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
color: #9da8b2;
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
.hbe-input-label-wave {
|
||||
position: absolute;
|
||||
top: 0.95em;
|
||||
font-size: 0.85em;
|
||||
left: 0;
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
padding: 0em;
|
||||
pointer-events: none;
|
||||
-webkit-transform-origin: 0 0;
|
||||
transform-origin: 0 0;
|
||||
-webkit-transition: -webkit-transform 0.2s 0.15s, color 1s;
|
||||
transition: transform 0.2s 0.15s, color 1s;
|
||||
-webkit-transition-timing-function: ease-out;
|
||||
transition-timing-function: ease-out;
|
||||
}
|
||||
|
||||
.hbe-graphic-wave {
|
||||
stroke: #92989e;
|
||||
pointer-events: none;
|
||||
-webkit-transition: -webkit-transform 0.7s, stroke 0.7s;
|
||||
transition: transform 0.7s, stroke 0.7s;
|
||||
-webkit-transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1);
|
||||
transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1);
|
||||
}
|
||||
|
||||
.hbe-input-field-wave:focus + .hbe-input-label-wave,
|
||||
.input--filled .hbe-input-label-wave {
|
||||
color: #333;
|
||||
-webkit-transform: translate3d(0, -1.25em, 0) scale3d(0.75, 0.75, 1);
|
||||
transform: translate3d(0, -1.25em, 0) scale3d(0.75, 0.75, 1);
|
||||
}
|
||||
|
||||
.hbe-input-field-wave:focus ~ .hbe-graphic-wave,
|
||||
.input--filled .graphic-wave {
|
||||
stroke: #333;
|
||||
-webkit-transform: translate3d(-66.6%, 0, 0);
|
||||
transform: translate3d(-66.6%, 0, 0);
|
||||
}
|
||||
/* wave theme }}} */
|
||||
|
||||
/* flip theme {{{ */
|
||||
.hbe-input-field-flip {
|
||||
width: 100%;
|
||||
background-color: #d0d1d0;
|
||||
border: 2px solid transparent;
|
||||
-webkit-transition: background-color 0.25s, border-color 0.25s;
|
||||
transition: background-color 0.25s, border-color 0.25s;
|
||||
}
|
||||
|
||||
.hbe-input-label-flip {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
padding: 0 1.25em;
|
||||
-webkit-transform: translate3d(0, 3em, 0);
|
||||
transform: translate3d(0, 3em, 0);
|
||||
-webkit-transition: -webkit-transform 0.25s;
|
||||
transition: transform 0.25s ;
|
||||
-webkit-transition-timing-function: ease-in-out;
|
||||
transition-timing-function: ease-in-out;
|
||||
}
|
||||
|
||||
.hbe-input-label-content-flip {
|
||||
color: #8B8C8B;
|
||||
padding: 0.25em 0;
|
||||
-webkit-transition: -webkit-transform 0.25s;
|
||||
transition: transform 0.25s;
|
||||
-webkit-transition-timing-function: ease-in-out;
|
||||
transition-timing-function: ease-in-out;
|
||||
}
|
||||
|
||||
.hbe-input-label-content-flip::after {
|
||||
content: attr(data-content);
|
||||
position: absolute;
|
||||
font-weight: 800;
|
||||
bottom: 100%;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
color: #666666;
|
||||
padding: 0.25em 0;
|
||||
letter-spacing: 1px;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.hbe-input-field-flip:focus + .hbe-input-label-flip,
|
||||
.input--filled .hbe-input-label-flip {
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
.hbe-input-field-flip:focus + .hbe-input-label-flip .hbe-input-label-content-flip,
|
||||
.input--filled .hbe-input-label-content-flip {
|
||||
-webkit-transform: translate3d(0, 100%, 0);
|
||||
transform: translate3d(0, 100%, 0);
|
||||
}
|
||||
|
||||
.hbe-input-field-flip:focus + .hbe-input-field-flip,
|
||||
.input--filled .hbe-input-field-flip {
|
||||
background-color: transparent;
|
||||
border-color: #666666;
|
||||
}
|
||||
/* flip theme }}} */
|
||||
|
||||
/* xray theme {{{ */
|
||||
.hbe-input-xray {
|
||||
overflow: hidden;
|
||||
padding-bottom: 2.5em;
|
||||
}
|
||||
|
||||
.hbe-input-field-xray {
|
||||
padding: 0;
|
||||
margin-top: 1.2em;
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
color: #84AF9B ;
|
||||
font-size: 1.55em;
|
||||
}
|
||||
|
||||
.hbe-input-label-xray {
|
||||
position: absolute;
|
||||
top: 2em;
|
||||
left: 0;
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
padding: 0em;
|
||||
letter-spacing: 1px;
|
||||
color: #84AF9B ;
|
||||
pointer-events: none;
|
||||
-webkit-transform-origin: 0 0;
|
||||
transform-origin: 0 0;
|
||||
-webkit-transition: -webkit-transform 0.2s 0.1s, color 0.3s;
|
||||
transition: transform 0.2s 0.1s, color 0.3s;
|
||||
-webkit-transition-timing-function: ease-out;
|
||||
transition-timing-function: ease-out;
|
||||
}
|
||||
|
||||
.hbe-graphic-xray {
|
||||
stroke: #84AF9B ;
|
||||
pointer-events: none;
|
||||
stroke-width: 2px;
|
||||
top: 1.25em;
|
||||
bottom: 0px;
|
||||
height: 3.275em;
|
||||
-webkit-transition: -webkit-transform 0.7s, stroke 0.7s;
|
||||
transition: transform 0.7s, stroke 0.7s;
|
||||
-webkit-transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1);
|
||||
transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1);
|
||||
}
|
||||
|
||||
.hbe-input-field-xray:focus + .hbe-input-label-xray,
|
||||
.input--filled .hbe-input-label-xray {
|
||||
color: #84AF9B ;
|
||||
-webkit-transform: translate3d(0, 3.5em, 0) scale3d(0.85, 0.85, 1);
|
||||
transform: translate3d(0, 3.5em, 0) scale3d(0.85, 0.85, 1);
|
||||
}
|
||||
|
||||
.hbe-input-field-xray:focus ~ .hbe-graphic-xray,
|
||||
.input--filled .graphic-xray {
|
||||
stroke: #84AF9B ;
|
||||
-webkit-transform: translate3d(-66.6%, 0, 0);
|
||||
transform: translate3d(-66.6%, 0, 0);
|
||||
}
|
||||
/* xray theme }}} */
|
||||
|
||||
/* blink theme {{{ */
|
||||
.hbe-input-blink {
|
||||
padding-top: 1em;
|
||||
}
|
||||
|
||||
.hbe-input-field-blink {
|
||||
width: 100%;
|
||||
padding: 0.8em 0.5em;
|
||||
background: transparent;
|
||||
border: 2px solid;
|
||||
color: #8781bd;
|
||||
-webkit-transition: border-color 0.25s;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
|
||||
.hbe-input-label-blink {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
pointer-events: none;
|
||||
-webkit-transform: translate3d(0, 3em, 0);
|
||||
transform: translate3d(0, 3em, 0);
|
||||
}
|
||||
|
||||
.hbe-input-label-content-blink {
|
||||
padding: 0 1em;
|
||||
font-weight: 400;
|
||||
color: #b5b5b5;
|
||||
}
|
||||
|
||||
.hbe-input-label-content-blink::after {
|
||||
content: attr(data-content);
|
||||
position: absolute;
|
||||
top: -200%;
|
||||
left: 0;
|
||||
color: #8781bd ;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.hbe-input-field-blink:focus,
|
||||
.input--filled .hbe-input-field-blink {
|
||||
border-color: #8781bd ;
|
||||
}
|
||||
|
||||
.hbe-input-field-blink:focus + .hbe-input-label-blink,
|
||||
.input--filled .hbe-input-label-blink {
|
||||
-webkit-animation: anim-blink-1 0.25s forwards;
|
||||
animation: anim-blink-1 0.25s forwards;
|
||||
}
|
||||
|
||||
.hbe-input-field-blink:focus + .hbe-input-label-blink .hbe-input-label-content-blink,
|
||||
.input--filled .hbe-input-label-content-blink {
|
||||
-webkit-animation: anim-blink-2 0.25s forwards ease-in;
|
||||
animation: anim-blink-2 0.25s forwards ease-in;
|
||||
}
|
||||
|
||||
@-webkit-keyframes anim-blink-1 {
|
||||
0%, 70% {
|
||||
-webkit-transform: translate3d(0, 3em, 0);
|
||||
transform: translate3d(0, 3em, 0);
|
||||
}
|
||||
71%, 100% {
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes anim-blink-2 {
|
||||
0% {
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
70%, 71% {
|
||||
-webkit-transform: translate3d(0, 125%, 0);
|
||||
transform: translate3d(0, 125%, 0);
|
||||
opacity: 0;
|
||||
-webkit-animation-timing-function: ease-out;
|
||||
}
|
||||
100% {
|
||||
color: transparent;
|
||||
-webkit-transform: translate3d(0, 200%, 0);
|
||||
transform: translate3d(0, 200%, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes anim-blink-1 {
|
||||
0%, 70% {
|
||||
-webkit-transform: translate3d(0, 3em, 0);
|
||||
transform: translate3d(0, 3em, 0);
|
||||
}
|
||||
71%, 100% {
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes anim-blink-2 {
|
||||
0% {
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
70%, 71% {
|
||||
-webkit-transform: translate3d(0, 125%, 0);
|
||||
transform: translate3d(0, 125%, 0);
|
||||
opacity: 0;
|
||||
-webkit-animation-timing-function: ease-out;
|
||||
}
|
||||
100% {
|
||||
color: transparent;
|
||||
-webkit-transform: translate3d(0, 200%, 0);
|
||||
transform: translate3d(0, 200%, 0);
|
||||
}
|
||||
}
|
||||
/* blink theme }}} */
|
||||
|
||||
/* surge theme {{{ */
|
||||
.hbe-input-surge {
|
||||
overflow: hidden;
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
|
||||
.hbe-input-field-surge {
|
||||
padding: 0.25em 0.5em;
|
||||
margin-top: 1.25em;
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
color: #D0D0D0;
|
||||
font-size: 1.55em;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.hbe-input-label-surge {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
position: absolute;
|
||||
top: 1em;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
padding: 0 0.25em;
|
||||
-webkit-transform: translate3d(1em, 2.75em, 0);
|
||||
transform: translate3d(1em, 2.75em, 0);
|
||||
-webkit-transition: -webkit-transform 0.3s;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.hbe-input-label-content-surge {
|
||||
color: #A4A5A6;
|
||||
padding: 0.4em 0 0.25em;
|
||||
-webkit-transition: -webkit-transform 0.3s;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.hbe-input-label-content-surge::after {
|
||||
content: attr(data-content);
|
||||
position: absolute;
|
||||
font-weight: 800;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
color: #2C3E50;
|
||||
padding: 0.25em 0;
|
||||
letter-spacing: 1px;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.hbe-graphic-surge {
|
||||
fill: #2C3E50;
|
||||
pointer-events: none;
|
||||
top: 1em;
|
||||
bottom: 0px;
|
||||
height: 4.5em;
|
||||
z-index: -1;
|
||||
-webkit-transition: -webkit-transform 0.7s, fill 0.7s;
|
||||
transition: transform 0.7s, fill 0.7s;
|
||||
-webkit-transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1);
|
||||
transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1);
|
||||
}
|
||||
|
||||
.hbe-input-field-surge:focus,
|
||||
.input--filled .hbe-input-field-surge {
|
||||
-webkit-transition: opacity 0s 0.35s;
|
||||
transition: opacity 0s 0.35s;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.hbe-input-field-surge:focus + .hbe-input-label-surge,
|
||||
.input--filled .hbe-input-label-surge {
|
||||
-webkit-transition-delay: 0.15s;
|
||||
transition-delay: 0.15s;
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
.hbe-input-field-surge:focus + .hbe-input-label-surge .hbe-input-label-content-surge,
|
||||
.input--filled .hbe-input-label-content-surge {
|
||||
-webkit-transition-delay: 0.15s;
|
||||
transition-delay: 0.15s;
|
||||
-webkit-transform: translate3d(0, -100%, 0);
|
||||
transform: translate3d(0, -100%, 0);
|
||||
}
|
||||
|
||||
.hbe-input-field-surge:focus ~ .hbe-graphic-surge,
|
||||
.input--filled .graphic-surge {
|
||||
fill: #2C3E50;
|
||||
-webkit-transform: translate3d(-66.6%, 0, 0);
|
||||
transform: translate3d(-66.6%, 0, 0);
|
||||
}
|
||||
/* surge theme }}} */
|
||||
|
||||
/* shrink theme {{{ */
|
||||
.hbe-input-field-shrink {
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
padding: 0.5em 0;
|
||||
margin-bottom: 2em;
|
||||
color: #2C3E50;
|
||||
}
|
||||
|
||||
.hbe-input-label-shrink {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
text-align: left;
|
||||
font-size: 1em;
|
||||
padding: 10px 0 5px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.hbe-input-label-shrink::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 7px;
|
||||
background: #B7C3AC;
|
||||
left: 0;
|
||||
top: 100%;
|
||||
-webkit-transform-origin: 50% 100%;
|
||||
transform-origin: 50% 100%;
|
||||
-webkit-transition: -webkit-transform 0.3s, background-color 0.3s;
|
||||
transition: transform 0.3s, background-color 0.3s;
|
||||
}
|
||||
|
||||
.hbe-input-label-content-shrink {
|
||||
padding: 0;
|
||||
-webkit-transform-origin: 0 0;
|
||||
transform-origin: 0 0;
|
||||
-webkit-transition: -webkit-transform 0.3s, color 0.3s;
|
||||
transition: transform 0.3s, color 0.3s;
|
||||
}
|
||||
|
||||
.hbe-input-field-shrink:focus + .hbe-input-label-shrink::after,
|
||||
.input--filled .hbe-input-label-shrink::after {
|
||||
background: #84AF9B;
|
||||
-webkit-transform: scale3d(1, 0.25, 1);
|
||||
transform: scale3d(1, 0.25, 1);
|
||||
}
|
||||
|
||||
.hbe-input-field-shrink:focus + .hbe-input-label-shrink .hbe-input-label-content-shrink,
|
||||
.input--filled .hbe-input-label-shrink .hbe-input-label-content-shrink {
|
||||
color: #84AF9B;
|
||||
-webkit-transform: translate3d(0, 2em, 0) scale3d(0.655, 0.655, 1);
|
||||
transform: translate3d(0, 2em, 0) scale3d(0.655, 0.655, 1);
|
||||
}
|
||||
/* shrink theme }}} */
|
5924
css/index.css
Normal file
BIN
img/404.jpg
Normal file
After Width: | Height: | Size: 80 KiB |
BIN
img/avatar.jpg
Normal file
After Width: | Height: | Size: 720 KiB |
BIN
img/favicon.png
Normal file
After Width: | Height: | Size: 323 B |
BIN
img/loading.gif
Normal file
After Width: | Height: | Size: 342 KiB |
BIN
img/图标.png
Normal file
After Width: | Height: | Size: 100 KiB |
355
index.html
Normal file
827
js/main.js
Normal file
@ -0,0 +1,827 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
let headerContentWidth, $nav
|
||||
let mobileSidebarOpen = false
|
||||
|
||||
const adjustMenu = init => {
|
||||
const getAllWidth = ele => {
|
||||
let width = 0
|
||||
ele.length && Array.from(ele).forEach(i => { width += i.offsetWidth })
|
||||
return width
|
||||
}
|
||||
|
||||
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')
|
||||
}
|
||||
|
||||
let hideMenuIndex = ''
|
||||
if (window.innerWidth <= 768) hideMenuIndex = true
|
||||
else hideMenuIndex = headerContentWidth > $nav.offsetWidth - 120
|
||||
|
||||
if (hideMenuIndex) {
|
||||
$nav.classList.add('hide-menu')
|
||||
} else {
|
||||
$nav.classList.remove('hide-menu')
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化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 $scrollDownEle = document.getElementById('scroll-down')
|
||||
$scrollDownEle && $scrollDownEle.addEventListener('click', function () {
|
||||
btf.scrollToDest(document.getElementById('content-inner').offsetTop, 300)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 代碼
|
||||
* 只適用於Hexo默認的代碼渲染
|
||||
*/
|
||||
const addHighlightTool = function () {
|
||||
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 === 'highlighjs' ? 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 ${highlightShrinkClass}"></i>` : ''
|
||||
const highlightCopyEle = highlightCopy ? '<div class="copy-notice"></div><i class="fas fa-paste copy-button"></i>' : ''
|
||||
|
||||
const copy = (text, ctx) => {
|
||||
if (document.queryCommandSupported && document.queryCommandSupported('copy')) {
|
||||
document.execCommand('copy')
|
||||
if (GLOBAL_CONFIG.Snackbar !== undefined) {
|
||||
btf.snackbarShow(GLOBAL_CONFIG.copy.success)
|
||||
} else {
|
||||
const prevEle = ctx.previousElementSibling
|
||||
prevEle.textContent = GLOBAL_CONFIG.copy.success
|
||||
prevEle.style.opacity = 1
|
||||
setTimeout(() => { prevEle.style.opacity = 0 }, 700)
|
||||
}
|
||||
} else {
|
||||
if (GLOBAL_CONFIG.Snackbar !== undefined) {
|
||||
btf.snackbarShow(GLOBAL_CONFIG.copy.noSupport)
|
||||
} else {
|
||||
ctx.previousElementSibling.textContent = 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)
|
||||
const text = selection.toString()
|
||||
copy(text, ele.lastChild)
|
||||
selection.removeAllRanges()
|
||||
$buttonParent.classList.remove('copy-true')
|
||||
}
|
||||
|
||||
const highlightShrinkFn = (ele) => {
|
||||
const $nextEle = [...ele.parentNode.children].slice(1)
|
||||
ele.firstChild.classList.toggle('closed')
|
||||
if (btf.isHidden($nextEle[$nextEle.length - 1])) {
|
||||
$nextEle.forEach(e => { e.style.display = 'block' })
|
||||
} else {
|
||||
$nextEle.forEach(e => { e.style.display = 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
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')
|
||||
}
|
||||
|
||||
function 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
|
||||
hlTools.addEventListener('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>'
|
||||
ele.addEventListener('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(function (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
|
||||
*/
|
||||
function addPhotoFigcaption () {
|
||||
document.querySelectorAll('#article-container img').forEach(function (item) {
|
||||
const parentEle = item.parentNode
|
||||
const altValue = item.title || item.alt
|
||||
if (altValue && !parentEle.parentNode.classList.contains('justified-gallery')) {
|
||||
const ele = document.createElement('div')
|
||||
ele.className = 'img-alt is-center'
|
||||
ele.textContent = altValue
|
||||
parentEle.insertBefore(ele, item.nextSibling)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Lightbox
|
||||
*/
|
||||
const runLightbox = () => {
|
||||
btf.loadLightbox(document.querySelectorAll('#article-container img:not(.no-lightbox)'))
|
||||
}
|
||||
|
||||
/**
|
||||
* justified-gallery 圖庫排版
|
||||
*/
|
||||
const runJustifiedGallery = function (ele) {
|
||||
const htmlStr = arr => {
|
||||
let str = ''
|
||||
const replaceDq = str => str.replace(/"/g, '"') // replace double quotes to "
|
||||
arr.forEach(i => {
|
||||
const alt = i.alt ? `alt="${replaceDq(i.alt)}"` : ''
|
||||
const title = i.title ? `title="${replaceDq(i.title)}"` : ''
|
||||
str += `<div class="fj-gallery-item"><img src="${i.url}" ${alt + title}"></div>`
|
||||
})
|
||||
return str
|
||||
}
|
||||
|
||||
const lazyloadFn = (i, arr, limit) => {
|
||||
const loadItem = limit
|
||||
const arrLength = arr.length
|
||||
if (arrLength > loadItem) i.insertAdjacentHTML('beforeend', htmlStr(arr.splice(0, loadItem)))
|
||||
else {
|
||||
i.insertAdjacentHTML('beforeend', htmlStr(arr))
|
||||
i.classList.remove('lazyload')
|
||||
}
|
||||
return arrLength > loadItem ? loadItem : arrLength
|
||||
}
|
||||
|
||||
const fetchUrl = async (url) => {
|
||||
const response = await fetch(url)
|
||||
return await response.json()
|
||||
}
|
||||
|
||||
const runJustifiedGallery = (item, arr) => {
|
||||
if (!item.classList.contains('lazyload')) item.innerHTML = htmlStr(arr)
|
||||
else {
|
||||
const limit = item.getAttribute('data-limit')
|
||||
lazyloadFn(item, arr, limit)
|
||||
const clickBtnFn = () => {
|
||||
const lastItemLength = lazyloadFn(item, arr, limit)
|
||||
fjGallery(item, 'appendImages', item.querySelectorAll(`.fj-gallery-item:nth-last-child(-n+${lastItemLength})`))
|
||||
btf.loadLightbox(item.querySelectorAll('img'))
|
||||
lastItemLength < limit && item.nextElementSibling.removeEventListener('click', clickBtnFn)
|
||||
}
|
||||
item.nextElementSibling.addEventListener('click', clickBtnFn)
|
||||
}
|
||||
btf.initJustifiedGallery(item)
|
||||
btf.loadLightbox(item.querySelectorAll('img'))
|
||||
}
|
||||
|
||||
const addJustifiedGallery = () => {
|
||||
ele.forEach(item => {
|
||||
item.classList.contains('url')
|
||||
? fetchUrl(item.textContent).then(res => { runJustifiedGallery(item, res) })
|
||||
: runJustifiedGallery(item, JSON.parse(item.textContent))
|
||||
})
|
||||
}
|
||||
|
||||
if (window.fjGallery) {
|
||||
addJustifiedGallery()
|
||||
return
|
||||
}
|
||||
|
||||
getCSS(`${GLOBAL_CONFIG.source.justifiedGallery.css}`)
|
||||
getScript(`${GLOBAL_CONFIG.source.justifiedGallery.js}`).then(addJustifiedGallery)
|
||||
}
|
||||
|
||||
/**
|
||||
* rightside scroll percent
|
||||
*/
|
||||
const rightsideScrollPercent = currentTop => {
|
||||
const perNum = btf.getScrollPercent(currentTop, document.body)
|
||||
const $goUp = document.getElementById('go-up')
|
||||
if (perNum < 95) {
|
||||
$goUp.classList.add('show-percent')
|
||||
$goUp.querySelector('.scroll-percent').textContent = perNum
|
||||
} else {
|
||||
$goUp.classList.remove('show-percent')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 滾動處理
|
||||
*/
|
||||
const scrollFn = function () {
|
||||
const $rightside = document.getElementById('rightside')
|
||||
const innerHeight = window.innerHeight + 56
|
||||
let initTop = 0
|
||||
let isChatShow = true
|
||||
const $header = document.getElementById('page-header')
|
||||
const isChatBtn = typeof chatBtn !== 'undefined'
|
||||
const isShowPercent = GLOBAL_CONFIG.percent.rightside
|
||||
|
||||
// 當滾動條小于 56 的時候
|
||||
if (document.body.scrollHeight <= innerHeight) {
|
||||
$rightside.style.cssText = 'opacity: 1; transform: translateX(-58px)'
|
||||
return
|
||||
}
|
||||
|
||||
// find the scroll direction
|
||||
const scrollDirection = currentTop => {
|
||||
const result = currentTop > initTop // true is down & false is up
|
||||
initTop = currentTop
|
||||
return result
|
||||
}
|
||||
|
||||
const scrollTask = btf.throttle(() => {
|
||||
const currentTop = window.scrollY || document.documentElement.scrollTop
|
||||
const isDown = scrollDirection(currentTop)
|
||||
if (currentTop > 56) {
|
||||
if (isDown) {
|
||||
if ($header.classList.contains('nav-visible')) $header.classList.remove('nav-visible')
|
||||
if (isChatBtn && isChatShow === true) {
|
||||
window.chatBtn.hide()
|
||||
isChatShow = false
|
||||
}
|
||||
} else {
|
||||
if (!$header.classList.contains('nav-visible')) $header.classList.add('nav-visible')
|
||||
if (isChatBtn && isChatShow === false) {
|
||||
window.chatBtn.show()
|
||||
isChatShow = true
|
||||
}
|
||||
}
|
||||
$header.classList.add('nav-fixed')
|
||||
if (window.getComputedStyle($rightside).getPropertyValue('opacity') === '0') {
|
||||
$rightside.style.cssText = 'opacity: 0.8; transform: translateX(-58px)'
|
||||
}
|
||||
} else {
|
||||
if (currentTop === 0) {
|
||||
$header.classList.remove('nav-fixed', 'nav-visible')
|
||||
}
|
||||
$rightside.style.cssText = "opacity: ''; transform: ''"
|
||||
}
|
||||
|
||||
isShowPercent && rightsideScrollPercent(currentTop)
|
||||
|
||||
if (document.body.scrollHeight <= innerHeight) {
|
||||
$rightside.style.cssText = 'opacity: 0.8; transform: translateX(-58px)'
|
||||
}
|
||||
}, 200)
|
||||
|
||||
window.scrollCollect = scrollTask
|
||||
|
||||
window.addEventListener('scroll', scrollCollect)
|
||||
}
|
||||
|
||||
/**
|
||||
* toc,anchor
|
||||
*/
|
||||
const scrollFnToDo = function () {
|
||||
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.getElementsByClassName('toc-content')[0]
|
||||
$tocLink = $cardToc.querySelectorAll('.toc-link')
|
||||
$tocPercentage = $cardTocLayout.querySelector('.toc-percentage')
|
||||
isExpand = $cardToc.classList.contains('is-expand')
|
||||
|
||||
window.mobileToc = {
|
||||
open: () => {
|
||||
$cardTocLayout.style.cssText = 'animation: toc-open .3s; opacity: 1; right: 55px'
|
||||
},
|
||||
|
||||
close: () => {
|
||||
$cardTocLayout.style.animation = 'toc-close .2s'
|
||||
setTimeout(() => {
|
||||
$cardTocLayout.style.cssText = "opacity:''; animation: ''; right: ''"
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
|
||||
// toc元素點擊
|
||||
$cardToc.addEventListener('click', e => {
|
||||
e.preventDefault()
|
||||
const target = e.target.classList
|
||||
if (target.contains('toc-content')) return
|
||||
const $target = target.contains('toc-link')
|
||||
? e.target
|
||||
: e.target.parentElement
|
||||
btf.scrollToDest(btf.getEleTop(document.getElementById(decodeURI($target.getAttribute('href')).replace('#', ''))), 300)
|
||||
if (window.innerWidth < 900) {
|
||||
window.mobileToc.close()
|
||||
}
|
||||
})
|
||||
|
||||
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 list = $article.querySelectorAll('h1,h2,h3,h4,h5,h6')
|
||||
let detectItem = ''
|
||||
const findHeadPosition = function (top) {
|
||||
if (top === 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
let currentId = ''
|
||||
let currentIndex = ''
|
||||
|
||||
list.forEach(function (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
|
||||
window.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)
|
||||
|
||||
window.addEventListener('scroll', tocScrollFn)
|
||||
}
|
||||
|
||||
const modeChangeFn = mode => {
|
||||
if (!window.themeChange) {
|
||||
return
|
||||
}
|
||||
|
||||
const turnMode = item => window.themeChange[item](mode)
|
||||
|
||||
Object.keys(window.themeChange).forEach(item => {
|
||||
if (['disqus', 'disqusjs'].includes(item)) {
|
||||
setTimeout(() => turnMode(item), 300)
|
||||
} else {
|
||||
turnMode(item)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Rightside
|
||||
*/
|
||||
const rightSideFn = {
|
||||
switchReadMode: () => { // 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)
|
||||
},
|
||||
switchDarkMode: () => { // Switch Between Light And Dark Mode
|
||||
const willChangeMode = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark'
|
||||
if (willChangeMode === 'dark') {
|
||||
activateDarkMode()
|
||||
saveToLocal.set('theme', 'dark', 2)
|
||||
GLOBAL_CONFIG.Snackbar !== undefined && btf.snackbarShow(GLOBAL_CONFIG.Snackbar.day_to_night)
|
||||
} else {
|
||||
activateLightMode()
|
||||
saveToLocal.set('theme', 'light', 2)
|
||||
GLOBAL_CONFIG.Snackbar !== undefined && btf.snackbarShow(GLOBAL_CONFIG.Snackbar.night_to_day)
|
||||
}
|
||||
modeChangeFn(willChangeMode)
|
||||
},
|
||||
showOrHideBtn: (e) => { // rightside 點擊設置 按鈕 展開
|
||||
const rightsideHideClassList = document.getElementById('rightside-config-hide').classList
|
||||
rightsideHideClassList.toggle('show')
|
||||
if (e.classList.contains('show')) {
|
||||
rightsideHideClassList.add('status')
|
||||
setTimeout(() => {
|
||||
rightsideHideClassList.remove('status')
|
||||
}, 300)
|
||||
}
|
||||
e.classList.toggle('show')
|
||||
},
|
||||
scrollToTop: () => { // Back to top
|
||||
btf.scrollToDest(0, 500)
|
||||
},
|
||||
hideAsideBtn: () => { // 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')
|
||||
},
|
||||
runMobileToc: () => {
|
||||
if (window.getComputedStyle(document.getElementById('card-toc')).getPropertyValue('opacity') === '0') window.mobileToc.open()
|
||||
else window.mobileToc.close()
|
||||
},
|
||||
toggleChatDisplay: () => {
|
||||
window.chatBtnFn()
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('rightside').addEventListener('click', function (e) {
|
||||
const $target = e.target.id ? e.target : e.target.parentNode
|
||||
switch ($target.id) {
|
||||
case 'go-up':
|
||||
rightSideFn.scrollToTop()
|
||||
break
|
||||
case 'rightside_config':
|
||||
rightSideFn.showOrHideBtn($target)
|
||||
break
|
||||
case 'mobile-toc-button':
|
||||
rightSideFn.runMobileToc()
|
||||
break
|
||||
case 'readmode':
|
||||
rightSideFn.switchReadMode()
|
||||
break
|
||||
case 'darkmode':
|
||||
rightSideFn.switchDarkMode()
|
||||
break
|
||||
case 'hide-aside-btn':
|
||||
rightSideFn.hideAsideBtn()
|
||||
break
|
||||
case 'chat-btn':
|
||||
rightSideFn.toggleChatDisplay()
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* menu
|
||||
* 側邊欄sub-menu 展開/收縮
|
||||
*/
|
||||
const clickFnOfSubMenu = () => {
|
||||
document.querySelectorAll('#sidebar-menus .site-page.group').forEach(function (item) {
|
||||
item.addEventListener('click', function () {
|
||||
this.classList.toggle('hide')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 複製時加上版權信息
|
||||
*/
|
||||
const addCopyright = () => {
|
||||
const copyright = GLOBAL_CONFIG.copyright
|
||||
document.body.oncopy = (e) => {
|
||||
e.preventDefault()
|
||||
const copyFont = window.getSelection(0).toString()
|
||||
let textFont = copyFont
|
||||
if (copyFont.length > copyright.limitCount) {
|
||||
textFont = `${copyFont}\n\n\n${copyright.languages.author}\n${copyright.languages.link}${window.location.href}\n${copyright.languages.source}\n${copyright.languages.info}`
|
||||
}
|
||||
if (e.clipboardData) {
|
||||
return e.clipboardData.setData('text', textFont)
|
||||
} else {
|
||||
return window.clipboardData.setData('text', textFont)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 網頁運行時間
|
||||
*/
|
||||
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 :not(.highlight) > table, #article-container > table')
|
||||
if ($table.length) {
|
||||
$table.forEach(item => {
|
||||
btf.wrap(item, 'div', { class: 'table-wrap' })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* tag-hide
|
||||
*/
|
||||
const clickFnOfTagHide = function () {
|
||||
const $hideInline = document.querySelectorAll('#article-container .hide-button')
|
||||
if ($hideInline.length) {
|
||||
$hideInline.forEach(function (item) {
|
||||
item.addEventListener('click', function (e) {
|
||||
const $this = this
|
||||
$this.classList.add('open')
|
||||
const $fjGallery = $this.nextElementSibling.querySelectorAll('.fj-gallery')
|
||||
$fjGallery.length && btf.initJustifiedGallery($fjGallery)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const tabsFn = {
|
||||
clickFnOfTabs: function () {
|
||||
document.querySelectorAll('#article-container .tab > button').forEach(function (item) {
|
||||
item.addEventListener('click', function (e) {
|
||||
const $this = this
|
||||
const $tabItem = $this.parentNode
|
||||
|
||||
if (!$tabItem.classList.contains('active')) {
|
||||
const $tabContent = $tabItem.parentNode.nextElementSibling
|
||||
const $siblings = btf.siblings($tabItem, '.active')[0]
|
||||
$siblings && $siblings.classList.remove('active')
|
||||
$tabItem.classList.add('active')
|
||||
const tabId = $this.getAttribute('data-href').replace('#', '')
|
||||
const childList = [...$tabContent.children]
|
||||
childList.forEach(item => {
|
||||
if (item.id === tabId) item.classList.add('active')
|
||||
else item.classList.remove('active')
|
||||
})
|
||||
const $isTabJustifiedGallery = $tabContent.querySelectorAll(`#${tabId} .fj-gallery`)
|
||||
if ($isTabJustifiedGallery.length > 0) {
|
||||
btf.initJustifiedGallery($isTabJustifiedGallery)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
backToTop: () => {
|
||||
document.querySelectorAll('#article-container .tabs .tab-to-top').forEach(function (item) {
|
||||
item.addEventListener('click', function () {
|
||||
btf.scrollToDest(btf.getEleTop(btf.getParents(this, '.tabs')), 300)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const toggleCardCategory = function () {
|
||||
const $cardCategory = document.querySelectorAll('#aside-cat-list .card-category-list-item.parent i')
|
||||
if ($cardCategory.length) {
|
||||
$cardCategory.forEach(function (item) {
|
||||
item.addEventListener('click', function (e) {
|
||||
e.preventDefault()
|
||||
const $this = this
|
||||
$this.classList.toggle('expand')
|
||||
const $parentEle = $this.parentNode.nextElementSibling
|
||||
if (btf.isHidden($parentEle)) {
|
||||
$parentEle.style.display = 'block'
|
||||
} else {
|
||||
$parentEle.style.display = 'none'
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const switchComments = function () {
|
||||
let switchDone = false
|
||||
const $switchBtn = document.querySelector('#comment-switch > .switch-btn')
|
||||
$switchBtn && $switchBtn.addEventListener('click', function () {
|
||||
this.classList.toggle('move')
|
||||
document.querySelectorAll('#post-comment > .comment-wrap > div').forEach(function (item) {
|
||||
if (btf.isHidden(item)) {
|
||||
item.style.cssText = 'display: block;animation: tabshow .5s'
|
||||
} else {
|
||||
item.style.cssText = "display: none;animation: ''"
|
||||
}
|
||||
})
|
||||
|
||||
if (!switchDone && typeof loadOtherComment === 'function') {
|
||||
switchDone = true
|
||||
loadOtherComment()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const addPostOutdateNotice = function () {
|
||||
const data = GLOBAL_CONFIG.noticeOutdate
|
||||
const diffDay = btf.diffDate(GLOBAL_CONFIG_SITE.postUpdate)
|
||||
if (diffDay >= data.limitDay) {
|
||||
const ele = document.createElement('div')
|
||||
ele.className = 'post-outdate-notice'
|
||||
ele.textContent = data.messagePrev + ' ' + diffDay + ' ' + data.messageNext
|
||||
const $targetEle = document.getElementById('article-container')
|
||||
if (data.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)
|
||||
btf.isHidden(document.getElementById('toggle-menu')) && mobileSidebarOpen && 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 ? modeChangeFn('dark') : modeChangeFn('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()
|
||||
|
||||
const $jgEle = document.querySelectorAll('#article-container .fj-gallery')
|
||||
$jgEle.length && runJustifiedGallery($jgEle)
|
||||
|
||||
runLightbox()
|
||||
addTableWrap()
|
||||
clickFnOfTagHide()
|
||||
tabsFn.clickFnOfTabs()
|
||||
tabsFn.backToTop()
|
||||
switchComments()
|
||||
document.getElementById('toggle-menu').addEventListener('click', () => { sidebarFn.open() })
|
||||
}
|
||||
|
||||
refreshFn()
|
||||
unRefreshFn()
|
||||
})
|
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 = () => {
|
||||
document.querySelector('#search-button > .search').addEventListener('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'))
|
||||
})
|
||||
})
|
360
js/search/local-search.js
Normal file
@ -0,0 +1,360 @@
|
||||
/**
|
||||
* 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 inputEventFunction = () => {
|
||||
if (!localSearch.isfetched) return
|
||||
const searchText = input.value.trim().toLowerCase()
|
||||
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.classList.add('no-result')
|
||||
container.textContent = ''
|
||||
} else if (resultItems.length === 0) {
|
||||
container.textContent = ''
|
||||
statsItem.innerHTML = `<div class="search-result-stats">${languages.hits_empty.replace(/\$\{query}/, searchText)}</div>`
|
||||
} 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.classList.remove('no-result')
|
||||
container.innerHTML = `<div class="search-result-list">${resultItems.map(result => result.item).join('')}</div>`
|
||||
statsItem.innerHTML = `<br><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 = () => {
|
||||
document.querySelector('#search-button > .search').addEventListener('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()
|
||||
})
|
||||
})
|
115
js/tw_cn.js
Normal file
307
js/utils.js
Normal file
@ -0,0 +1,307 @@
|
||||
const btf = {
|
||||
debounce: function (func, wait, immediate) {
|
||||
let timeout
|
||||
return function () {
|
||||
const context = this
|
||||
const args = arguments
|
||||
const later = function () {
|
||||
timeout = null
|
||||
if (!immediate) func.apply(context, args)
|
||||
}
|
||||
const callNow = immediate && !timeout
|
||||
clearTimeout(timeout)
|
||||
timeout = setTimeout(later, wait)
|
||||
if (callNow) func.apply(context, args)
|
||||
}
|
||||
},
|
||||
|
||||
throttle: function (func, wait, options) {
|
||||
let timeout, context, args
|
||||
let previous = 0
|
||||
if (!options) options = {}
|
||||
|
||||
const later = function () {
|
||||
previous = options.leading === false ? 0 : new Date().getTime()
|
||||
timeout = null
|
||||
func.apply(context, args)
|
||||
if (!timeout) context = args = null
|
||||
}
|
||||
|
||||
const throttled = function () {
|
||||
const now = new Date().getTime()
|
||||
if (!previous && options.leading === false) previous = now
|
||||
const remaining = wait - (now - previous)
|
||||
context = this
|
||||
args = arguments
|
||||
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
|
||||
},
|
||||
|
||||
getParents: (elem, selector) => {
|
||||
for (; elem && elem !== document; elem = elem.parentNode) {
|
||||
if (elem.matches(selector)) return elem
|
||||
}
|
||||
return null
|
||||
},
|
||||
|
||||
siblings: (ele, selector) => {
|
||||
return [...ele.parentNode.children].filter((child) => {
|
||||
if (selector) {
|
||||
return child !== ele && child.matches(selector)
|
||||
}
|
||||
return child !== ele
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {*} selector
|
||||
* @param {*} eleType the type of create element
|
||||
* @param {*} options object key: value
|
||||
*/
|
||||
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)
|
||||
},
|
||||
|
||||
unwrap: el => {
|
||||
const parent = el.parentNode
|
||||
if (parent && parent !== document.body) {
|
||||
parent.replaceChild(el, parent)
|
||||
}
|
||||
},
|
||||
|
||||
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') {
|
||||
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
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
initJustifiedGallery: function (selector) {
|
||||
const runJustifiedGallery = i => {
|
||||
if (!btf.isHidden(i)) {
|
||||
fjGallery(i, {
|
||||
itemSelector: '.fj-gallery-item',
|
||||
rowHeight: i.getAttribute('data-rowHeight'),
|
||||
gutter: 4,
|
||||
onJustify: function () {
|
||||
this.$container.style.opacity = '1'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.from(selector).length === 0) runJustifiedGallery(selector)
|
||||
else selector.forEach(i => { runJustifiedGallery(i) })
|
||||
},
|
||||
|
||||
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
|
||||
},
|
||||
|
||||
addModeChange: (name, fn) => {
|
||||
if (window.themeChange && window.themeChange[name]) return
|
||||
window.themeChange = {
|
||||
...window.themeChange,
|
||||
[name]: fn
|
||||
}
|
||||
}
|
||||
}
|
297
lib/hbe.js
Normal file
@ -0,0 +1,297 @@
|
||||
(() => {
|
||||
'use strict';
|
||||
|
||||
const cryptoObj = window.crypto || window.msCrypto;
|
||||
const storage = window.localStorage;
|
||||
|
||||
const storageName = 'hexo-blog-encrypt:#' + window.location.pathname;
|
||||
const keySalt = textToArray('hexo-blog-encrypt的作者们都是大帅比!');
|
||||
const ivSalt = textToArray('hexo-blog-encrypt是地表最强Hexo加密插件!');
|
||||
|
||||
// As we can't detect the wrong password with AES-CBC,
|
||||
// so adding an empty div and check it when decrption.
|
||||
const knownPrefix = "<hbe-prefix></hbe-prefix>";
|
||||
|
||||
const mainElement = document.getElementById('hexo-blog-encrypt');
|
||||
const wrongPassMessage = mainElement.dataset['wpm'];
|
||||
const wrongHashMessage = mainElement.dataset['whm'];
|
||||
const dataElement = mainElement.getElementsByTagName('script')['hbeData'];
|
||||
const encryptedData = dataElement.innerText;
|
||||
const HmacDigist = dataElement.dataset['hmacdigest'];
|
||||
|
||||
function hexToArray(s) {
|
||||
return new Uint8Array(s.match(/[\da-f]{2}/gi).map((h => {
|
||||
return parseInt(h, 16);
|
||||
})));
|
||||
}
|
||||
|
||||
function textToArray(s) {
|
||||
var i = s.length;
|
||||
var n = 0;
|
||||
var ba = new Array()
|
||||
|
||||
for (var j = 0; j < i;) {
|
||||
var c = s.codePointAt(j);
|
||||
if (c < 128) {
|
||||
ba[n++] = c;
|
||||
j++;
|
||||
} else if ((c > 127) && (c < 2048)) {
|
||||
ba[n++] = (c >> 6) | 192;
|
||||
ba[n++] = (c & 63) | 128;
|
||||
j++;
|
||||
} else if ((c > 2047) && (c < 65536)) {
|
||||
ba[n++] = (c >> 12) | 224;
|
||||
ba[n++] = ((c >> 6) & 63) | 128;
|
||||
ba[n++] = (c & 63) | 128;
|
||||
j++;
|
||||
} else {
|
||||
ba[n++] = (c >> 18) | 240;
|
||||
ba[n++] = ((c >> 12) & 63) | 128;
|
||||
ba[n++] = ((c >> 6) & 63) | 128;
|
||||
ba[n++] = (c & 63) | 128;
|
||||
j += 2;
|
||||
}
|
||||
}
|
||||
return new Uint8Array(ba);
|
||||
}
|
||||
|
||||
function arrayBufferToHex(arrayBuffer) {
|
||||
if (typeof arrayBuffer !== 'object' || arrayBuffer === null || typeof arrayBuffer.byteLength !== 'number') {
|
||||
throw new TypeError('Expected input to be an ArrayBuffer')
|
||||
}
|
||||
|
||||
var view = new Uint8Array(arrayBuffer)
|
||||
var result = ''
|
||||
var value
|
||||
|
||||
for (var i = 0; i < view.length; i++) {
|
||||
value = view[i].toString(16)
|
||||
result += (value.length === 1 ? '0' + value : value)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
async function getExecutableScript(oldElem) {
|
||||
let out = document.createElement('script');
|
||||
const attList = ['type', 'text', 'src', 'crossorigin', 'defer', 'referrerpolicy'];
|
||||
attList.forEach((att) => {
|
||||
if (oldElem[att])
|
||||
out[att] = oldElem[att];
|
||||
})
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
async function convertHTMLToElement(content) {
|
||||
let out = document.createElement('div');
|
||||
out.innerHTML = content;
|
||||
out.querySelectorAll('script').forEach(async (elem) => {
|
||||
elem.replaceWith(await getExecutableScript(elem));
|
||||
});
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
function getKeyMaterial(password) {
|
||||
let encoder = new TextEncoder();
|
||||
return cryptoObj.subtle.importKey(
|
||||
'raw',
|
||||
encoder.encode(password),
|
||||
{
|
||||
'name': 'PBKDF2',
|
||||
},
|
||||
false,
|
||||
[
|
||||
'deriveKey',
|
||||
'deriveBits',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
function getHmacKey(keyMaterial) {
|
||||
return cryptoObj.subtle.deriveKey({
|
||||
'name': 'PBKDF2',
|
||||
'hash': 'SHA-256',
|
||||
'salt': keySalt.buffer,
|
||||
'iterations': 1024
|
||||
}, keyMaterial, {
|
||||
'name': 'HMAC',
|
||||
'hash': 'SHA-256',
|
||||
'length': 256,
|
||||
}, true, [
|
||||
'verify',
|
||||
]);
|
||||
}
|
||||
|
||||
function getDecryptKey(keyMaterial) {
|
||||
return cryptoObj.subtle.deriveKey({
|
||||
'name': 'PBKDF2',
|
||||
'hash': 'SHA-256',
|
||||
'salt': keySalt.buffer,
|
||||
'iterations': 1024,
|
||||
}, keyMaterial, {
|
||||
'name': 'AES-CBC',
|
||||
'length': 256,
|
||||
}, true, [
|
||||
'decrypt',
|
||||
]);
|
||||
}
|
||||
|
||||
function getIv(keyMaterial) {
|
||||
return cryptoObj.subtle.deriveBits({
|
||||
'name': 'PBKDF2',
|
||||
'hash': 'SHA-256',
|
||||
'salt': ivSalt.buffer,
|
||||
'iterations': 512,
|
||||
}, keyMaterial, 16 * 8);
|
||||
}
|
||||
|
||||
async function verifyContent(key, content) {
|
||||
const encoder = new TextEncoder();
|
||||
const encoded = encoder.encode(content);
|
||||
|
||||
let signature = hexToArray(HmacDigist);
|
||||
|
||||
const result = await cryptoObj.subtle.verify({
|
||||
'name': 'HMAC',
|
||||
'hash': 'SHA-256',
|
||||
}, key, signature, encoded);
|
||||
console.log(`Verification result: ${result}`);
|
||||
if (!result) {
|
||||
alert(wrongHashMessage);
|
||||
console.log(`${wrongHashMessage}, got `, signature, ` but proved wrong.`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async function decrypt(decryptKey, iv, hmacKey) {
|
||||
let typedArray = hexToArray(encryptedData);
|
||||
|
||||
const result = await cryptoObj.subtle.decrypt({
|
||||
'name': 'AES-CBC',
|
||||
'iv': iv,
|
||||
}, decryptKey, typedArray.buffer).then(async (result) => {
|
||||
const decoder = new TextDecoder();
|
||||
const decoded = decoder.decode(result);
|
||||
|
||||
// check the prefix, if not then we can sure here is wrong password.
|
||||
if (!decoded.startsWith(knownPrefix)) {
|
||||
throw "Decode successfully but not start with KnownPrefix.";
|
||||
}
|
||||
|
||||
const hideButton = document.createElement('button');
|
||||
hideButton.textContent = 'Encrypt again';
|
||||
hideButton.type = 'button';
|
||||
hideButton.classList.add("hbe-button");
|
||||
hideButton.addEventListener('click', () => {
|
||||
window.localStorage.removeItem(storageName);
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
document.getElementById('hexo-blog-encrypt').style.display = 'inline';
|
||||
document.getElementById('hexo-blog-encrypt').innerHTML = '';
|
||||
document.getElementById('hexo-blog-encrypt').appendChild(await convertHTMLToElement(decoded));
|
||||
document.getElementById('hexo-blog-encrypt').appendChild(hideButton);
|
||||
|
||||
// support html5 lazyload functionality.
|
||||
document.querySelectorAll('img').forEach((elem) => {
|
||||
if (elem.getAttribute("data-src") && !elem.src) {
|
||||
elem.src = elem.getAttribute('data-src');
|
||||
}
|
||||
});
|
||||
|
||||
// support theme-next refresh
|
||||
window.NexT && NexT.boot && typeof NexT.boot.refresh === 'function' && NexT.boot.refresh();
|
||||
|
||||
// TOC part
|
||||
var tocDiv = document.getElementById("toc-div");
|
||||
if (tocDiv) {
|
||||
tocDiv.style.display = 'inline';
|
||||
}
|
||||
|
||||
var tocDivs = document.getElementsByClassName('toc-div-class');
|
||||
if (tocDivs && tocDivs.length > 0) {
|
||||
for (var idx = 0; idx < tocDivs.length; idx++) {
|
||||
tocDivs[idx].style.display = 'inline';
|
||||
}
|
||||
}
|
||||
|
||||
// trigger event
|
||||
var event = new Event('hexo-blog-decrypt');
|
||||
window.dispatchEvent(event);
|
||||
|
||||
return await verifyContent(hmacKey, decoded);
|
||||
}).catch((e) => {
|
||||
alert(wrongPassMessage);
|
||||
console.log(e);
|
||||
return false;
|
||||
});
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
function hbeLoader() {
|
||||
|
||||
const oldStorageData = JSON.parse(storage.getItem(storageName));
|
||||
|
||||
if (oldStorageData) {
|
||||
console.log(`Password got from localStorage(${storageName}): `, oldStorageData);
|
||||
|
||||
const sIv = hexToArray(oldStorageData.iv).buffer;
|
||||
const sDk = oldStorageData.dk;
|
||||
const sHmk = oldStorageData.hmk;
|
||||
|
||||
cryptoObj.subtle.importKey('jwk', sDk, {
|
||||
'name': 'AES-CBC',
|
||||
'length': 256,
|
||||
}, true, [
|
||||
'decrypt',
|
||||
]).then((dkCK) => {
|
||||
cryptoObj.subtle.importKey('jwk', sHmk, {
|
||||
'name': 'HMAC',
|
||||
'hash': 'SHA-256',
|
||||
'length': 256,
|
||||
}, true, [
|
||||
'verify',
|
||||
]).then((hmkCK) => {
|
||||
decrypt(dkCK, sIv, hmkCK).then((result) => {
|
||||
if (!result) {
|
||||
storage.removeItem(storageName);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
mainElement.addEventListener('keydown', async (event) => {
|
||||
if (event.isComposing || event.keyCode === 13) {
|
||||
const password = document.getElementById('hbePass').value;
|
||||
const keyMaterial = await getKeyMaterial(password);
|
||||
const hmacKey = await getHmacKey(keyMaterial);
|
||||
const decryptKey = await getDecryptKey(keyMaterial);
|
||||
const iv = await getIv(keyMaterial);
|
||||
|
||||
decrypt(decryptKey, iv, hmacKey).then((result) => {
|
||||
console.log(`Decrypt result: ${result}`);
|
||||
if (result) {
|
||||
cryptoObj.subtle.exportKey('jwk', decryptKey).then((dk) => {
|
||||
cryptoObj.subtle.exportKey('jwk', hmacKey).then((hmk) => {
|
||||
const newStorageData = {
|
||||
'dk': dk,
|
||||
'iv': arrayBufferToHex(iv),
|
||||
'hmk': hmk,
|
||||
};
|
||||
storage.setItem(storageName, JSON.stringify(newStorageData));
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
hbeLoader();
|
||||
|
||||
})();
|
204
notice/index.html
Normal file
@ -0,0 +1,204 @@
|
||||
<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0,viewport-fit=cover"><title>公告 | The Blog</title><meta name="author" content="Jason"><meta name="copyright" content="Jason"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="2023 03-1 建立博客 09-10 改进博客中图片的存储方案 09-11 博客增加百度访问统计功能 09-12 博客中增加公告页">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="公告">
|
||||
<meta property="og:url" content="https://jasonsgong.gitee.io/notice/index.html">
|
||||
<meta property="og:site_name" content="The Blog">
|
||||
<meta property="og:description" content="2023 03-1 建立博客 09-10 改进博客中图片的存储方案 09-11 博客增加百度访问统计功能 09-12 博客中增加公告页">
|
||||
<meta property="og:locale" content="zh_CN">
|
||||
<meta property="og:image" content="https://jasonsgong.gitee.io/img/5.jpg">
|
||||
<meta property="article:published_time" content="2023-09-12T05:52:08.000Z">
|
||||
<meta property="article:modified_time" content="2023-09-12T03:17:10.000Z">
|
||||
<meta property="article:author" content="Jason">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="https://jasonsgong.gitee.io/img/5.jpg"><link rel="shortcut icon" href="/img/%E5%9B%BE%E6%A0%87.png"><link rel="canonical" href="https://jasonsgong.gitee.io/notice/index.html"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//hm.baidu.com"/><link rel="preconnect" href="//busuanzi.ibruce.info"/><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/node-snackbar/dist/snackbar.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox/fancybox.min.css" media="print" onload="this.media='all'"><script>var _hmt = _hmt || [];
|
||||
(function() {
|
||||
var hm = document.createElement("script");
|
||||
hm.src = "https://hm.baidu.com/hm.js?863f84ff1342665d6b55193272aea7b2";
|
||||
var s = document.getElementsByTagName("script")[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
</script><script>const GLOBAL_CONFIG = {
|
||||
root: '/',
|
||||
algolia: undefined,
|
||||
localSearch: {"path":"/search.xml","preload":true,"top_n_per_article":1,"unescape":false,"languages":{"hits_empty":"找不到您查询的内容:${query}","hits_stats":"共找到 ${hits} 篇文章"}},
|
||||
translate: undefined,
|
||||
noticeOutdate: undefined,
|
||||
highlight: {"plugin":"highlighjs","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":400},
|
||||
copy: {
|
||||
success: '复制成功',
|
||||
error: '复制错误',
|
||||
noSupport: '浏览器不支持'
|
||||
},
|
||||
relativeDate: {
|
||||
homepage: true,
|
||||
post: true
|
||||
},
|
||||
runtime: '天',
|
||||
dateSuffix: {
|
||||
just: '刚刚',
|
||||
min: '分钟前',
|
||||
hour: '小时前',
|
||||
day: '天前',
|
||||
month: '个月前'
|
||||
},
|
||||
copyright: undefined,
|
||||
lightbox: 'mediumZoom',
|
||||
Snackbar: {"chs_to_cht":"你已切换为繁体","cht_to_chs":"你已切换为简体","day_to_night":"你已切换为深色模式","night_to_day":"你已切换为浅色模式","bgLight":"#008080","bgDark":"#008080","position":"top-center"},
|
||||
source: {
|
||||
justifiedGallery: {
|
||||
js: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.js',
|
||||
css: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.css'
|
||||
}
|
||||
},
|
||||
isPhotoFigcaption: false,
|
||||
islazyload: false,
|
||||
isAnchor: false,
|
||||
percent: {
|
||||
toc: true,
|
||||
rightside: false,
|
||||
},
|
||||
autoDarkmode: false
|
||||
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
|
||||
title: '公告',
|
||||
isPost: false,
|
||||
isHome: false,
|
||||
isHighlightShrink: false,
|
||||
isToc: false,
|
||||
postUpdate: '2023-09-12 11:17:10'
|
||||
}</script><noscript><style type="text/css">
|
||||
#nav {
|
||||
opacity: 1
|
||||
}
|
||||
.justified-gallery img {
|
||||
opacity: 1
|
||||
}
|
||||
|
||||
#recent-posts time,
|
||||
#post-meta time {
|
||||
display: inline !important
|
||||
}
|
||||
</style></noscript><script>(win=>{
|
||||
win.saveToLocal = {
|
||||
set: function setWithExpiry(key, value, ttl) {
|
||||
if (ttl === 0) return
|
||||
const now = new Date()
|
||||
const expiryDay = ttl * 86400000
|
||||
const item = {
|
||||
value: value,
|
||||
expiry: now.getTime() + expiryDay,
|
||||
}
|
||||
localStorage.setItem(key, JSON.stringify(item))
|
||||
},
|
||||
|
||||
get: function getWithExpiry(key) {
|
||||
const itemStr = localStorage.getItem(key)
|
||||
|
||||
if (!itemStr) {
|
||||
return undefined
|
||||
}
|
||||
const item = JSON.parse(itemStr)
|
||||
const now = new Date()
|
||||
|
||||
if (now.getTime() > item.expiry) {
|
||||
localStorage.removeItem(key)
|
||||
return undefined
|
||||
}
|
||||
return item.value
|
||||
}
|
||||
}
|
||||
|
||||
win.getScript = url => new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script')
|
||||
script.src = url
|
||||
script.async = true
|
||||
script.onerror = reject
|
||||
script.onload = script.onreadystatechange = function() {
|
||||
const loadState = this.readyState
|
||||
if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
|
||||
script.onload = script.onreadystatechange = null
|
||||
resolve()
|
||||
}
|
||||
document.head.appendChild(script)
|
||||
})
|
||||
|
||||
win.getCSS = (url,id = false) => new Promise((resolve, reject) => {
|
||||
const link = document.createElement('link')
|
||||
link.rel = 'stylesheet'
|
||||
link.href = url
|
||||
if (id) link.id = id
|
||||
link.onerror = reject
|
||||
link.onload = link.onreadystatechange = function() {
|
||||
const loadState = this.readyState
|
||||
if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
|
||||
link.onload = link.onreadystatechange = null
|
||||
resolve()
|
||||
}
|
||||
document.head.appendChild(link)
|
||||
})
|
||||
|
||||
win.activateDarkMode = function () {
|
||||
document.documentElement.setAttribute('data-theme', 'dark')
|
||||
if (document.querySelector('meta[name="theme-color"]') !== null) {
|
||||
document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
|
||||
}
|
||||
}
|
||||
win.activateLightMode = function () {
|
||||
document.documentElement.setAttribute('data-theme', 'light')
|
||||
if (document.querySelector('meta[name="theme-color"]') !== null) {
|
||||
document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
|
||||
}
|
||||
}
|
||||
const t = saveToLocal.get('theme')
|
||||
|
||||
if (t === 'dark') activateDarkMode()
|
||||
else if (t === 'light') activateLightMode()
|
||||
|
||||
const asideStatus = saveToLocal.get('aside-status')
|
||||
if (asideStatus !== undefined) {
|
||||
if (asideStatus === 'hide') {
|
||||
document.documentElement.classList.add('hide-aside')
|
||||
} else {
|
||||
document.documentElement.classList.remove('hide-aside')
|
||||
}
|
||||
}
|
||||
|
||||
const detectApple = () => {
|
||||
if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){
|
||||
document.documentElement.classList.add('apple')
|
||||
}
|
||||
}
|
||||
detectApple()
|
||||
})(window)</script><!-- hexo injector head_end start --><link rel="stylesheet" href="https://npm.elemecdn.com/hexo-butterfly-swiper/lib/swiper.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://npm.elemecdn.com/hexo-butterfly-swiper/lib/swiperstyle.css" media="print" onload="this.media='all'"><!-- hexo injector head_end end --><meta name="generator" content="Hexo 6.3.0"></head><body><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="avatar-img is-center"><img src="/img/avatar.jpg" onerror="onerror=null;src='/img/loading.gif'" alt="avatar"/></div><div class="sidebar-site-data site-data is-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">70</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">37</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">6</div></a></div><br/><div class="menus_items"><div class="menus_item"><a class="site-page" target="_blank" rel="noopener" href="https://gitee.com/JasonsGong/jasonsgong/pages"><i class="fa-fw fas fa-sync-alt"></i><span> 更新</span></a></div><div class="menus_item"><a class="site-page" target="_blank" rel="noopener" href="https://www.tutorialspoint.com/compile_java8_online.php"><i class="fa-fw fas fa-code"></i><span> 代码</span></a></div><div class="menus_item"><a class="site-page" href="/notice/"><i class="fa-fw far fa-paper-plane"></i><span> 公告</span></a></div><div class="menus_item"><a class="site-page" href="/website/bookmarks.html"><i class="fa-fw fas fa-desktop"></i><span> 网址</span></a></div><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 主页</span></a></div></div></div></div><div class="page" id="body-wrap"><header class="not-top-img" id="page-header"><nav id="nav"><span id="blog-info"><a href="/" title="The Blog"><span class="site-name">The Blog</span></a></span><div id="menus"><div id="search-button"><a class="site-page social-icon search" href="javascript:void(0);"><i class="fas fa-search fa-fw"></i><span> 搜索</span></a></div><div class="menus_items"><div class="menus_item"><a class="site-page" target="_blank" rel="noopener" href="https://gitee.com/JasonsGong/jasonsgong/pages"><i class="fa-fw fas fa-sync-alt"></i><span> 更新</span></a></div><div class="menus_item"><a class="site-page" target="_blank" rel="noopener" href="https://www.tutorialspoint.com/compile_java8_online.php"><i class="fa-fw fas fa-code"></i><span> 代码</span></a></div><div class="menus_item"><a class="site-page" href="/notice/"><i class="fa-fw far fa-paper-plane"></i><span> 公告</span></a></div><div class="menus_item"><a class="site-page" href="/website/bookmarks.html"><i class="fa-fw fas fa-desktop"></i><span> 网址</span></a></div><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 主页</span></a></div></div><div id="toggle-menu"><a class="site-page" href="javascript:void(0);"><i class="fas fa-bars fa-fw"></i></a></div></div></nav></header><main class="layout hide-aside" id="content-inner"><div id="page"><h1 class="page-title">公告</h1><div id="article-container"><div class="timeline green"><div class='timeline-item headline'><div class='timeline-item-title'><div class='item-circle'><p>2023</p>
|
||||
</div></div></div><div class='timeline-item'><div class='timeline-item-title'><div class='item-circle'><p>03-1</p>
|
||||
</div></div><div class='timeline-item-content'><p>建立博客</p>
|
||||
</div></div><div class='timeline-item'><div class='timeline-item-title'><div class='item-circle'><p>09-10</p>
|
||||
</div></div><div class='timeline-item-content'><p>改进博客中图片的存储方案</p>
|
||||
</div></div><div class='timeline-item'><div class='timeline-item-title'><div class='item-circle'><p>09-11</p>
|
||||
</div></div><div class='timeline-item-content'><p>博客增加百度访问统计功能</p>
|
||||
</div></div><div class='timeline-item'><div class='timeline-item-title'><div class='item-circle'><p>09-12</p>
|
||||
</div></div><div class='timeline-item-content'><p>博客中增加公告页</p>
|
||||
</div></div></div>
|
||||
</div></div></main><footer id="footer"><div id="footer-wrap"><div class="framework-info"><span>框架 </span><a target="_blank" rel="noopener" href="https://hexo.io">Hexo</a><span class="footer-separator">|</span><span>主题 </span><a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="fas fa-adjust"></i></button></div><div id="rightside-config-show"><button id="rightside_config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button id="go-up" type="button" title="回到顶部"><span class="scroll-percent"></span><i class="fas fa-arrow-up"></i></button></div></div><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><script src="https://cdn.jsdelivr.net/npm/medium-zoom/dist/medium-zoom.min.js"></script><script src="https://cdn.jsdelivr.net/npm/node-snackbar/dist/snackbar.min.js"></script><div class="js-pjax"></div><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script><div id="local-search"><div class="search-dialog"><nav class="search-nav"><span class="search-dialog-title">搜索</span><span id="loading-status"></span><button class="search-close-button"><i class="fas fa-times"></i></button></nav><div class="is-center" id="loading-database"><i class="fas fa-spinner fa-pulse"></i><span> 数据库加载中</span></div><div class="search-wrap"><div id="local-search-input"><div class="local-search-box"><input class="local-search-box--input" placeholder="搜索文章" type="text"/></div></div><br/><div class="no-result" id="local-search-results"></div><div id="local-search-stats-wrap"></div></div></div><div id="search-mask"></div><script src="/js/search/local-search.js"></script></div></div><!-- hexo injector body_end start --><script data-pjax>
|
||||
function butterfly_swiper_injector_config(){
|
||||
var parent_div_git = document.getElementById('recent-posts');
|
||||
var item_html = '<div class="recent-post-item" style="height: auto;width: 100%"><div class="blog-slider swiper-container-fade swiper-container-horizontal" id="swiper_container"><div class="blog-slider__wrp swiper-wrapper" style="transition-duration: 0ms;"><div class="blog-slider__item swiper-slide" style="width: 750px; opacity: 1; transform: translate3d(0px, 0px, 0px); transition-duration: 0ms;"><a class="blog-slider__img" href="posts/19306.html" alt=""><img width="48" height="48" src="/img/1.jpg" alt="" onerror="this.src=https://unpkg.zhimg.com/akilar-candyassets/image/loading.gif; this.onerror = null;"/></a><div class="blog-slider__content"><span class="blog-slider__code">2023-04-21</span><a class="blog-slider__title" href="posts/19306.html" alt="">Docker容器化技术</a><div class="blog-slider__text">Docker</div><a class="blog-slider__button" href="posts/19306.html" alt="">详情 </a></div></div><div class="blog-slider__item swiper-slide" style="width: 750px; opacity: 1; transform: translate3d(0px, 0px, 0px); transition-duration: 0ms;"><a class="blog-slider__img" href="posts/47003.html" alt=""><img width="48" height="48" src="/img/1.jpg" alt="" onerror="this.src=https://unpkg.zhimg.com/akilar-candyassets/image/loading.gif; this.onerror = null;"/></a><div class="blog-slider__content"><span class="blog-slider__code">2023-03-10</span><a class="blog-slider__title" href="posts/47003.html" alt="">常用正则表达式大全</a><div class="blog-slider__text">正则表达式</div><a class="blog-slider__button" href="posts/47003.html" alt="">详情 </a></div></div><div class="blog-slider__item swiper-slide" style="width: 750px; opacity: 1; transform: translate3d(0px, 0px, 0px); transition-duration: 0ms;"><a class="blog-slider__img" href="posts/20683.html" alt=""><img width="48" height="48" src="/img/3.jpg" alt="" onerror="this.src=https://unpkg.zhimg.com/akilar-candyassets/image/loading.gif; this.onerror = null;"/></a><div class="blog-slider__content"><span class="blog-slider__code">2023-06-05</span><a class="blog-slider__title" href="posts/20683.html" alt="">Linux中开发环境的搭建</a><div class="blog-slider__text">环境搭建</div><a class="blog-slider__button" href="posts/20683.html" alt="">详情 </a></div></div><div class="blog-slider__item swiper-slide" style="width: 750px; opacity: 1; transform: translate3d(0px, 0px, 0px); transition-duration: 0ms;"><a class="blog-slider__img" href="posts/63333.html" alt=""><img width="48" height="48" src="/img/1.jpg" alt="" onerror="this.src=https://unpkg.zhimg.com/akilar-candyassets/image/loading.gif; this.onerror = null;"/></a><div class="blog-slider__content"><span class="blog-slider__code">2023-06-03</span><a class="blog-slider__title" href="posts/63333.html" alt="">开发环境的搭建</a><div class="blog-slider__text">环境搭建</div><a class="blog-slider__button" href="posts/63333.html" alt="">详情 </a></div></div></div><div class="blog-slider__pagination swiper-pagination-clickable swiper-pagination-bullets"></div></div></div>';
|
||||
parent_div_git.insertAdjacentHTML("afterbegin",item_html)
|
||||
}
|
||||
var elist = 'undefined'.split(',');
|
||||
var cpage = location.pathname;
|
||||
var epage = '/';
|
||||
var flag = 0;
|
||||
|
||||
for (var i=0;i<elist.length;i++){
|
||||
if (cpage.includes(elist[i])){
|
||||
flag++;
|
||||
}
|
||||
}
|
||||
|
||||
if ((epage ==='all')&&(flag == 0)){
|
||||
butterfly_swiper_injector_config();
|
||||
}
|
||||
else if (epage === cpage){
|
||||
butterfly_swiper_injector_config();
|
||||
}
|
||||
</script><script defer src="https://npm.elemecdn.com/hexo-butterfly-swiper/lib/swiper.min.js"></script><script defer data-pjax src="https://npm.elemecdn.com/hexo-butterfly-swiper/lib/swiper_init.js"></script><!-- hexo injector body_end end --></body></html>
|
315
page/2/index.html
Normal file
403
page/3/index.html
Normal file
276
page/4/index.html
Normal file
271
page/5/index.html
Normal file
307
page/6/index.html
Normal file
235
page/7/index.html
Normal file
BIN
pdf/2023最新Java面试题全集.pdf
Normal file
BIN
pdf/JAVA开发_李传播_5年.pdf
Normal file
BIN
pdf/Java8实战.pdf
Normal file
BIN
pdf/Java开发_AAA_N年.pdf
Normal file
BIN
pdf/Linux命令行与shell脚本编程大全.pdf
Normal file
BIN
pdf/MyBatis.pdf
Normal file
BIN
pdf/MySQL-基础篇.pdf
Normal file
BIN
pdf/MySQL-运维篇.pdf
Normal file
BIN
pdf/MySQL-进阶篇.pdf
Normal file
BIN
pdf/MySQL5.7.19安装.pdf
Normal file
BIN
pdf/MySQL面试题-参考回答.pdf
Normal file
BIN
pdf/Redis面试题-参考回答.pdf
Normal file
BIN
pdf/SSM整合.pdf
Normal file
BIN
pdf/Thymeleaf中文参考文档.pdf
Normal file
BIN
pdf/【参考】1年_本科.pdf
Normal file
BIN
pdf/【必看】面试参考话术.pdf
Normal file
BIN
pdf/个人简历(大四实习).pdf
Normal file
BIN
pdf/尚硅谷图解Java数据结构和算法.pdf
Normal file
BIN
pdf/微服务面试题-参考回答.pdf
Normal file
BIN
pdf/时尚线条简历模板.pdf
Normal file
BIN
pdf/框架篇面试题-参考回答.pdf
Normal file
BIN
pdf/消息中间件面试题-参考回答.pdf
Normal file
BIN
pdf/灰色大气简约简历模板.pdf
Normal file
BIN
pdf/灰蓝色色时尚简历模板.pdf
Normal file
BIN
pdf/科技版简历模板.pdf
Normal file
BIN
pdf/简历.pdf
Normal file
BIN
pdf/简历模板_CN.pdf
Normal file
BIN
pdf/简约大气橙色简历模板.pdf
Normal file
BIN
pdf/精通Linux.第2版.pdf
Normal file
BIN
pdf/经典风格简历模板.pdf
Normal file
BIN
pdf/韩顺平_循序渐进学Java零基础【完整笔记】.pdf
Normal file
BIN
pdf/韩顺平教育安装CentOS7.6.pdf
Normal file
BIN
pdf/韩顺平教育安装CentOS8.1.pdf
Normal file
BIN
pictures/1573636765632.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
pictures/1573649804623.png
Normal file
After Width: | Height: | Size: 12 KiB |