├── .editorconfig
├── .eslintrc.js
├── .github
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE.md
└── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── LICENSE
├── ReadMe.md
├── ReadMe.zh.md
├── index.js
├── lib
├── hbe.blink.html
├── hbe.default.html
├── hbe.flip.html
├── hbe.js
├── hbe.shrink.html
├── hbe.style.css
├── hbe.surge.html
├── hbe.up.html
├── hbe.wave.html
└── hbe.xray.html
└── package.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 | end_of_line = lf
10 | # editorconfig-tools is unable to ignore longs strings or urls
11 | max_line_length = null
12 |
13 | [*.md]
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'env': {
3 | 'browser': true,
4 | 'commonjs': true,
5 | 'es6': true
6 | },
7 | 'extends': 'eslint:recommended',
8 | 'parserOptions': {
9 | 'ecmaVersion': 10,
10 | },
11 | };
12 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute to This Project
2 |
3 | ## **Did You Find a Bug?**
4 |
5 | - **Ensure the bug was not already reported** by searching on GitHub under [Issues].
6 | - If you're unable to find an open issue addressing the problem, [open a new one]. Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring.
7 |
8 | ## **Did You Write a Patch That Fixes a Bug?**
9 |
10 | - Open a new GitHub pull request with the patch.
11 | 1. Fork this project
12 | 2. Create your feature branch: `git checkout -b my-new-feature`
13 | 3. Commit your changes: `git commit -am 'Add some feature'`
14 | 4. Push to the branch: `git push origin my-new-feature`
15 | 5. Submit a pull request :tada:
16 | - Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable.
17 |
18 | ## **Do You Intend to Add a New Feature or Change an Existing One?**
19 |
20 | - Suggest your change as a new [issue] using the label `enhancement` **BEFORE** you start writing code.
21 |
22 | Thanks for contributing! :heart:
23 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # Issue
2 |
3 | ## Expected Behavior
4 |
5 | ## Actual Behavior
6 |
7 | ## Steps to Reproduce the Problem
8 |
9 | 1. _
10 | 2. _
11 | 3. _
12 |
13 | ## Specifications
14 |
15 | (The version of the project, operating system, hardware etc.)
16 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # PR
2 |
3 | Issue Fixed #
4 |
5 | ## Proposed Changes
6 |
7 | - _
8 | - _
9 | - _
10 |
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### *.iml template
3 | node_modules/
4 | package-lock.json
5 | ### .git template
6 |
7 |
8 | ### Tags template
9 | # Ignore tags created by etags, ctags, gtags (GNU global) and cscope
10 | TAGS
11 | !TAGS/
12 | tags
13 | !tags/
14 | gtags.files
15 | GTAGS
16 | GRTAGS
17 | GPATH
18 | cscope.files
19 | cscope.out
20 | cscope.in.out
21 | cscope.po.out
22 |
23 |
24 |
25 | ### Xcode template
26 | build/
27 | *.pbxuser
28 | !default.pbxuser
29 | *.mode1v3
30 | !default.mode1v3
31 | *.mode2v3
32 | !default.mode2v3
33 | *.perspectivev3
34 | !default.perspectivev3
35 | xcuserdata
36 | *.xccheckout
37 | *.moved-aside
38 | DerivedData
39 | *.xcuserstate
40 |
41 |
42 | ### Java template
43 | *.class
44 |
45 | # Mobile Tools for Java (J2ME)
46 | .mtj.tmp/
47 |
48 | # Package Files #
49 | *.jar
50 | *.war
51 | *.ear
52 |
53 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
54 | hs_err_pid*
55 |
56 |
57 | ### *.classpath template
58 |
59 |
60 | ### JetBrains template
61 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm
62 |
63 | *.iml
64 |
65 | ## Directory-based project format:
66 | .idea/
67 | # if you remove the above rule, at least ignore the following:
68 |
69 | # User-specific stuff:
70 | .idea/workspace.xml
71 | .idea/tasks.xml
72 | .idea/dictionaries
73 |
74 | # Sensitive or high-churn files:
75 | .idea/dataSources.ids
76 | .idea/dataSources.xml
77 | .idea/sqlDataSources.xml
78 | .idea/dynamic.xml
79 | .idea/uiDesigner.xml
80 |
81 | # Gradle:
82 | # .idea/gradle.xml
83 | # .idea/libraries
84 |
85 | # Mongo Explorer plugin:
86 | # .idea/mongoSettings.xml
87 |
88 | ## File-based project format:
89 | *.ipr
90 | *.iws
91 |
92 | ## Plugin-specific files:
93 |
94 | # IntelliJ
95 | out/
96 |
97 | # mpeltonen/sbt-idea plugin
98 | .idea_modules/
99 |
100 | # JIRA plugin
101 | atlassian-ide-plugin.xml
102 |
103 | # Crashlytics plugin (for Android Studio and IntelliJ)
104 | com_crashlytics_export_strings.xml
105 | crashlytics.properties
106 | crashlytics-build.properties
107 |
108 |
109 | ### Vim template
110 | [._]*.s[a-w][a-z]
111 | [._]s[a-w][a-z]
112 | *.un~
113 | Session.vim
114 | .netrwhist
115 | *~
116 |
117 |
118 | ### *.settings template
119 | *.settings
120 | *.settings/
121 |
122 | ### Maven template
123 | target/
124 | pom.xml.tag
125 | pom.xml.releaseBackup
126 | pom.xml.versionsBackup
127 | pom.xml.next
128 | release.properties
129 | dependency-reduced-pom.xml
130 |
131 |
132 | ### Linux template
133 | *~
134 |
135 | # KDE directory preferences
136 | .directory
137 |
138 | # Linux trash folder which might appear on any partition or disk
139 | .Trash-*
140 |
141 |
142 | ### OSX template
143 | .DS_Store
144 | .AppleDouble
145 | .LSOverride
146 |
147 | # Icon must end with two \r
148 | Icon
149 |
150 | # Thumbnails
151 | ._*
152 |
153 | # Files that might appear in the root of a volume
154 | .DocumentRevisions-V100
155 | .fseventsd
156 | .Spotlight-V100
157 | .TemporaryItems
158 | .Trashes
159 | .VolumeIcon.icns
160 |
161 | # Directories potentially created on remote AFP share
162 | .AppleDB
163 | .AppleDesktop
164 | Network Trash Folder
165 | Temporary Items
166 | .apdisk
167 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright © 2022 D0n9X1n
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the "Software"),
5 | to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense,
7 | and/or sell copies of the Software, and to permit persons to whom the
8 | Software is furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included
11 | in all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
14 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
15 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
16 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
18 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
19 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 |
21 |
--------------------------------------------------------------------------------
/ReadMe.md:
--------------------------------------------------------------------------------
1 | # hexo-blog-encrypt
2 |
3 | 
4 | [](https://scrutinizer-ci.com/g/MikeCoder/hexo-blog-encrypt/build-status/master)
5 | [](https://scrutinizer-ci.com/g/MikeCoder/hexo-blog-encrypt/?branch=master)
6 |
7 | [中文说明](./ReadMe.zh.md)
8 |
9 | ## What's this
10 |
11 | - ~~First of all, the **BEST** post encryption plugin in the universe for hexo.(But what about the other plugins?)~~
12 |
13 | - It is for those who write a post, but don't want everyone to read it. Thus, password is required in certain pages to access these encrypted posts.
14 |
15 | - Encryption is simple on wordpress, emlog or other blog systems, except hexo. :(
16 |
17 | - So it's "hexo-blog-encrypt"'s time.
18 |
19 | ## Features
20 |
21 | - Once you enter the correct password, you can get the access to encrypted posts, and the password is remembered locally. Press the button again, and the stored password will be erased. If there're scripts in the post, they will be executed once the post is decrypted.
22 |
23 | - Support preset tag-specified password.
24 |
25 | - All functions are provided by the native APIs. We use [Crypto](https://nodejs.org/dist/latest-v12.x/docs/api/crypto.html) in Node.js, and use [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) in Browsers.
26 |
27 | - [PBKDF2](https://tools.ietf.org/html/rfc2898), [SHA256](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf) is used to derive keys, We use [AES256-CBC](https://csrc.nist.gov/publications/detail/sp/800-38a/final) to encrypt and decrypt data, we also use [HMAC](https://csrc.nist.gov/csrc/media/publications/fips/198/1/final/documents/fips-198-1_final.pdf) to verify message authentication codes to make sure the posts are decrypted well and not modified.
28 |
29 | - Promise is widely used to make sure our main procedures are asynchronous, so that there is little chance for the process to be blocked, and the experience will be more fluent.
30 |
31 | - Template theme supported, you can use [`default`, `blink`, `flip`, `shrink`, `surge`, `up`, `wave`, `xray`] to set up your template theme, and [CHECK ONLINE](https://mhexo.github.io/tags/ThemeTests/).
32 |
33 | - Outdated browsers may not work well. In such case, please upgrade your browser.
34 |
35 | ## Online demo
36 |
37 | - See [Demo Page](https://mhexo.github.io/), **all passwords are `hello`**.
38 |
39 | ## Install
40 |
41 | - `npm install --save hexo-blog-encrypt`
42 |
43 | - or `yarn add hexo-blog-encrypt` (require [Yarn](https://yarnpkg.com/en/))
44 |
45 | ## Quick start
46 |
47 | - Add the "password" value to your post's front matter like:
48 |
49 | ```markdown
50 |
51 | ---
52 | title: Hello World
53 | date: 2016-03-30 21:18:02
54 | password: hello
55 | ---
56 |
57 | ```
58 |
59 | - Then use `hexo clean && hexo g && hexo s` to see your encrypted post locally.
60 |
61 | ## Password Priority
62 |
63 | post's front matter > encrypt tags
64 |
65 | ## Advanced settings
66 |
67 | ### in post's front matter
68 |
69 | ```markdown
70 |
71 | ---
72 | title: Hello World
73 | tags:
74 | - encryptAsDiary
75 | date: 2016-03-30 21:12:21
76 | password: mikemessi
77 | abstract: Here's something encrypted, password is required to continue reading.
78 | message: Hey, password is required here.
79 | wrong_pass_message: Oh, this is an invalid password. Check and try again, please.
80 | wrong_hash_message: Oh, these decrypted content cannot be verified, but you can still have a look.
81 | ---
82 |
83 | ```
84 |
85 | ### In `_config.yml`
86 |
87 | #### Example
88 |
89 | ```yaml
90 | # Security
91 | encrypt: # hexo-blog-encrypt
92 | abstract: Here's something encrypted, password is required to continue reading.
93 | message: Hey, password is required here.
94 | tags:
95 | - {name: encryptAsDiary, password: passwordA}
96 | - {name: encryptAsTips, password: passwordB}
97 | wrong_pass_message: Oh, this is an invalid password. Check and try again, please.
98 | wrong_hash_message: Oh, these decrypted content cannot be verified, but you can still have a look.
99 |
100 | ```
101 |
102 | #### To disable tag encryption
103 |
104 | Just set the `password` property in front matter to `""`.
105 |
106 | Example:
107 |
108 | ```
109 | ---
110 | title: Callback Test
111 | date: 2019-12-21 11:54:07
112 | tags:
113 | - A Tag should be encrypted
114 | password: ""
115 | ---
116 |
117 | Use a "" to disable tag encryption.
118 | ```
119 |
120 | ### Config priority
121 |
122 | post's front matter > `_config.yml` (in the root directory) > default
123 |
124 | ### About Callback
125 | In some blogs, some elements may not be displayed normally after decryption. This is a known issue. The current solution is to check the code in your blog to learn which functions are called when the onload event occurs.
126 | Then write these code at the end of your post. For example:
127 |
128 | ```
129 | ---
130 | title: Callback Test
131 | date: 2019-12-21 11:54:07
132 | tags:
133 | - Encrypted
134 | ---
135 |
136 | This is a blog to test Callback functions. You just need to add code at the end of your post as follows:
137 |
138 | It will be called after the blog is decrypted.
139 |
140 |
144 | ```
145 |
146 | Demo: [Callback Example](https://mhexo.github.io/2020/12/06/Callback-Test/).
147 |
148 | ### After Decrypt Event
149 | Thanks to @[f-dong](https://github.com/f-dong), we now will trigger a event named `hexo-blog-decrypt`, so you can add a call back to listen to that event.
150 |
151 | ```
152 | // trigger event
153 | var event = new Event('hexo-blog-decrypt');
154 | window.dispatchEvent(event);
155 | ```
156 |
157 | ### Encrypt TOC
158 |
159 | If you has a post with TOC, you should change the code of your template. Take the default theme 'landscape' as an example:
160 |
161 | + You should find the `article.ejs` file located at `hexo/themes/landscape/layout/_partial/article.ejs`.
162 | + Find the code like <% post.content %>, which is usually at line 30.
163 | + Replace the <% post.content %> with the following code block:
164 |
165 | ```
166 | <% if(post.toc == true){ %>
167 |
style="display:none" <% } %>>
168 | Index
169 | <% if (post.encrypt == true) { %>
170 | <%- toc(post.origin, {list_number: true}) %>
171 | <% } else { %>
172 | <%- toc(post.content, {list_number: true}) %>
173 | <% } %>
174 |
175 | <% } %>
176 | <%- post.content %>
177 | ```
178 |
179 | ### Disable logging
180 | If you want to disable the logging, you can add a silent property in `_config.yml` and set it to true.
181 |
182 | ```yaml
183 | # Security
184 | encrypt: # hexo-blog-encrypt
185 | silent: true
186 | ```
187 |
188 | This would disable the logging like `INFO hexo-blog-encrypt: encrypting "{Blog Name}" based on Tag: "EncryptedTag".`.
189 |
190 | ### Encrypt Theme
191 | Previously, we use `template` to let users modify their own themes. Turn out that it's not a simple way. So, we are introducing this feature here.
192 |
193 | You can simply use `theme` in `_config.yml` or in header like:
194 |
195 | #### In post's front matter
196 |
197 | ```markdown
198 | ---
199 | title: Theme test
200 | date: 2019-12-21 11:54:07
201 | tags:
202 | - A Tag should be encrypted
203 | theme: xray
204 | password: "hello"
205 | ---
206 | ```
207 |
208 | #### In `_config.yml`
209 |
210 | This would be a default one.
211 |
212 | ```yaml
213 | # Security
214 | encrypt: # hexo-blog-encrypt
215 | abstract: Here's something encrypted, password is required to continue reading.
216 | message: Hey, password is required here.
217 | tags:
218 | - {name: encryptAsDiary, password: passwordA}
219 | - {name: encryptAsTips, password: passwordB}
220 | theme: xray
221 | wrong_pass_message: Oh, this is an invalid password. Check and try again, please.
222 | wrong_hash_message: Oh, these decrypted content cannot be verified, but you can still have a look.
223 |
224 | ```
225 |
226 | Check them online, and PICK one:
227 |
228 | + [default](https://mhexo.github.io/2020/12/23/Theme-Test-Default/)
229 | + [blink](https://mhexo.github.io/2020/12/23/Theme-Test-Blink/)
230 | + [shrink](https://mhexo.github.io/2020/12/23/Theme-Test-Shrink/)
231 | + [flip](https://mhexo.github.io/2020/12/23/Theme-Test-Flip/)
232 | + [up](https://mhexo.github.io/2020/12/23/Theme-Test-Up/)
233 | + [surge](https://mhexo.github.io/2020/12/23/Theme-Test-Surge/)
234 | + [wave](https://mhexo.github.io/2020/12/23/Theme-Test-Wave/)
235 | + [xray](https://mhexo.github.io/2020/12/23/Theme-Test-Xray/)
236 |
237 |
238 | ## License
239 |
240 | See [LICENSE](./LICENSE) file.
241 |
242 | ## Thanks
243 |
244 | Collaborator - [xiazeyu](https://github.com/xiazeyu)
245 |
--------------------------------------------------------------------------------
/ReadMe.zh.md:
--------------------------------------------------------------------------------
1 | # hexo-blog-encrypt
2 |
3 | 
4 | [](https://scrutinizer-ci.com/g/MikeCoder/hexo-blog-encrypt/build-status/master)
5 | [](https://scrutinizer-ci.com/g/MikeCoder/hexo-blog-encrypt/?branch=master)
6 |
7 | #### 提 issue 之前,请务必提供复现方式,log,配置信息等必要信息。良好的 issue 可以节省双方的时间。
8 | *请严格按照模版要求,不明确的 issue 将直接关闭。*
9 |
10 | **参考 issue:**
11 |
12 | > + [Issue 83](https://github.com/MikeCoder/hexo-blog-encrypt/issues/83)
13 | > + [Issue 68](https://github.com/MikeCoder/hexo-blog-encrypt/issues/68)
14 |
15 | ## 这是个啥
16 |
17 | - ~~首先, 这是 Hexo 生态圈中 **最好的** 博客加密插件~~
18 |
19 | - 你可能需要写一些私密的博客, 通过密码验证的方式让人不能随意浏览.
20 |
21 | - 这在 wordpress, emlog 或是其他博客系统中都很容易实现, 然而 hexo 除外. :(
22 |
23 | - 为了解决这个问题, 让我们有请 "hexo-blog-encrypt".
24 |
25 | ## 特性
26 |
27 | - 一旦你输入了正确的密码, 它将会被存储在本地浏览器的 localStorage中. 按个按钮, 密码将会被清空. 若博客中有脚本, 它将会被正确地执行.
28 |
29 | - 支持按标签加密.
30 |
31 | - 所有的核心功能都是由原生的 API 所提供的. 在 Node.js中, 我们使用 [Crypto](https://nodejs.org/dist/latest-v12.x/docs/api/crypto.html). 在浏览器中, 我们使用 [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API).
32 |
33 | - [PBKDF2](https://tools.ietf.org/html/rfc2898), [SHA256](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf) 被用于分发密钥, [AES256-CBC](https://csrc.nist.gov/publications/detail/sp/800-38a/final) 被用于加解密, 我们还使用 [HMAC](https://csrc.nist.gov/csrc/media/publications/fips/198/1/final/documents/fips-198-1_final.pdf) 来验证密文的来源, 并确保其未被篡改.
34 |
35 | - 我们广泛地使用 Promise 来进行异步操作, 以此确保线程不被阻塞.
36 |
37 | - 加密页面多主题支持, 现在已经支持的主题有 [`default`, `xray`], 更多的主题正在开发中.
38 |
39 | - 过时的浏览器将不能正常显示, 因此, 请升级您的浏览器.
40 |
41 | ## 在线演示
42 |
43 | - 点击 [Demo Page](https://mhexo.github.io/), **所有的密码都是 `hello`**.
44 |
45 | ## 安装
46 |
47 | - `npm install --save hexo-blog-encrypt`
48 |
49 | - 或 `yarn add hexo-blog-encrypt` (需要) [Yarn](https://yarnpkg.com/en/))
50 |
51 | ## 快速使用
52 |
53 | - 将 "password" 字段添加到您文章信息头就像这样.
54 |
55 | ```markdown
56 |
57 | ---
58 | title: Hello World
59 | date: 2016-03-30 21:18:02
60 | password: hello
61 | ---
62 |
63 | ```
64 |
65 | - 再使用 `hexo clean && hexo g && hexo s` 在本地预览加密的文章.
66 |
67 | ## 设置优先级
68 |
69 | 文章信息头 > 按标签加密
70 |
71 | ## 高级设置
72 |
73 | ### 文章信息头
74 |
75 | ```markdown
76 |
77 | ---
78 | title: Hello World
79 | tags:
80 | - 作为日记加密
81 | date: 2016-03-30 21:12:21
82 | password: mikemessi
83 | abstract: 有东西被加密了, 请输入密码查看.
84 | message: 您好, 这里需要密码.
85 | wrong_pass_message: 抱歉, 这个密码看着不太对, 请再试试.
86 | wrong_hash_message: 抱歉, 这个文章不能被校验, 不过您还是能看看解密后的内容.
87 | ---
88 |
89 | ```
90 |
91 | ### `_config.yml`
92 |
93 | #### 示例
94 |
95 | ```yaml
96 |
97 | # Security
98 | encrypt: # hexo-blog-encrypt
99 | abstract: 有东西被加密了, 请输入密码查看.
100 | message: 您好, 这里需要密码.
101 | tags:
102 | - {name: tagName, password: 密码A}
103 | - {name: tagName, password: 密码B}
104 | wrong_pass_message: 抱歉, 这个密码看着不太对, 请再试试.
105 | wrong_hash_message: 抱歉, 这个文章不能被校验, 不过您还是能看看解密后的内容.
106 |
107 | ```
108 |
109 | #### 对博文禁用 Tag 加密
110 |
111 | 只需要将博文头部的 `password` 设置为 `""` 即可取消 Tag 加密.
112 |
113 | Example:
114 |
115 | ```
116 | ---
117 | title: Callback Test
118 | date: 2019-12-21 11:54:07
119 | tags:
120 | - A Tag should be encrypted
121 | password: ""
122 | ---
123 |
124 | Use a "" to diable tag encryption.
125 | ```
126 |
127 | ### 配置优先级
128 |
129 | 文章信息头 > `_config.yml` (站点根目录下的) > 默认配置
130 |
131 | ### 关于 Callback 函数
132 | 在部分博客中, 解密后部分元素可能无法正常显示或者表现, 这属于已知问题. 目前的解决办法是通过自行查阅自己的博客中的代码, 了解到在 onload 事件发生时调用了哪些函数, 并将这些函数挑选后写入到博客内容中. 如:
133 |
134 | ```
135 | ---
136 | title: Callback Test
137 | date: 2019-12-21 11:54:07
138 | tags:
139 | - Encrypted
140 | ---
141 |
142 | This is a blog to test Callback functions. You just need to add code at the last of your post like following:
143 |
144 | It will be called after the blog decrypted.
145 |
146 |
150 | ```
151 |
152 | 例子在: [Callback 例子](https://mhexo.github.io/2020/12/06/Callback-Test/).
153 |
154 | ### 解密后的触发事件
155 | 感谢 @[f-dong](https://github.com/f-dong), 我们现在会在解密完成后触发一个 `hexo-blog-decrypt` 事件, 你们可以编写 callback 来监听该事件.
156 |
157 | ```
158 | // trigger event
159 | var event = new Event('hexo-blog-decrypt');
160 | window.dispatchEvent(event);
161 | ```
162 |
163 | ### 对 TOC 进行加密
164 |
165 | 如果你有一篇文章使用了 TOC,你需要修改模板的部分代码。这里用 landscape 作为例子:
166 |
167 | + 你可以在 hexo/themes/landscape/layout/_partial/article.ejs 找到 article.ejs。
168 | + 然后找到 <% post.content %> 这段代码,通常在30行左右。
169 | + 使用如下的代码来替代它:
170 |
171 | ```
172 | <% if(post.toc == true){ %>
173 | style="display:none" <% } %>>
174 | Index
175 | <% if (post.encrypt == true) { %>
176 | <%- toc(post.origin, {list_number: true}) %>
177 | <% } else { %>
178 | <%- toc(post.content, {list_number: true}) %>
179 | <% } %>
180 |
181 | <% } %>
182 | <%- post.content %>
183 | ```
184 |
185 | ### 禁用 Log
186 | If you want to disable the logging, you can add a silent property in `_config.yml` and set it to true.
187 | 如果你想要禁止使用 Log, 你可以在 `_config.yml` 中增加一个 silent 属性, 并将其设置为 true.
188 |
189 | ```yaml
190 | # Security
191 | encrypt: # hexo-blog-encrypt
192 | silent: true
193 | ```
194 |
195 | 这样就会禁止如 `INFO hexo-blog-encrypt: encrypting "{Blog Name}" based on Tag: "EncryptedTag".` 的日志.
196 |
197 | ### 加密主题
198 |
199 | 之前, 我们尝试使用 `template` 关键字来让用户能修改自己的主题. 后来发现真不是一个好主意. 所以我们现在引入了主题: `theme` 关键字.
200 |
201 | 你可以简单的使用 `theme` 在 `_config.yml` 里或者文章头, 如下:
202 |
203 | ### 文章信息头
204 |
205 | ```markdown
206 |
207 | ---
208 | title: Hello World
209 | tags:
210 | - 作为日记加密
211 | date: 2016-03-30 21:12:21
212 | password: mikemessi
213 | abstract: 有东西被加密了, 请输入密码查看.
214 | message: 您好, 这里需要密码.
215 | theme: xray
216 | wrong_pass_message: 抱歉, 这个密码看着不太对, 请再试试.
217 | wrong_hash_message: 抱歉, 这个文章不能被校验, 不过您还是能看看解密后的内容.
218 | ---
219 |
220 | ```
221 |
222 | ### 在 `_config.yml`
223 |
224 | #### 示例
225 |
226 | ```yaml
227 |
228 | # Security
229 | encrypt: # hexo-blog-encrypt
230 | abstract: 有东西被加密了, 请输入密码查看.
231 | message: 您好, 这里需要密码.
232 | tags:
233 | - {name: tagName, password: 密码A}
234 | - {name: tagName, password: 密码B}
235 | theme: xray
236 | wrong_pass_message: 抱歉, 这个密码看着不太对, 请再试试.
237 | wrong_hash_message: 抱歉, 这个文章不能被校验, 不过您还是能看看解密后的内容.
238 |
239 | ```
240 |
241 | 你可以在线挑选你喜欢的主题,并应用到你的博客中:
242 |
243 | + [default](https://mhexo.github.io/2020/12/23/Theme-Test-Default/)
244 | + [blink](https://mhexo.github.io/2020/12/23/Theme-Test-Blink/)
245 | + [shrink](https://mhexo.github.io/2020/12/23/Theme-Test-Shrink/)
246 | + [flip](https://mhexo.github.io/2020/12/23/Theme-Test-Flip/)
247 | + [up](https://mhexo.github.io/2020/12/23/Theme-Test-Up/)
248 | + [surge](https://mhexo.github.io/2020/12/23/Theme-Test-Surge/)
249 | + [wave](https://mhexo.github.io/2020/12/23/Theme-Test-Wave/)
250 | + [xray](https://mhexo.github.io/2020/12/23/Theme-Test-Xray/)
251 |
252 |
253 | ## 许可
254 |
255 | 看看 [LICENSE](./LICENSE).
256 |
257 | ## 感谢
258 |
259 | Collaborator - [xiazeyu](https://github.com/xiazeyu)
260 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /* global hexo, __dirname */
2 |
3 | 'use strict';
4 |
5 | const crypto = require('crypto');
6 | const fs = require('fs');
7 | const path = require('path');
8 | const log = hexo.log;
9 |
10 | const defaultConfig = {
11 | 'abstract': 'Here\'s something encrypted, password is required to continue reading.',
12 | 'message': 'Hey, password is required here.',
13 | 'theme': 'default',
14 | 'wrong_pass_message': 'Oh, this is an invalid password. Check and try again, please.',
15 | 'wrong_hash_message': 'OOPS, these decrypted content may changed, but you can still have a look.',
16 | 'silent': false,
17 | };
18 |
19 | const keySalt = crypto.randomBytes(32);
20 | const ivSalt = crypto.randomBytes(32);
21 |
22 | // As we can't detect the wrong password with AES-CBC,
23 | // so adding an empty tag and check it when decrption.
24 | const knownPrefix = "";
25 |
26 | // disable log
27 | var silent = false;
28 | // use default theme
29 | var theme = 'default';
30 |
31 | hexo.extend.filter.register('after_post_render', (data) => {
32 | const tagEncryptPairs = [];
33 |
34 | let password = data.password;
35 | let tagUsed = false;
36 |
37 | // use a empty password to disable category encryption
38 | if (password === "") {
39 | return data;
40 | }
41 |
42 | if (hexo.config.encrypt === undefined) {
43 | hexo.config.encrypt = [];
44 | }
45 |
46 | if(('encrypt' in hexo.config) && ('tags' in hexo.config.encrypt)){
47 | hexo.config.encrypt.tags.forEach((tagObj) => {
48 | tagEncryptPairs[tagObj.name] = tagObj.password;
49 | });
50 | }
51 |
52 | if (data.tags) {
53 | data.tags.forEach((cTag) => {
54 | if (tagEncryptPairs.hasOwnProperty(cTag.name)) {
55 | tagUsed = password ? tagUsed : cTag.name;
56 | password = password || tagEncryptPairs[cTag.name];
57 | }
58 | });
59 | }
60 |
61 | if(password == undefined){
62 | return data;
63 | }
64 |
65 | password = password.toString();
66 |
67 | // make sure toc can work.
68 | data.origin = data.content;
69 |
70 | // Let's rock n roll
71 | const config = Object.assign(defaultConfig, hexo.config.encrypt, data);
72 | silent = config.silent;
73 | theme = config.theme.trim().toLowerCase();
74 |
75 | // deprecate the template keyword
76 | if (config.template) {
77 | dlog('warn', 'Looks like you use a deprecated property "template" to set up template, consider to use "theme"? See https://github.com/D0n9X1n/hexo-blog-encrypt#encrypt-theme');
78 | }
79 |
80 | // read theme from file
81 | let template = fs.readFileSync(path.resolve(__dirname, `./lib/hbe.${theme}.html`)).toString();
82 |
83 | if (tagUsed === false) {
84 | dlog('info', `hexo-blog-encrypt: encrypting "${data.title.trim()}" based on the password configured in Front-matter with theme: ${theme}.`);
85 | } else {
86 | dlog('info', `hexo-blog-encrypt: encrypting "${data.title.trim()}" based on Tag: "${tagUsed}" with theme ${theme}.`);
87 | }
88 |
89 | data.content = knownPrefix + data.content.trim();
90 | data.encrypt = true;
91 |
92 | const key = crypto.pbkdf2Sync(password, keySalt, 1024, 32, 'sha256');
93 | const iv = crypto.pbkdf2Sync(password, ivSalt, 512, 16, 'sha256');
94 |
95 | const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
96 | const hmac = crypto.createHmac('sha256', key);
97 |
98 | let encryptedData = cipher.update(data.content, 'utf8', 'hex');
99 | hmac.update(data.content, 'utf8');
100 | encryptedData += cipher.final('hex');
101 | const hmacDigest = hmac.digest('hex');
102 |
103 | data.content = template.replace(/{{hbeEncryptedData}}/g, encryptedData)
104 | .replace(/{{hbeHmacDigest}}/g, hmacDigest)
105 | .replace(/{{hbeWrongPassMessage}}/g, config.wrong_pass_message)
106 | .replace(/{{hbeWrongHashMessage}}/g, config.wrong_hash_message)
107 | .replace(/{{hbeMessage}}/g, config.message)
108 | .replace(/{{hbeKeySalt}}/g, keySalt.toString('hex'))
109 | .replace(/{{hbeIvSalt}}/g, ivSalt.toString('hex'));
110 | data.content += ``;
111 | data.excerpt = data.more = config.abstract;
112 |
113 | return data;
114 | }, 1000);
115 |
116 | hexo.extend.generator.register('hexo-blog-encrypt', () => [
117 | {
118 | 'data': () => fs.createReadStream(path.resolve(__dirname, `./lib/hbe.style.css`)),
119 | 'path': `css/hbe.style.css`,
120 | },
121 | {
122 | 'data': () => fs.createReadStream(path.resolve(__dirname, './lib/hbe.js')),
123 | 'path': 'lib/hbe.js',
124 | },
125 | ]);
126 |
127 | // log function
128 | function dlog(level, x) {
129 | switch (level) {
130 | case 'warn':
131 | log.warn(x);
132 | break;
133 |
134 | case 'info':
135 | default:
136 | if (silent) {
137 | return;
138 | }
139 |
140 | log.info(x);
141 | }
142 | }
143 |
144 | // Utils functions
145 | function textToArray(s) {
146 | var i = s.length;
147 | var n = 0;
148 | var ba = new Array()
149 |
150 | for (var j = 0; j < i;) {
151 | var c = s.codePointAt(j);
152 | if (c < 128) {
153 | ba[n++] = c;
154 | j++;
155 | } else if ((c > 127) && (c < 2048)) {
156 | ba[n++] = (c >> 6) | 192;
157 | ba[n++] = (c & 63) | 128;
158 | j++;
159 | } else if ((c > 2047) && (c < 65536)) {
160 | ba[n++] = (c >> 12) | 224;
161 | ba[n++] = ((c >> 6) & 63) | 128;
162 | ba[n++] = (c & 63) | 128;
163 | j++;
164 | } else {
165 | ba[n++] = (c >> 18) | 240;
166 | ba[n++] = ((c >> 12) & 63) | 128;
167 | ba[n++] = ((c >> 6) & 63) | 128;
168 | ba[n++] = (c & 63) | 128;
169 | j += 2;
170 | }
171 | }
172 |
173 | return new Uint8Array(ba);
174 | }
175 |
--------------------------------------------------------------------------------
/lib/hbe.blink.html:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/lib/hbe.default.html:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/lib/hbe.flip.html:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/lib/hbe.js:
--------------------------------------------------------------------------------
1 | (() => {
2 | 'use strict';
3 |
4 | const cryptoObj = window.crypto || window.msCrypto;
5 | const storage = window.localStorage;
6 |
7 | const storageName = 'hexo-blog-encrypt:#' + window.location.pathname;
8 |
9 | // As we can't detect the wrong password with AES-CBC,
10 | // so adding an empty div and check it when decrption.
11 | const knownPrefix = "";
12 |
13 | const mainElement = document.getElementById('hexo-blog-encrypt');
14 | const wrongPassMessage = mainElement.dataset['wpm'];
15 | const wrongHashMessage = mainElement.dataset['whm'];
16 | const dataElement = mainElement.getElementsByTagName('script')['hbeData'];
17 | const encryptedData = dataElement.innerText;
18 | const HmacDigist = dataElement.dataset['hmacdigest'];
19 | // If the plugin version is updated but the blog is not regenerated (e.g. caching), the legacy fixed salt value is used.
20 | const keySalt = dataElement.dataset['keysalt'] ? hexToArray(dataElement.dataset['keysalt']) : textToArray('hexo-blog-encrypt的作者们都是大帅比!');
21 | const ivSalt = dataElement.dataset['ivsalt'] ? hexToArray(dataElement.dataset['ivsalt']) : textToArray('hexo-blog-encrypt是地表最强Hexo加密插件!');
22 |
23 | function hexToArray(s) {
24 | return new Uint8Array(s.match(/[\da-f]{2}/gi).map((h => {
25 | return parseInt(h, 16);
26 | })));
27 | }
28 |
29 | function textToArray(s) {
30 | var i = s.length;
31 | var n = 0;
32 | var ba = new Array()
33 |
34 | for (var j = 0; j < i;) {
35 | var c = s.codePointAt(j);
36 | if (c < 128) {
37 | ba[n++] = c;
38 | j++;
39 | } else if ((c > 127) && (c < 2048)) {
40 | ba[n++] = (c >> 6) | 192;
41 | ba[n++] = (c & 63) | 128;
42 | j++;
43 | } else if ((c > 2047) && (c < 65536)) {
44 | ba[n++] = (c >> 12) | 224;
45 | ba[n++] = ((c >> 6) & 63) | 128;
46 | ba[n++] = (c & 63) | 128;
47 | j++;
48 | } else {
49 | ba[n++] = (c >> 18) | 240;
50 | ba[n++] = ((c >> 12) & 63) | 128;
51 | ba[n++] = ((c >> 6) & 63) | 128;
52 | ba[n++] = (c & 63) | 128;
53 | j += 2;
54 | }
55 | }
56 | return new Uint8Array(ba);
57 | }
58 |
59 | function arrayBufferToHex(arrayBuffer) {
60 | if (typeof arrayBuffer !== 'object' || arrayBuffer === null || typeof arrayBuffer.byteLength !== 'number') {
61 | throw new TypeError('Expected input to be an ArrayBuffer')
62 | }
63 |
64 | var view = new Uint8Array(arrayBuffer)
65 | var result = ''
66 | var value
67 |
68 | for (var i = 0; i < view.length; i++) {
69 | value = view[i].toString(16)
70 | result += (value.length === 1 ? '0' + value : value)
71 | }
72 |
73 | return result
74 | }
75 |
76 | async function getExecutableScript(oldElem) {
77 | let out = document.createElement('script');
78 | const attList = ['type', 'text', 'src', 'crossorigin', 'defer', 'referrerpolicy'];
79 | attList.forEach((att) => {
80 | if (oldElem[att])
81 | out[att] = oldElem[att];
82 | })
83 |
84 | return out;
85 | }
86 |
87 | async function convertHTMLToElement(content) {
88 | let out = document.createElement('div');
89 | out.innerHTML = content;
90 | out.querySelectorAll('script').forEach(async (elem) => {
91 | elem.replaceWith(await getExecutableScript(elem));
92 | });
93 |
94 | return out;
95 | }
96 |
97 | function getKeyMaterial(password) {
98 | let encoder = new TextEncoder();
99 | return cryptoObj.subtle.importKey(
100 | 'raw',
101 | encoder.encode(password),
102 | {
103 | 'name': 'PBKDF2',
104 | },
105 | false,
106 | [
107 | 'deriveKey',
108 | 'deriveBits',
109 | ]
110 | );
111 | }
112 |
113 | function getHmacKey(keyMaterial) {
114 | return cryptoObj.subtle.deriveKey({
115 | 'name': 'PBKDF2',
116 | 'hash': 'SHA-256',
117 | 'salt': keySalt.buffer,
118 | 'iterations': 1024
119 | }, keyMaterial, {
120 | 'name': 'HMAC',
121 | 'hash': 'SHA-256',
122 | 'length': 256,
123 | }, true, [
124 | 'verify',
125 | ]);
126 | }
127 |
128 | function getDecryptKey(keyMaterial) {
129 | return cryptoObj.subtle.deriveKey({
130 | 'name': 'PBKDF2',
131 | 'hash': 'SHA-256',
132 | 'salt': keySalt.buffer,
133 | 'iterations': 1024,
134 | }, keyMaterial, {
135 | 'name': 'AES-CBC',
136 | 'length': 256,
137 | }, true, [
138 | 'decrypt',
139 | ]);
140 | }
141 |
142 | function getIv(keyMaterial) {
143 | return cryptoObj.subtle.deriveBits({
144 | 'name': 'PBKDF2',
145 | 'hash': 'SHA-256',
146 | 'salt': ivSalt.buffer,
147 | 'iterations': 512,
148 | }, keyMaterial, 16 * 8);
149 | }
150 |
151 | async function verifyContent(key, content) {
152 | const encoder = new TextEncoder();
153 | const encoded = encoder.encode(content);
154 |
155 | let signature = hexToArray(HmacDigist);
156 |
157 | const result = await cryptoObj.subtle.verify({
158 | 'name': 'HMAC',
159 | 'hash': 'SHA-256',
160 | }, key, signature, encoded);
161 | console.log(`Verification result: ${result}`);
162 | if (!result) {
163 | alert(wrongHashMessage);
164 | console.log(`${wrongHashMessage}, got `, signature, ` but proved wrong.`);
165 | }
166 | return result;
167 | }
168 |
169 | async function decrypt(decryptKey, iv, hmacKey) {
170 | let typedArray = hexToArray(encryptedData);
171 |
172 | const result = await cryptoObj.subtle.decrypt({
173 | 'name': 'AES-CBC',
174 | 'iv': iv,
175 | }, decryptKey, typedArray.buffer).then(async (result) => {
176 | const decoder = new TextDecoder();
177 | const decoded = decoder.decode(result);
178 |
179 | // check the prefix, if not then we can sure here is wrong password.
180 | if (!decoded.startsWith(knownPrefix)) {
181 | throw "Decode successfully but not start with KnownPrefix.";
182 | }
183 |
184 | const hideButton = document.createElement('button');
185 | hideButton.textContent = 'Encrypt again';
186 | hideButton.type = 'button';
187 | hideButton.classList.add("hbe-button");
188 | hideButton.addEventListener('click', () => {
189 | window.localStorage.removeItem(storageName);
190 | window.location.reload();
191 | });
192 |
193 | document.getElementById('hexo-blog-encrypt').style.display = 'inline';
194 | document.getElementById('hexo-blog-encrypt').innerHTML = '';
195 | document.getElementById('hexo-blog-encrypt').appendChild(await convertHTMLToElement(decoded));
196 | document.getElementById('hexo-blog-encrypt').appendChild(hideButton);
197 |
198 | // support html5 lazyload functionality.
199 | document.querySelectorAll('img').forEach((elem) => {
200 | if (elem.getAttribute("data-src") && !elem.src) {
201 | elem.src = elem.getAttribute('data-src');
202 | }
203 | });
204 |
205 | // support theme-next refresh
206 | window.NexT && NexT.boot && typeof NexT.boot.refresh === 'function' && NexT.boot.refresh();
207 |
208 | // TOC part
209 | var tocDiv = document.getElementById("toc-div");
210 | if (tocDiv) {
211 | tocDiv.style.display = 'inline';
212 | }
213 |
214 | var tocDivs = document.getElementsByClassName('toc-div-class');
215 | if (tocDivs && tocDivs.length > 0) {
216 | for (var idx = 0; idx < tocDivs.length; idx++) {
217 | tocDivs[idx].style.display = 'inline';
218 | }
219 | }
220 |
221 | // trigger event
222 | var event = new Event('hexo-blog-decrypt');
223 | window.dispatchEvent(event);
224 |
225 | return await verifyContent(hmacKey, decoded);
226 | }).catch((e) => {
227 | alert(wrongPassMessage);
228 | console.log(e);
229 | return false;
230 | });
231 |
232 | return result;
233 |
234 | }
235 |
236 | function hbeLoader() {
237 |
238 | const oldStorageData = JSON.parse(storage.getItem(storageName));
239 |
240 | if (oldStorageData) {
241 | console.log(`Password got from localStorage(${storageName}): `, oldStorageData);
242 |
243 | const sIv = hexToArray(oldStorageData.iv).buffer;
244 | const sDk = oldStorageData.dk;
245 | const sHmk = oldStorageData.hmk;
246 |
247 | cryptoObj.subtle.importKey('jwk', sDk, {
248 | 'name': 'AES-CBC',
249 | 'length': 256,
250 | }, true, [
251 | 'decrypt',
252 | ]).then((dkCK) => {
253 | cryptoObj.subtle.importKey('jwk', sHmk, {
254 | 'name': 'HMAC',
255 | 'hash': 'SHA-256',
256 | 'length': 256,
257 | }, true, [
258 | 'verify',
259 | ]).then((hmkCK) => {
260 | decrypt(dkCK, sIv, hmkCK).then((result) => {
261 | if (!result) {
262 | storage.removeItem(storageName);
263 | }
264 | });
265 | });
266 | });
267 | }
268 |
269 | mainElement.addEventListener('keydown', async (event) => {
270 | if (event.isComposing || event.keyCode === 13) {
271 | const password = document.getElementById('hbePass').value;
272 | const keyMaterial = await getKeyMaterial(password);
273 | const hmacKey = await getHmacKey(keyMaterial);
274 | const decryptKey = await getDecryptKey(keyMaterial);
275 | const iv = await getIv(keyMaterial);
276 |
277 | decrypt(decryptKey, iv, hmacKey).then((result) => {
278 | console.log(`Decrypt result: ${result}`);
279 | if (result) {
280 | cryptoObj.subtle.exportKey('jwk', decryptKey).then((dk) => {
281 | cryptoObj.subtle.exportKey('jwk', hmacKey).then((hmk) => {
282 | const newStorageData = {
283 | 'dk': dk,
284 | 'iv': arrayBufferToHex(iv),
285 | 'hmk': hmk,
286 | };
287 | storage.setItem(storageName, JSON.stringify(newStorageData));
288 | });
289 | });
290 | }
291 | });
292 | }
293 | });
294 | }
295 |
296 | hbeLoader();
297 |
298 | })();
299 |
--------------------------------------------------------------------------------
/lib/hbe.shrink.html:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/lib/hbe.style.css:
--------------------------------------------------------------------------------
1 | .hbe,
2 | .hbe:after,
3 | .hbe:before {
4 | -webkit-box-sizing: border-box;
5 | -moz-box-sizing: border-box;
6 | box-sizing: border-box;
7 | }
8 |
9 | .hbe-container{
10 | margin: 0 auto;
11 | overflow: hidden;
12 | }
13 | .hbe-content {
14 | text-align: center;
15 | font-size: 150%;
16 | padding: 1em 0;
17 | }
18 |
19 | .hbe-input {
20 | position: relative;
21 | z-index: 1;
22 | display: inline-block;
23 | margin: 1em;
24 | width: 80%;
25 | min-width: 200px;
26 | vertical-align: top;
27 | }
28 |
29 | .hbe-input-field {
30 | line-height: normal;
31 | font-size: 100%;
32 | margin: 0;
33 | position: relative;
34 | display: block;
35 | float: right;
36 | padding: 0.8em;
37 | width: 60%;
38 | border: none;
39 | border-radius: 0;
40 | background: #f0f0f0;
41 | color: #aaa;
42 | font-weight: 400;
43 | font-family: "Avenir Next", "Helvetica Neue", Helvetica, Arial, sans-serif;
44 | -webkit-appearance: none; /* for box shadows to show on iOS */
45 | }
46 |
47 | .hbe-input-field:focus {
48 | outline: none;
49 | }
50 |
51 | .hbe-input-label {
52 | display: inline-block;
53 | float: right;
54 | padding: 0 1em;
55 | width: 40%;
56 | color: #696969;
57 | font-weight: bold;
58 | font-size: 70.25%;
59 | -webkit-font-smoothing: antialiased;
60 | -moz-osx-font-smoothing: grayscale;
61 | -webkit-touch-callout: none;
62 | -webkit-user-select: none;
63 | -khtml-user-select: none;
64 | -moz-user-select: none;
65 | -ms-user-select: none;
66 | user-select: none;
67 | }
68 |
69 | .hbe-input-label-content {
70 | position: relative;
71 | display: block;
72 | padding: 1.6em 0;
73 | width: 100%;
74 | }
75 |
76 | .hbe-graphic {
77 | position: absolute;
78 | top: 0;
79 | left: 0;
80 | fill: none;
81 | }
82 |
83 | /* hbe button in post page */
84 | .hbe-button {
85 | width: 130px;
86 | height: 40px;
87 | background: linear-gradient(to bottom, #4eb5e5 0%,#389ed5 100%); /* W3C */
88 | border: none;
89 | border-radius: 5px;
90 | position: relative;
91 | border-bottom: 4px solid #2b8bc6;
92 | color: #fbfbfb;
93 | font-weight: 600;
94 | font-family: 'Open Sans', sans-serif;
95 | text-shadow: 1px 1px 1px rgba(0,0,0,.4);
96 | font-size: 15px;
97 | text-align: left;
98 | text-indent: 5px;
99 | box-shadow: 0px 3px 0px 0px rgba(0,0,0,.2);
100 | cursor: pointer;
101 |
102 | display: block;
103 | margin: 0 auto;
104 | margin-bottom: 20px;
105 | }
106 |
107 | .hbe-button:active {
108 | box-shadow: 0px 2px 0px 0px rgba(0,0,0,.2);
109 | top: 1px;
110 | }
111 |
112 | .hbe-button:after {
113 | content: "";
114 | width: 0;
115 | height: 0;
116 | display: block;
117 | border-top: 20px solid #187dbc;
118 | border-bottom: 20px solid #187dbc;
119 | border-left: 16px solid transparent;
120 | border-right: 20px solid #187dbc;
121 | position: absolute;
122 | opacity: 0.6;
123 | right: 0;
124 | top: 0;
125 | border-radius: 0 5px 5px 0;
126 | }
127 | /* hbe button in post page */
128 |
129 | /* default theme {{{ */
130 | .hbe-input-default {
131 | overflow: hidden;
132 | }
133 |
134 | .hbe-input-field-default {
135 | width: 100%;
136 | background: transparent;
137 | padding: 0.5em;
138 | margin-bottom: 2em;
139 | color: #f9f7f6;
140 | z-index: 100;
141 | opacity: 0;
142 | }
143 |
144 | .hbe-input-label-default {
145 | width: 100%;
146 | position: absolute;
147 | text-align: left;
148 | padding: 0.5em 0;
149 | pointer-events: none;
150 | font-size: 1em;
151 | }
152 |
153 | .hbe-input-label-default::before,
154 | .hbe-input-label-default::after {
155 | content: '';
156 | position: absolute;
157 | width: 100%;
158 | left: 0;
159 | }
160 |
161 | .hbe-input-label-default::before {
162 | height: 100%;
163 | background: #666666;
164 | top: 0;
165 | -webkit-transform: translate3d(0, -100%, 0);
166 | transform: translate3d(0, -100%, 0);
167 | -webkit-transition: -webkit-transform 0.2s;
168 | transition: transform 0.2s;
169 | }
170 |
171 | .hbe-input-label-default::after {
172 | height: 2px;
173 | background: #666666;
174 | top: 100%;
175 | -webkit-transition: opacity 0.2s;
176 | transition: opacity 0.2s;
177 | }
178 |
179 | .hbe-input-label-content-default {
180 | padding: 0;
181 | -webkit-transform-origin: 0 0;
182 | transform-origin: 0 0;
183 | -webkit-transition: -webkit-transform 0.2s, color 0.2s;
184 | transition: transform 0.2s, color 0.2s;
185 | }
186 |
187 | .hbe-input-field-default:focus,
188 | .hbe-input--filled .hbe-input-field-default {
189 | opacity: 1;
190 | -webkit-transition: opacity 0s 0.2s;
191 | transition: opacity 0s 0.2s;
192 | }
193 |
194 | .hbe-input-label-default::before,
195 | .hbe-input-label-default::after,
196 | .hbe-input-label-content-default,
197 | .hbe-input-field-default:focus,
198 | .hbe-input--filled .hbe-input-field-default {
199 | -webkit-transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1);
200 | transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1);
201 | }
202 |
203 | .hbe-input-field-default:focus + .hbe-input-label-default::before,
204 | .hbe-input--filled .hbe-input-label-default::before {
205 | -webkit-transform: translate3d(0, 0, 0);
206 | transform: translate3d(0, 0, 0);
207 | }
208 |
209 | .hbe-input-field-default:focus + .hbe-input-label-default::after,
210 | .hbe-input--filled .hbe-input-label-default::after {
211 | opacity: 0;
212 | }
213 |
214 | .hbe-input-field-default:focus + .hbe-input-label-default .hbe-input-label-content-default,
215 | .hbe-input--filled .hbe-input-label-default .hbe-input-label-content-default {
216 | color: #555555;
217 | -webkit-transform: translate3d(0, 2.1em, 0) scale3d(0.65, 0.65, 1);
218 | transform: translate3d(0, 2.1em, 0) scale3d(0.65, 0.65, 1);
219 | }
220 | /* default theme }}} */
221 |
222 | /* up theme {{{ */
223 | .hbe-input-up {
224 | overflow: hidden;
225 | padding-top: 2em;
226 | }
227 |
228 | .hbe-input-field-up {
229 | width: 100%;
230 | background: transparent;
231 | opacity: 0;
232 | padding: 0.35em;
233 | z-index: 100;
234 | color: #837482;
235 | }
236 |
237 | .hbe-input-label-up {
238 | width: 100%;
239 | bottom: 0;
240 | position: absolute;
241 | pointer-events: none;
242 | text-align: left;
243 | color: #8E9191;
244 | padding: 0 0.5em;
245 | }
246 |
247 | .hbe-input-label-up::before {
248 | content: '';
249 | position: absolute;
250 | width: 100%;
251 | height: 4em;
252 | top: 100%;
253 | left: 0;
254 | background: #fff;
255 | border-top: 4px solid #9B9F9F;
256 | -webkit-transform: translate3d(0, -3px, 0);
257 | transform: translate3d(0, -3px, 0);
258 | -webkit-transition: -webkit-transform 0.4s;
259 | transition: transform 0.4s;
260 | -webkit-transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1);
261 | transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1);
262 | }
263 |
264 | .hbe-input-label-content-up {
265 | padding: 0.5em 0;
266 | -webkit-transform-origin: 0% 100%;
267 | transform-origin: 0% 100%;
268 | -webkit-transition: -webkit-transform 0.4s, color 0.4s;
269 | transition: transform 0.4s, color 0.4s;
270 | -webkit-transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1);
271 | transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1);
272 | }
273 |
274 | .hbe-input-field-up:focus,
275 | .input--filled .hbe-input-field-up {
276 | cursor: text;
277 | opacity: 1;
278 | -webkit-transition: opacity 0s 0.4s;
279 | transition: opacity 0s 0.4s;
280 | }
281 |
282 | .hbe-input-field-up:focus + .hbe-input-label-up::before,
283 | .input--filled .hbe-input-label-up::before {
284 | -webkit-transition-delay: 0.05s;
285 | transition-delay: 0.05s;
286 | -webkit-transform: translate3d(0, -3.3em, 0);
287 | transform: translate3d(0, -3.3em, 0);
288 | }
289 |
290 | .hbe-input-field-up:focus + .hbe-input-label-up .hbe-input-label-content-up,
291 | .input--filled .hbe-input-label-content-up {
292 | color: #6B6E6E;
293 | -webkit-transform: translate3d(0, -3.3em, 0) scale3d(0.81, 0.81, 1);
294 | transform: translate3d(0, -3.3em, 0) scale3d(0.81, 0.81, 1);
295 | }
296 | /* up theme }}} */
297 |
298 | /* wave theme {{{ */
299 | .hbe-input-wave {
300 | overflow: hidden;
301 | padding-top: 1em;
302 | }
303 |
304 | .hbe-input-field-wave {
305 | padding: 0.5em 0em 0.25em;
306 | width: 100%;
307 | background: transparent;
308 | color: #9da8b2;
309 | font-size: 1.25em;
310 | }
311 |
312 | .hbe-input-label-wave {
313 | position: absolute;
314 | top: 0.95em;
315 | font-size: 0.85em;
316 | left: 0;
317 | display: block;
318 | width: 100%;
319 | text-align: left;
320 | padding: 0em;
321 | pointer-events: none;
322 | -webkit-transform-origin: 0 0;
323 | transform-origin: 0 0;
324 | -webkit-transition: -webkit-transform 0.2s 0.15s, color 1s;
325 | transition: transform 0.2s 0.15s, color 1s;
326 | -webkit-transition-timing-function: ease-out;
327 | transition-timing-function: ease-out;
328 | }
329 |
330 | .hbe-graphic-wave {
331 | stroke: #92989e;
332 | pointer-events: none;
333 | -webkit-transition: -webkit-transform 0.7s, stroke 0.7s;
334 | transition: transform 0.7s, stroke 0.7s;
335 | -webkit-transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1);
336 | transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1);
337 | }
338 |
339 | .hbe-input-field-wave:focus + .hbe-input-label-wave,
340 | .input--filled .hbe-input-label-wave {
341 | color: #333;
342 | -webkit-transform: translate3d(0, -1.25em, 0) scale3d(0.75, 0.75, 1);
343 | transform: translate3d(0, -1.25em, 0) scale3d(0.75, 0.75, 1);
344 | }
345 |
346 | .hbe-input-field-wave:focus ~ .hbe-graphic-wave,
347 | .input--filled .graphic-wave {
348 | stroke: #333;
349 | -webkit-transform: translate3d(-66.6%, 0, 0);
350 | transform: translate3d(-66.6%, 0, 0);
351 | }
352 | /* wave theme }}} */
353 |
354 | /* flip theme {{{ */
355 | .hbe-input-field-flip {
356 | width: 100%;
357 | background-color: #d0d1d0;
358 | border: 2px solid transparent;
359 | -webkit-transition: background-color 0.25s, border-color 0.25s;
360 | transition: background-color 0.25s, border-color 0.25s;
361 | }
362 |
363 | .hbe-input-label-flip {
364 | width: 100%;
365 | text-align: left;
366 | position: absolute;
367 | bottom: 100%;
368 | pointer-events: none;
369 | overflow: hidden;
370 | padding: 0 1.25em;
371 | -webkit-transform: translate3d(0, 3em, 0);
372 | transform: translate3d(0, 3em, 0);
373 | -webkit-transition: -webkit-transform 0.25s;
374 | transition: transform 0.25s ;
375 | -webkit-transition-timing-function: ease-in-out;
376 | transition-timing-function: ease-in-out;
377 | }
378 |
379 | .hbe-input-label-content-flip {
380 | color: #8B8C8B;
381 | padding: 0.25em 0;
382 | -webkit-transition: -webkit-transform 0.25s;
383 | transition: transform 0.25s;
384 | -webkit-transition-timing-function: ease-in-out;
385 | transition-timing-function: ease-in-out;
386 | }
387 |
388 | .hbe-input-label-content-flip::after {
389 | content: attr(data-content);
390 | position: absolute;
391 | font-weight: 800;
392 | bottom: 100%;
393 | left: 0;
394 | height: 100%;
395 | width: 100%;
396 | color: #666666;
397 | padding: 0.25em 0;
398 | letter-spacing: 1px;
399 | font-size: 1em;
400 | }
401 |
402 | .hbe-input-field-flip:focus + .hbe-input-label-flip,
403 | .input--filled .hbe-input-label-flip {
404 | -webkit-transform: translate3d(0, 0, 0);
405 | transform: translate3d(0, 0, 0);
406 | }
407 |
408 | .hbe-input-field-flip:focus + .hbe-input-label-flip .hbe-input-label-content-flip,
409 | .input--filled .hbe-input-label-content-flip {
410 | -webkit-transform: translate3d(0, 100%, 0);
411 | transform: translate3d(0, 100%, 0);
412 | }
413 |
414 | .hbe-input-field-flip:focus + .hbe-input-field-flip,
415 | .input--filled .hbe-input-field-flip {
416 | background-color: transparent;
417 | border-color: #666666;
418 | }
419 | /* flip theme }}} */
420 |
421 | /* xray theme {{{ */
422 | .hbe-input-xray {
423 | overflow: hidden;
424 | padding-bottom: 2.5em;
425 | }
426 |
427 | .hbe-input-field-xray {
428 | padding: 0;
429 | margin-top: 1.2em;
430 | width: 100%;
431 | background: transparent;
432 | color: #84AF9B ;
433 | font-size: 1.55em;
434 | }
435 |
436 | .hbe-input-label-xray {
437 | position: absolute;
438 | top: 2em;
439 | left: 0;
440 | display: block;
441 | width: 100%;
442 | text-align: left;
443 | padding: 0em;
444 | letter-spacing: 1px;
445 | color: #84AF9B ;
446 | pointer-events: none;
447 | -webkit-transform-origin: 0 0;
448 | transform-origin: 0 0;
449 | -webkit-transition: -webkit-transform 0.2s 0.1s, color 0.3s;
450 | transition: transform 0.2s 0.1s, color 0.3s;
451 | -webkit-transition-timing-function: ease-out;
452 | transition-timing-function: ease-out;
453 | }
454 |
455 | .hbe-graphic-xray {
456 | stroke: #84AF9B ;
457 | pointer-events: none;
458 | stroke-width: 2px;
459 | top: 1.25em;
460 | bottom: 0px;
461 | height: 3.275em;
462 | -webkit-transition: -webkit-transform 0.7s, stroke 0.7s;
463 | transition: transform 0.7s, stroke 0.7s;
464 | -webkit-transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1);
465 | transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1);
466 | }
467 |
468 | .hbe-input-field-xray:focus + .hbe-input-label-xray,
469 | .input--filled .hbe-input-label-xray {
470 | color: #84AF9B ;
471 | -webkit-transform: translate3d(0, 3.5em, 0) scale3d(0.85, 0.85, 1);
472 | transform: translate3d(0, 3.5em, 0) scale3d(0.85, 0.85, 1);
473 | }
474 |
475 | .hbe-input-field-xray:focus ~ .hbe-graphic-xray,
476 | .input--filled .graphic-xray {
477 | stroke: #84AF9B ;
478 | -webkit-transform: translate3d(-66.6%, 0, 0);
479 | transform: translate3d(-66.6%, 0, 0);
480 | }
481 | /* xray theme }}} */
482 |
483 | /* blink theme {{{ */
484 | .hbe-input-blink {
485 | padding-top: 1em;
486 | }
487 |
488 | .hbe-input-field-blink {
489 | width: 100%;
490 | padding: 0.8em 0.5em;
491 | background: transparent;
492 | border: 2px solid;
493 | color: #8781bd;
494 | -webkit-transition: border-color 0.25s;
495 | transition: border-color 0.25s;
496 | }
497 |
498 | .hbe-input-label-blink {
499 | width: 100%;
500 | position: absolute;
501 | top: 0;
502 | text-align: left;
503 | overflow: hidden;
504 | padding: 0;
505 | pointer-events: none;
506 | -webkit-transform: translate3d(0, 3em, 0);
507 | transform: translate3d(0, 3em, 0);
508 | }
509 |
510 | .hbe-input-label-content-blink {
511 | padding: 0 1em;
512 | font-weight: 400;
513 | color: #b5b5b5;
514 | }
515 |
516 | .hbe-input-label-content-blink::after {
517 | content: attr(data-content);
518 | position: absolute;
519 | top: -200%;
520 | left: 0;
521 | color: #8781bd ;
522 | font-weight: 800;
523 | }
524 |
525 | .hbe-input-field-blink:focus,
526 | .input--filled .hbe-input-field-blink {
527 | border-color: #8781bd ;
528 | }
529 |
530 | .hbe-input-field-blink:focus + .hbe-input-label-blink,
531 | .input--filled .hbe-input-label-blink {
532 | -webkit-animation: anim-blink-1 0.25s forwards;
533 | animation: anim-blink-1 0.25s forwards;
534 | }
535 |
536 | .hbe-input-field-blink:focus + .hbe-input-label-blink .hbe-input-label-content-blink,
537 | .input--filled .hbe-input-label-content-blink {
538 | -webkit-animation: anim-blink-2 0.25s forwards ease-in;
539 | animation: anim-blink-2 0.25s forwards ease-in;
540 | }
541 |
542 | @-webkit-keyframes anim-blink-1 {
543 | 0%, 70% {
544 | -webkit-transform: translate3d(0, 3em, 0);
545 | transform: translate3d(0, 3em, 0);
546 | }
547 | 71%, 100% {
548 | -webkit-transform: translate3d(0, 0, 0);
549 | transform: translate3d(0, 0, 0);
550 | }
551 | }
552 |
553 | @-webkit-keyframes anim-blink-2 {
554 | 0% {
555 | -webkit-transform: translate3d(0, 0, 0);
556 | transform: translate3d(0, 0, 0);
557 | }
558 | 70%, 71% {
559 | -webkit-transform: translate3d(0, 125%, 0);
560 | transform: translate3d(0, 125%, 0);
561 | opacity: 0;
562 | -webkit-animation-timing-function: ease-out;
563 | }
564 | 100% {
565 | color: transparent;
566 | -webkit-transform: translate3d(0, 200%, 0);
567 | transform: translate3d(0, 200%, 0);
568 | }
569 | }
570 |
571 | @keyframes anim-blink-1 {
572 | 0%, 70% {
573 | -webkit-transform: translate3d(0, 3em, 0);
574 | transform: translate3d(0, 3em, 0);
575 | }
576 | 71%, 100% {
577 | -webkit-transform: translate3d(0, 0, 0);
578 | transform: translate3d(0, 0, 0);
579 | }
580 | }
581 |
582 | @keyframes anim-blink-2 {
583 | 0% {
584 | -webkit-transform: translate3d(0, 0, 0);
585 | transform: translate3d(0, 0, 0);
586 | }
587 | 70%, 71% {
588 | -webkit-transform: translate3d(0, 125%, 0);
589 | transform: translate3d(0, 125%, 0);
590 | opacity: 0;
591 | -webkit-animation-timing-function: ease-out;
592 | }
593 | 100% {
594 | color: transparent;
595 | -webkit-transform: translate3d(0, 200%, 0);
596 | transform: translate3d(0, 200%, 0);
597 | }
598 | }
599 | /* blink theme }}} */
600 |
601 | /* surge theme {{{ */
602 | .hbe-input-surge {
603 | overflow: hidden;
604 | padding-bottom: 1em;
605 | }
606 |
607 | .hbe-input-field-surge {
608 | padding: 0.25em 0.5em;
609 | margin-top: 1.25em;
610 | width: 100%;
611 | background: transparent;
612 | color: #D0D0D0;
613 | font-size: 1.55em;
614 | opacity: 0;
615 | }
616 |
617 | .hbe-input-label-surge {
618 | width: 100%;
619 | text-align: left;
620 | position: absolute;
621 | top: 1em;
622 | pointer-events: none;
623 | overflow: hidden;
624 | padding: 0 0.25em;
625 | -webkit-transform: translate3d(1em, 2.75em, 0);
626 | transform: translate3d(1em, 2.75em, 0);
627 | -webkit-transition: -webkit-transform 0.3s;
628 | transition: transform 0.3s;
629 | }
630 |
631 | .hbe-input-label-content-surge {
632 | color: #A4A5A6;
633 | padding: 0.4em 0 0.25em;
634 | -webkit-transition: -webkit-transform 0.3s;
635 | transition: transform 0.3s;
636 | }
637 |
638 | .hbe-input-label-content-surge::after {
639 | content: attr(data-content);
640 | position: absolute;
641 | font-weight: 800;
642 | top: 100%;
643 | left: 0;
644 | height: 100%;
645 | width: 100%;
646 | color: #2C3E50;
647 | padding: 0.25em 0;
648 | letter-spacing: 1px;
649 | font-size: 0.85em;
650 | }
651 |
652 | .hbe-graphic-surge {
653 | fill: #2C3E50;
654 | pointer-events: none;
655 | top: 1em;
656 | bottom: 0px;
657 | height: 4.5em;
658 | z-index: -1;
659 | -webkit-transition: -webkit-transform 0.7s, fill 0.7s;
660 | transition: transform 0.7s, fill 0.7s;
661 | -webkit-transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1);
662 | transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1);
663 | }
664 |
665 | .hbe-input-field-surge:focus,
666 | .input--filled .hbe-input-field-surge {
667 | -webkit-transition: opacity 0s 0.35s;
668 | transition: opacity 0s 0.35s;
669 | opacity: 1;
670 | }
671 |
672 | .hbe-input-field-surge:focus + .hbe-input-label-surge,
673 | .input--filled .hbe-input-label-surge {
674 | -webkit-transition-delay: 0.15s;
675 | transition-delay: 0.15s;
676 | -webkit-transform: translate3d(0, 0, 0);
677 | transform: translate3d(0, 0, 0);
678 | }
679 |
680 | .hbe-input-field-surge:focus + .hbe-input-label-surge .hbe-input-label-content-surge,
681 | .input--filled .hbe-input-label-content-surge {
682 | -webkit-transition-delay: 0.15s;
683 | transition-delay: 0.15s;
684 | -webkit-transform: translate3d(0, -100%, 0);
685 | transform: translate3d(0, -100%, 0);
686 | }
687 |
688 | .hbe-input-field-surge:focus ~ .hbe-graphic-surge,
689 | .input--filled .graphic-surge {
690 | fill: #2C3E50;
691 | -webkit-transform: translate3d(-66.6%, 0, 0);
692 | transform: translate3d(-66.6%, 0, 0);
693 | }
694 | /* surge theme }}} */
695 |
696 | /* shrink theme {{{ */
697 | .hbe-input-field-shrink {
698 | width: 100%;
699 | background: transparent;
700 | padding: 0.5em 0;
701 | margin-bottom: 2em;
702 | color: #2C3E50;
703 | }
704 |
705 | .hbe-input-label-shrink {
706 | width: 100%;
707 | position: absolute;
708 | text-align: left;
709 | font-size: 1em;
710 | padding: 10px 0 5px;
711 | pointer-events: none;
712 | }
713 |
714 | .hbe-input-label-shrink::after {
715 | content: '';
716 | position: absolute;
717 | width: 100%;
718 | height: 7px;
719 | background: #B7C3AC;
720 | left: 0;
721 | top: 100%;
722 | -webkit-transform-origin: 50% 100%;
723 | transform-origin: 50% 100%;
724 | -webkit-transition: -webkit-transform 0.3s, background-color 0.3s;
725 | transition: transform 0.3s, background-color 0.3s;
726 | }
727 |
728 | .hbe-input-label-content-shrink {
729 | padding: 0;
730 | -webkit-transform-origin: 0 0;
731 | transform-origin: 0 0;
732 | -webkit-transition: -webkit-transform 0.3s, color 0.3s;
733 | transition: transform 0.3s, color 0.3s;
734 | }
735 |
736 | .hbe-input-field-shrink:focus + .hbe-input-label-shrink::after,
737 | .input--filled .hbe-input-label-shrink::after {
738 | background: #84AF9B;
739 | -webkit-transform: scale3d(1, 0.25, 1);
740 | transform: scale3d(1, 0.25, 1);
741 | }
742 |
743 | .hbe-input-field-shrink:focus + .hbe-input-label-shrink .hbe-input-label-content-shrink,
744 | .input--filled .hbe-input-label-shrink .hbe-input-label-content-shrink {
745 | color: #84AF9B;
746 | -webkit-transform: translate3d(0, 2em, 0) scale3d(0.655, 0.655, 1);
747 | transform: translate3d(0, 2em, 0) scale3d(0.655, 0.655, 1);
748 | }
749 | /* shrink theme }}} */
750 |
--------------------------------------------------------------------------------
/lib/hbe.surge.html:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/lib/hbe.up.html:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/lib/hbe.wave.html:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/lib/hbe.xray.html:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": {
3 | "name": "D0n9X1n"
4 | },
5 | "bugs": {
6 | "url": "https://github.com/D0n9X1n/hexo-blog-encrypt/issues"
7 | },
8 | "description": "Yet, just another blog encrypt plugin for hexo.",
9 | "homepage": "https://github.com/D0n9X1n/hexo-blog-encrypt#readme",
10 | "keywords": [
11 | "encrypt",
12 | "blog",
13 | "hexo"
14 | ],
15 | "license": "MIT",
16 | "main": "index.js",
17 | "maintainers": [
18 | {
19 | "email": "D0n9X1n@outlook.com",
20 | "name": "D0n9X1n"
21 | }
22 | ],
23 | "name": "hexo-blog-encrypt",
24 | "repository": {
25 | "type": "git",
26 | "url": "git+ssh://git@github.com/D0n9X1n/hexo-blog-encrypt.git"
27 | },
28 | "scripts": {
29 | "test": "echo \"Error: no test specified\" && exit 1",
30 | "lint": "eslint --ext .js ./"
31 | },
32 | "version": "3.1.9",
33 | "devDependencies": {
34 | "eslint": "^6.2.2"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------