d0n9x1n/hexo-blog-encrypt/hexo-encrypt-toc

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Patches any Hexo theme so its table of contents works with hexo-blog-encrypt without leaking encrypted headings. Use when a theme shows an empty, hidden, or plaintext TOC on password-protected posts, when installing hexo-blog-encrypt on a theme with a TOC, or when asked to "fix TOC for encrypted posts". Do NOT use for themes with no TOC, or for changing the encryption plugin itself.

Install

npx vskill@latest install d0n9x1n/hexo-blog-encrypt/hexo-encrypt-toc

SKILL.md

Encrypted-post TOC for any Hexo theme

hexo-blog-encrypt replaces page.content with ciphertext in an after_post_render filter, sets page.encrypt = true, and keeps the plaintext in page.origin. After the reader decrypts, it swaps in the post as #hexo-blog-encrypt.hbe-decrypted-content and dispatches hexo-blog-decrypt on window.

That breaks theme TOCs in one of three ways:

SymptomCauseRisk
TOC empty or missingtoc(page.content) sees ciphertextCosmetic
TOC visible on the locked pagetoc(page.origin) renderedPlaintext headings published
TOC hidden, appears after decrypttoc(page.origin) with display:nonePlaintext headings published (view source)

The goal is always the same: no encrypted heading in the generated HTML, and a working TOC after decryption. This procedure works for any theme; the per-theme notes in Per-theme examples are examples, not a whitelist. Everything the procedure needs is in this one file: the builder script is Appendix A and the leak checker is Appendix B.

1. Locate the TOC

Run from the Hexo site root. Include theme packages installed from npm.

bash
THEME=$(sed -n 's/^theme:[[:space:]]*//p' _config.yml | tr -d "\"'")
grep -rnE "toc\(|\.origin|encrypt|tocbot|hexo-blog-decrypt|hbe|password" \
"themes/$THEME" "node_modules/hexo-theme-$THEME" 2>/dev/null \
| grep -v -E "/(node_modules|source/lib|vendor)/" | head -80

Also search component packages the theme depends on (for example Icarus renders its TOC from hexo-component-inferno).

Classify every hit:

  • Server TOC: a template calls toc(...) (ejs, njk, pug, jsx, swig).
  • Client TOC: JavaScript scans headings in the DOM (tocbot, a custom

querySelectorAll('h1,h2,…'), or a theme initTOC function).

  • Theme-native encryption: the theme has its own password handling or

bundles a copy of hexo-blog-encrypt. Resolve this first (see step 5).

Record the TOC container selector, the class passed to toc() (default toc), and the function that starts scrollspy or highlighting.

2. Remove every plaintext source

In each server template:

  • Delete any toc(page.origin), toc(post.origin), or equivalent.
  • Guard the remaining call with the encrypt flag and **still output the empty

container**, because many themes drop the sidebar panel when the TOC is empty:

ejs
<div id="toc" class="toc-article">
<% if (!page.encrypt) { %><%- toc(page.content, {class: 'toc'}) %><% } %>
</div>

Nunjucks: {% if not page.encrypt %}…{% endif %}. Pug: if !page.encrypt with the container outside the branch. JSX: pass '' instead of origin.

Also check head templates for open_graph() and meta descriptions, and search, feed, and sitemap generator configs for fields that read origin, raw, or _content.

3. Rebuild the TOC after decryption

Prefer the theme's own initializer. Otherwise use the generic builder.

Client TOC (tocbot style). Make sure the decrypted node is inside the scanner's content selector, then refresh:

html
<script>
window.addEventListener('hexo-blog-decrypt', function () {
var el = document.getElementById('hexo-blog-encrypt');
if (el) el.classList.add('markdown-body'); // match the theme's contentSelector
if (window.tocbot) tocbot.refresh();
});
</script>

Server TOC. Write the builder from Appendix A to themes/$THEME/source/js/hbe-toc.js (or the theme's equivalent asset folder) and load it on post pages after the theme's own scripts:

html
<script>
window.HBE_TOC = {
container: '#toc', // the empty container from step 2
className: 'toc', // the class the theme passed to toc()
onRender: function () { /* call the theme's scrollspy init here */ }
};
</script>
<script src="/js/hbe-toc.js"></script>

The builder emits the same markup as Hexo's toc() helper, so theme CSS applies. It reads heading text with textContent and never innerHTML. It runs on the decrypt event and once on load, which covers autoSave pages that decrypt from a cached key before the theme's script runs.

Event rules for any custom handler:

  • Do not depend on event.detail; some forks dispatch a plain Event.
  • Also check for #hexo-blog-encrypt.hbe-decrypted-content at load time.
  • On PJAX themes, re-run the handler after each page swap.

4. Verify

bash
hexo clean && hexo generate
# Write Appendix B to a temporary file first, e.g. /tmp/check-toc-leak.js
node /tmp/check-toc-leak.js --source source/_posts --public public

The checker reads every post with a front-matter password, extracts its Markdown headings (code fences skipped, headings shorter than 4 characters ignored), and searches all generated .html, .xml, .json, .txt, and .js files. Pass tag-encrypted posts explicitly with --post <file>. Add --body to also check every prose line of 40+ characters (search indexes, feeds, metadata). Exit code 1 means a leak; fix it before deploying. A heading that also appears in an unencrypted post is a false positive; confirm by viewing source.

Then in a browser:

  1. Open an encrypted post: the TOC is empty or hidden, and view-source shows no

headings.

  1. Enter the password: the TOC appears, links scroll to headings, scrollspy

highlights the current section.

  1. With autoSave: true, reload: the TOC appears without a password.
  2. Open an unencrypted post: the TOC is unchanged.

5. Pitfalls

  • Theme-native encryption (for example Keep's password filter, or

Redefine's bundled copy of hexo-blog-encrypt): use one system. With both, posts can be encrypted twice or the plugin's form never renders. Remove or disable the theme's copy, or don't install the plugin.

  • Hidden is not secret. display:none, hidden, and CSS-only tricks still

publish headings.

  • Stale hooks. Themes written for plugin v3 may wait for elements that v4

no longer outputs (for example hbe-prefix). Replace them with the hexo-blog-decrypt listener.

  • Theme updates overwrite edits in themes/<name>. Prefer the theme's

override mechanism (source/_data, injectors, custom layouts) or record the patch so it can be re-applied.

Report back

State the theme and version, the files changed, the checker's output, and the browser checks you ran. Say which checks you could not run.

Per-theme examples

Examples gathered from each theme's default branch on 2026-10-12. Line numbers drift; re-run the step 1 search before editing. Themes not listed here use the same procedure.

ThemeTOC sourceTypeLeaks today?Re-init after decrypt
NexTlayout/_macro/sidebar.njk toc(page.content, {class: 'nav'})ServerNo (empty)NexT.utils.registerSidebarTOC(), NexT.utils.activateSidebarPanel(0)
Butterflylayout/includes/widget/card_post_toc.pugServerYes, hidden toc(page.origin)Build into #card-toc .toc-content before the theme's own hexo-blog-decrypt listener runs forPostFn()
AnZhiYulayout/includes/widget/card_post_toc.pugServerYes, hidden toc(page.origin)Build into #card-toc .toc-content, then call the theme's scrollFnToDo()
Fluidlayout/_partials/post/toc.ejsClient (tocbot, .markdown-body)NoAdd markdown-body to #hexo-blog-encrypt, then Fluid.boot.refresh(). The theme's hbe-prefix hook is from plugin v3 and never fires
Icarushexo-component-inferno src/view/widget/toc.jsxServerYes, visible encrypt ? origin : contentOverride the widget to pass '' when encrypted; build ul.menu-list and reload /js/toc.js
Stellarlayout/_partial/widgets/toc.ejsServer + client rebuildNoOutput an empty nav.toc, then dispatch stellar:mdrender on document
Redefinelayout/pages/post/toc.ejsServerNoRemove the theme's bundled copy of the plugin first; fill .post-toc and dispatch redefine:page:refresh
Volantislayout/_widget/toc.ejsServerYes, hidden page.encrypt ? page.origin : page.contentRender an empty #toc-div .content, build, then listenSidebarTOC()
Materylayout/_partial/post-detail-toc.ejsClient (tocbot, #articleContent)Notocbot.refresh()
Keeplayout/_partial/toc.ejsServerNo, but has its own password encryptionChoose one encryption system; then fill .post-toc and call KEEP.initTOC()
Shokalayout/_macro/sidebar.njkServerNoOutput an empty .contents panel; build ol.toc; call sidebarTOC()
Cactuslayout/_partial/post/actions_desktop.ejs, actions_mobile.ejsServerNoAlways output an empty #toc; fill it. No scrollspy
Landscapenone—NoNothing to do
Notes
  • `class` option. NexT, Keep, and Redefine call toc() with

class: 'nav'. Set window.HBE_TOC.className = 'nav' so their CSS and scrollspy selectors match.

  • Closure-scoped initializers. Butterfly and AnZhiYu keep their scrollspy

inside main.js. Either build the TOC before their own decrypt listener runs, or expose the function with a one-line edit.

  • Hidden-origin pattern. Any template that renders origin inside a

hidden container publishes the headings. Replace it with an empty container and the builder; do not keep the hidden copy.

Appendix A: hbe-toc.js

Theme-agnostic TOC builder. Write this block verbatim to the theme's JavaScript folder.

`js
/* hbe-toc.js: theme-agnostic table of contents for hexo-blog-encrypt posts.
*
* Builds the TOC in the browser from the decrypted post, so the published
* HTML never contains the encrypted post's headings. Markup matches Hexo's
* `toc()` helper (`ol.<cls> > li.<cls>-item.<cls>-level-N > a.<cls>-link >
* span.<cls>-text`, nested `ol.<cls>-child`), so most theme CSS and scrollspy
* code works unchanged.
*
* Configure before this script loads (all optional):
* window.HBE_TOC = {
* container: '#toc', // element that receives the TOC
* className: 'toc', // Hexo toc() `class` option ('nav' for NexT, Keep, Redefine)
* maxDepth: 6, // deepest heading level to include
* listNumber: false, // add <span class="<cls>-number"> like toc({list_number: true})
* onRender: function (container, decrypted) {}, // re-run the theme's scrollspy here
* };
*/
(function () {
'use strict';
var opts = window.HBE_TOC || {};
var cls = opts.className || 'toc';
var maxDepth = opts.maxDepth || 6;
function slug(text, used) {
var base = text.trim().toLowerCase().replace(/[\s]+/g, '-').replace(/[^\p{L}\p{N}_-]/gu, '') || 'section';
var id = base;
for (var i = 1; used[id] || document.getElementById(id); i++) id = base + '-' + i;
used[id] = true;
return id;
}
function headings(root) {
var selector = [];
for (var level = 1; level <= maxDepth; level++) selector.push('h' + level);
return Array.prototype.filter.call(root.querySelectorAll(selector.join(',')), function (h) {
return h.textContent.trim() !== '';
});
}
function build(root) {
var used = {};
var list = document.createElement('ol');
list.className = cls;
// stack[i] = { level, item } for the open ancestors of the next heading.
var stack = [];
var counters = [];
headings(root).forEach(function (h) {
var level = Number(h.tagName.charAt(1));
if (!h.id) h.id = slug(h.textContent, used);
while (stack.length && stack[stack.length - 1].level >= level) stack.pop();
counters.length = stack.length + 1;
counters[stack.length] = (counters[stack.length] || 0) + 1;

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
1,052
Repository forks
108
7-day trend score
0

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/d0n9x1n/hexo-blog-encrypt/hexo-encrypt-toc/badge)](https://verified-skill.com/skills/d0n9x1n/hexo-blog-encrypt/hexo-encrypt-toc)

Something wrong with this skill or its page? Tell us.