-
<%- toc(
- page?.encrypt === true ? page.origin : page.content,
+ page.content,
{
class: 'nav',
list_number: theme?.toc?.number || false
diff --git a/scripts/filters/encrypt-handle.js b/scripts/filters/encrypt-handle.js
new file mode 100644
index 00000000..c384e783
--- /dev/null
+++ b/scripts/filters/encrypt-handle.js
@@ -0,0 +1,30 @@
+/* global hexo */
+
+'use strict'
+
+const crypto = require('crypto')
+
+function encrypt(text, key, iv) {
+ const algorithm = 'aes-256-cbc'
+ const cipher = crypto.createCipheriv(algorithm, Buffer.from(key, 'hex'), Buffer.from(iv, 'hex'))
+ let encrypted = cipher.update(text, 'utf8', 'hex')
+ encrypted += cipher.final('hex')
+ return encrypted
+}
+
+hexo.extend.filter.register(
+ 'after_post_render',
+ function (data) {
+ let { password } = data
+ password = String(password).trim()
+ if (password) {
+ const secretKey = crypto.randomBytes(32).toString('hex') // 256-bit
+ const iv = crypto.randomBytes(16).toString('hex') // 128-bit
+ data.secretKey = secretKey
+ data.iv = iv
+ data.encryptedPassword = encrypt(password, secretKey, iv)
+ data.encryptedContent = encrypt(data.content, secretKey, iv)
+ }
+ },
+ 1
+)
diff --git a/source/css/common/code-block/code-theme.styl b/source/css/common/code-block/code-theme.styl
index ccd96883..cd20214e 100644
--- a/source/css/common/code-block/code-theme.styl
+++ b/source/css/common/code-block/code-theme.styl
@@ -27,8 +27,8 @@ if (hexo-config('code_block') && hexo-config('code_block.highlight_theme') == 'o
$highlight-aqua = #458383
$highlight-blue = #6e95bd
$highlight-purple = #b18bb7
- $highlight-deletion = #ff9999
- $highlight-addition = #ccff99
+ $highlight-deletion = #f39090
+ $highlight-addition = #7ccf26
$shrink-line-foreground = $dark-text-color-4
$shrink-line-background-1 = rgba(40, 40, 40, 0.9)
$shrink-line-background-2 = rgba(40, 40, 40, 0.6)
@@ -48,8 +48,8 @@ if (hexo-config('code_block') && hexo-config('code_block.highlight_theme') == 'o
$dark-highlight-aqua = darken($highlight-aqua, 2%)
$dark-highlight-blue = darken($highlight-blue, 2%)
$dark-highlight-purple = darken($highlight-purple, 2%)
- $dark-highlight-deletion = darken($highlight-deletion, 2%)
- $dark-highlight-addition = darken($highlight-addition, 2%)
+ $dark-highlight-deletion = darken($highlight-deletion, 20%)
+ $dark-highlight-addition = darken($highlight-addition, 20%)
$dark-shrink-line-foreground = $dark-text-color-4
$dark-shrink-line-background-1 = rgba(40, 40, 40, 0.9)
$dark-shrink-line-background-2 = rgba(40, 40, 40, 0.6)
@@ -90,8 +90,8 @@ else {
$dark-highlight-aqua = #8abeb7
$dark-highlight-blue = #81a2be
$dark-highlight-purple = #b294bb
- $dark-highlight-deletion = #008000
- $dark-highlight-addition = #800000
+ $dark-highlight-addition = darken($highlight-addition, 80%)
+ $dark-highlight-deletion = darken($highlight-deletion, 75%)
$dark-shrink-line-foreground = $dark-text-color-4
$dark-shrink-line-background-1 = rgba(38, 38, 38, 0.9)
$dark-shrink-line-background-2 = rgba(52, 52, 52, 0.6)
@@ -114,6 +114,8 @@ code-theme(mode) {
--highlight-aqua mode == 'light' ? $highlight-aqua : $dark-highlight-aqua
--highlight-blue mode == 'light' ? $highlight-blue : $dark-highlight-blue
--highlight-purple mode == 'light' ? $highlight-purple : $dark-highlight-purple
+ --highlight-deletion mode == 'light' ? $highlight-deletion : $dark-highlight-deletion
+ --highlight-addition mode == 'light' ? $highlight-addition : $dark-highlight-addition
--highlight-gutter-color mode == 'light' ? $highlight-gutter-color : $dark-highlight-gutter-color
--highlight-gutter-bg-color mode == 'light' ? $highlight-gutter-bg-color : $dark-highlight-gutter-bg-color
--mac-toolbar-background-color mode == 'light' ? $mac-toolbar-background-color : $dark-mac-toolbar-background-color
diff --git a/source/css/common/code-block/highlight.styl b/source/css/common/code-block/highlight.styl
index 74a01054..bdaa384a 100644
--- a/source/css/common/code-block/highlight.styl
+++ b/source/css/common/code-block/highlight.styl
@@ -146,11 +146,13 @@ pre {
pre .deletion {
+ color var(--highlight-foreground)
background var(--highlight-deletion)
}
pre .addition {
+ color var(--highlight-foreground)
background var(--highlight-addition)
}
diff --git a/source/css/common/markdown.styl b/source/css/common/markdown.styl
index ff245f05..dca7c59f 100644
--- a/source/css/common/markdown.styl
+++ b/source/css/common/markdown.styl
@@ -1,4 +1,5 @@
-.keep-markdown-body {
+.keep-markdown-body
+.keep-markdown-body > .post {
font-size 1rem
blockquote {
diff --git a/source/css/layout/_page/post.styl b/source/css/layout/_page/post.styl
index b5d66033..0664d1ae 100644
--- a/source/css/layout/_page/post.styl
+++ b/source/css/layout/_page/post.styl
@@ -25,6 +25,13 @@ $spacer-padding = 2rem
}
+ &.encrypt {
+ .post-aging-tips {
+ display none !important
+ }
+ }
+
+
+keep-tablet() {
.pc-post-toc {
display none !important
@@ -266,6 +273,40 @@ $spacer-padding = 2rem
}
+ .post-encrypt-box {
+ margin-left $avatar-width + 0.8rem
+ padding 1rem 0
+
+ .password-input {
+ width 20rem
+ margin 0
+ padding 0.8rem 1.2rem
+ color var(--text-color-3)
+ font-weight 400 !important
+ font-size 1.2rem
+ letter-spacing 2px
+ background none
+ border none
+ border-bottom 0.2rem solid var(--border-color)
+ outline none
+
+ &.error {
+ border 0.2rem solid var(--keep-danger-color)
+ }
+
+ &::placeholder {
+ color var(--text-color-4)
+ font-size 1.1rem
+ letter-spacing 1px
+ }
+
+ &:hover
+ &:focus {
+ background var(--background-color-2)
+ }
+ }
+ }
+
if (hexo-config('post') && hexo-config('post.copyright_info') == true) {
border-bottom 2px dashed var(--border-color)
}
@@ -402,33 +443,4 @@ $spacer-padding = 2rem
max-height calc(100vh - calc(var(--header-shrink-height) + var(--component-gap)))
}
}
-
-
- .hbe-container {
-
- .hbe-input-field {
- color var(--text-color-3) !important
- background var(--background-color-3) !important
- }
-
-
- .hbe-input-label {
- &::before {
- display none !important
- }
-
- &::after {
- background var(--text-color-4) !important
- }
-
- .hbe-input-label-content {
- color var(--text-color-4) !important
- }
- }
-
-
- .hbe-button {
- margin-top 2rem
- }
- }
}
diff --git a/source/css/layout/_partial/archive-list.styl b/source/css/layout/_partial/archive-list.styl
index d8c770e0..1308b2b2 100644
--- a/source/css/layout/_partial/archive-list.styl
+++ b/source/css/layout/_partial/archive-list.styl
@@ -3,7 +3,7 @@ $post-title-font-size = 1.2rem
$post-date-font-size = 1rem
$post-date-width = 3.6rem
$post-item-padding = 1.2rem
-$timeline-circle-width = 0.6rem
+$timeline-circle-size = 0.5rem
.archive-list-container {
@@ -68,10 +68,10 @@ $timeline-circle-width = 0.6rem
left 0
z-index $z-index-2
box-sizing border-box
- width $timeline-circle-width * 0.8
- height $timeline-circle-width * 2
+ width $timeline-circle-size
+ height $timeline-circle-size
background var(--text-color-5)
- border-radius 0.2rem
+ border-radius 50%
content ''
transition-t("height", "0", "0.2", "ease")
}
@@ -81,7 +81,8 @@ $timeline-circle-width = 0.6rem
&:hover {
.starting-point {
&::before {
- height $timeline-circle-width * 2.4
+ width $timeline-circle-size * 1.2
+ height $timeline-circle-size * 1.2
background var(--text-color-4)
}
}
diff --git a/source/css/layout/_partial/post/post-tools.styl b/source/css/layout/_partial/post/post-tools.styl
index eaec7d59..00620b28 100644
--- a/source/css/layout/_partial/post/post-tools.styl
+++ b/source/css/layout/_partial/post/post-tools.styl
@@ -4,7 +4,7 @@ $post-tool-button-width = 2.5rem
.post-tools-container {
padding-top var(--component-gap)
- .tools-list {
+ .post-tools-list {
li {
position relative
@@ -33,7 +33,7 @@ $post-tool-button-width = 2.5rem
background var(--primary-color)
i {
- color var(--background-color-1)
+ color var(--background-color-1) !important
}
}
@@ -70,6 +70,31 @@ $post-tool-button-width = 2.5rem
}
}
}
+
+ &.post-lock {
+ cursor default
+
+ .fa-lock-open {
+ display none
+ color var(--keep-success-color)
+ }
+
+ .fa-lock {
+ color var(--keep-warning-color)
+ }
+
+ &.decrypt {
+ cursor pointer
+
+ .fa-lock-open {
+ display block
+ }
+
+ .fa-lock {
+ display none
+ }
+ }
+ }
}
}
}
diff --git a/source/js/lazyload.js b/source/js/lazyload.js
index 031b16cf..b18604ae 100644
--- a/source/js/lazyload.js
+++ b/source/js/lazyload.js
@@ -1,6 +1,10 @@
/* global KEEP */
KEEP.initLazyLoad = () => {
+ if (KEEP?.theme_config?.lazyload?.enable !== true) {
+ return
+ }
+
const imgs = document.querySelectorAll('img')
let now = Date.now()
let needLoad = true
diff --git a/source/js/main.js b/source/js/main.js
index a0a8abd7..832ccfbd 100644
--- a/source/js/main.js
+++ b/source/js/main.js
@@ -1,13 +1,14 @@
/* global KEEP */
window.addEventListener('DOMContentLoaded', () => {
- const { version, local_search, lazyload } = KEEP.theme_config
+ const { version, local_search } = KEEP.theme_config
KEEP.themeInfo = {
theme: `Keep v${version}`,
author: 'XPoet',
repository: 'https://github.com/XPoet/hexo-theme-keep',
localStorageKey: 'KEEP-THEME-STATUS',
+ encryptKey: 'KEEP-ENCRYPT',
styleStatus: {
isDark: false,
fontSizeLevel: 0,
@@ -67,14 +68,11 @@ window.addEventListener('DOMContentLoaded', () => {
KEEP.initBack2Top()
KEEP.initCodeBlock()
KEEP.setFooterVersion()
+ KEEP.initLazyLoad()
if (local_search?.enable === true) {
KEEP.initLocalSearch()
}
-
- if (lazyload?.enable === true) {
- KEEP.initLazyLoad()
- }
}
KEEP.initExecute()
})
diff --git a/source/js/post/post-helper.js b/source/js/post/post-helper.js
index 5b132eed..c1579e4c 100644
--- a/source/js/post/post-helper.js
+++ b/source/js/post/post-helper.js
@@ -1,11 +1,10 @@
/* global KEEP */
-function initPostHelper() {
+async function initPostHelper() {
KEEP.utils.postHelper = {
postPageContainerDom: document.querySelector('.post-page-container'),
toggleShowTocBtn: document.querySelector('.toggle-show-toc'),
toggleShowTocTabletBtn: document.querySelector('.toggle-show-toc-tablet'),
- toggleShowTocIcon: document.querySelector('.toggle-show-toc i'),
mainContentDom: document.querySelector('.main-content'),
postToolsDom: document.querySelector('.post-tools'),
@@ -50,7 +49,7 @@ function initPostHelper() {
setTimeout(() => {
this.setPostToolsLayout()
- }, 120)
+ }, 100)
},
hasToc(isOpen) {
@@ -235,6 +234,133 @@ function initPostHelper() {
document.addEventListener('mozfullscreenchange', handleFullscreenChange)
document.addEventListener('webkitfullscreenchange', handleFullscreenChange)
}
+ },
+
+ hexToBuffer(hex) {
+ const typedArray = new Uint8Array(hex.match(/[\da-f]{2}/gi).map((h) => parseInt(h, 16)))
+ return typedArray.buffer
+ },
+
+ async decrypt(encrypted, key) {
+ const algorithm = { name: 'AES-CBC', iv: this.hexToBuffer(encrypted.iv) }
+ const cryptoKey = await crypto.subtle.importKey(
+ 'raw',
+ this.hexToBuffer(key),
+ algorithm,
+ false,
+ ['decrypt']
+ )
+ const decrypted = await crypto.subtle.decrypt(
+ algorithm,
+ cryptoKey,
+ this.hexToBuffer(encrypted.encryptedData)
+ )
+ const decoder = new TextDecoder()
+ return decoder.decode(decrypted)
+ },
+
+ // encrypt toc handle
+ encryptTocHandle(show) {
+ setTimeout(() => {
+ const tocDom = document.querySelector('.pc-post-toc')
+ if (tocDom) {
+ this.handleToggleToc(show)
+ if (show) {
+ tocDom?.removeAttribute('style')
+ } else {
+ tocDom.style.display = 'none'
+ }
+ }
+ })
+ },
+
+ // post encrypt handle
+ async postEncryptHandle() {
+ const postContentDom = document.querySelector('.post-content')
+ const encryptBoxDom = postContentDom.querySelector('.post-encrypt-box')
+ const lockIconDom = document.querySelector('.post-tools-list .post-lock')
+ const sessionKey = `${KEEP.themeInfo.encryptKey}#${location.pathname}`
+ const lockClassName = `decrypt`
+
+ if (encryptBoxDom) {
+ this.encryptTocHandle(false)
+ const { secret, ep, content, iv } = encryptBoxDom.dataset
+
+ encryptBoxDom.removeAttribute('data-secret')
+ encryptBoxDom.removeAttribute('data-iv')
+ encryptBoxDom.removeAttribute('data-ep')
+ encryptBoxDom.removeAttribute('data-content')
+
+ const pwdInput = encryptBoxDom.querySelector('.password-input')
+
+ const doDecrypt = async (isDecrypted = false) => {
+ const pwdVal = pwdInput.value
+ const dp = await this.decrypt({ iv, encryptedData: ep }, secret)
+
+ const ddc = async () => {
+ const dc = await this.decrypt({ iv, encryptedData: content }, secret)
+ encryptBoxDom.style.display = 'none'
+ postContentDom.removeChild(encryptBoxDom)
+ this.postPageContainerDom.classList.remove('encrypt')
+ this.encryptTocHandle(true)
+ postContentDom.querySelector('.post').innerHTML = dc
+ setTimeout(() => {
+ KEEP.initLazyLoad()
+ KEEP.initCodeBlock()
+ KEEP.initTOC()
+ KEEP.utils.zoomInImage()
+ KEEP.utils.insertTooltipContent()
+ KEEP.utils.tabsActiveHandle()
+ KEEP.utils.wrapTableWithBox()
+ KEEP.utils.aAnchorJump()
+ })
+ lockIconDom.classList.add(lockClassName)
+ sessionStorage.setItem(`${KEEP.themeInfo.encryptKey}#${location.pathname}`, '1')
+ }
+
+ if (isDecrypted) {
+ await ddc()
+ return
+ }
+
+ if (pwdVal === dp) {
+ await ddc()
+ } else {
+ pwdInput.classList.add('error')
+ }
+ }
+
+ const decrypted = sessionStorage.getItem(sessionKey)
+
+ if (decrypted) {
+ await doDecrypt(true)
+ }
+
+ pwdInput.addEventListener('keydown', async (e) => {
+ if (pwdInput.value === '') {
+ pwdInput.classList.remove('error')
+ }
+
+ if (e.keyCode === 13) {
+ await doDecrypt()
+ e.preventDefault()
+ }
+ })
+
+ pwdInput.addEventListener('keyup', async (e) => {
+ if (pwdInput.value === '') {
+ pwdInput.classList.remove('error')
+ }
+ })
+
+ lockIconDom.addEventListener('click', () => {
+ if (lockIconDom.classList.contains(lockClassName)) {
+ lockIconDom.classList.remove(lockClassName)
+ sessionStorage.removeItem(sessionKey)
+ location.reload()
+ }
+ })
+ }
}
}
@@ -243,6 +369,10 @@ function initPostHelper() {
KEEP.utils.postHelper.resetPostUpdateDate()
KEEP.utils.postHelper.enableFullScreen()
+ if (KEEP.utils.postHelper.postPageContainerDom.classList.contains('encrypt')) {
+ await KEEP.utils.postHelper.postEncryptHandle()
+ }
+
if (KEEP.theme_config.toc?.enable === true) {
KEEP.utils.postHelper.initToggleToc()
}
diff --git a/source/js/post/toc.js b/source/js/post/toc.js
index 0eb68ee8..43230870 100644
--- a/source/js/post/toc.js
+++ b/source/js/post/toc.js
@@ -1,6 +1,6 @@
/* global KEEP */
-function initTOC() {
+KEEP.initTOC = () => {
const pageContainer = document.querySelector('.page-container')
const postPageContainer = document.querySelector('.post-page-container')
const pcTocContainer = document.querySelector('.pc-post-toc')
@@ -115,7 +115,7 @@ function initTOC() {
}
if (KEEP.theme_config?.pjax?.enable === true && KEEP.utils) {
- initTOC()
+ KEEP.initTOC()
} else {
- window.addEventListener('DOMContentLoaded', initTOC)
+ window.addEventListener('DOMContentLoaded', KEEP.initTOC)
}
diff --git a/source/js/toggle-theme.js b/source/js/toggle-theme.js
index e573731c..192992ac 100644
--- a/source/js/toggle-theme.js
+++ b/source/js/toggle-theme.js
@@ -48,10 +48,13 @@ KEEP.initModeToggle = () => {
},
initModeToggleButton() {
- this.themeModeToggleBtn.addEventListener('click', () => {
- const isDark = document.documentElement.classList.contains('dark-mode')
- isDark ? this.enableLightMode() : this.enableDarkMode()
- })
+ if (!this.themeModeToggleBtn?.hasClickListener) {
+ this.themeModeToggleBtn.addEventListener('click', () => {
+ const isDark = document.documentElement.classList.contains('dark-mode')
+ isDark ? this.enableLightMode() : this.enableDarkMode()
+ })
+ this.themeModeToggleBtn.hasClickListener = true
+ }
},
initModeAutoTrigger() {
diff --git a/source/js/utils.js b/source/js/utils.js
index dda1571e..b66b3a28 100644
--- a/source/js/utils.js
+++ b/source/js/utils.js
@@ -130,10 +130,15 @@ KEEP.initUtils = () => {
toggleShowToolsList() {
const sideToolsListDom = document.querySelector('.side-tools-list')
const toggleShowToolsDom = document.querySelector('.tool-toggle-show')
- toggleShowToolsDom.addEventListener('click', (e) => {
- sideToolsListDom.classList.toggle('show')
- e.stopPropagation()
- })
+
+ if (!toggleShowToolsDom?.hasClickListener) {
+ toggleShowToolsDom.addEventListener('click', (e) => {
+ sideToolsListDom.classList.toggle('show')
+ e.stopPropagation()
+ })
+ toggleShowToolsDom.hasClickListener = true
+ }
+
sideToolsListDom.querySelectorAll('.tools-item').forEach((item) => {
item.addEventListener('click', (e) => {
e.stopPropagation()
@@ -498,17 +503,20 @@ KEEP.initUtils = () => {
eventTrigger = 'mouseover'
}
- dom.addEventListener(eventTrigger, (e) => {
- if (isLazyLoadImg && !imgsSet[nameIdx].imgLoaded) {
- loadImg(
- document.querySelector(`.tooltip-img-box img.${imgDomClass}`),
- imgsSet[nameIdx].imgLoaded
- )
- }
- imgsSet[nameIdx].isShowImg = !imgsSet[nameIdx].isShowImg
- dom.classList.toggle('show-img')
- e.stopPropagation()
- })
+ if (!dom?.hasEventListener) {
+ dom.addEventListener(eventTrigger, (e) => {
+ if (isLazyLoadImg && !imgsSet[nameIdx].imgLoaded) {
+ loadImg(
+ document.querySelector(`.tooltip-img-box img.${imgDomClass}`),
+ imgsSet[nameIdx].imgLoaded
+ )
+ }
+ imgsSet[nameIdx].isShowImg = !imgsSet[nameIdx].isShowImg
+ dom.classList.toggle('show-img')
+ e.stopPropagation()
+ })
+ dom.hasEventListener = true
+ }
hideTooltipImg(dom, nameIdx, tooltipImgTrigger)
}
@@ -718,34 +726,37 @@ KEEP.initUtils = () => {
// H tag title to top
title2Top4HTag(a, h, duration, cb) {
if (a && h) {
- a.addEventListener('click', (e) => {
- e.preventDefault()
+ if (!a?.hasEventListener) {
+ a.addEventListener('click', (e) => {
+ e.preventDefault()
- cb && cb()
+ cb && cb()
- let winScrollY = window.scrollY
- winScrollY = winScrollY <= 1 ? -19 : winScrollY
- let offset = h.getBoundingClientRect().top + winScrollY
+ let winScrollY = window.scrollY
+ winScrollY = winScrollY <= 1 ? -19 : winScrollY
+ let offset = h.getBoundingClientRect().top + winScrollY
- if (!this.isHideHeader) {
- offset = offset - this.headerWrapperDom.getBoundingClientRect().height
- }
+ if (!this.isHideHeader) {
+ offset = offset - this.headerWrapperDom.getBoundingClientRect().height
+ }
- window.anime({
- targets: document.scrollingElement,
- duration,
- easing: 'linear',
- scrollTop: offset,
- complete: () => {
- history.pushState(null, document.title, a.href)
- if (this.isHideHeader) {
- setTimeout(() => {
- this.pageTopDom.classList.add('hide')
- }, 160)
+ window.anime({
+ targets: document.scrollingElement,
+ duration,
+ easing: 'linear',
+ scrollTop: offset,
+ complete: () => {
+ history.pushState(null, document.title, a.href)
+ if (this.isHideHeader) {
+ setTimeout(() => {
+ this.pageTopDom.classList.add('hide')
+ }, 160)
+ }
}
- }
+ })
})
- })
+ a.hasEventListener = true
+ }
}
},
@@ -757,20 +768,25 @@ KEEP.initUtils = () => {
}
}
+ // global
KEEP.utils.initData()
KEEP.utils.registerWindowScroll()
KEEP.utils.toggleShowToolsList()
KEEP.utils.globalFontAdjust()
KEEP.utils.initHasToc()
- KEEP.utils.zoomInImage()
- KEEP.utils.setHowLongAgoInHome()
- KEEP.utils.insertTooltipContent()
KEEP.utils.siteCountInitialize()
KEEP.utils.pageNumberJump()
- KEEP.utils.tabsActiveHandle()
+
+ // home page
+ KEEP.utils.setHowLongAgoInHome()
KEEP.utils.initTypewriter()
- KEEP.utils.trimPostMetaInfoBar()
KEEP.utils.closeWebsiteAnnouncement()
+ KEEP.utils.trimPostMetaInfoBar()
+
+ // post page
+ KEEP.utils.insertTooltipContent()
+ KEEP.utils.zoomInImage()
+ KEEP.utils.tabsActiveHandle()
KEEP.utils.wrapTableWithBox()
KEEP.utils.aAnchorJump()
}