Loading...

Loading...

Layout & Pages

This page covers the layout & pages features of the matery theme: post layout features, sidebar widgets, responsive breakpoints, galleries, search, comments, dark mode, encryption, RSS/sitemap and PWA. Each feature lists its config location and how to use it.

Config lives in two places: theme config themes/matery/_config.yml (layout/appearance/interaction) and site config _config.yml (plugins/generators), marked per item below. Note: in this project the theme config file is overwritten by the userConfig/_config.tmp.yml template during CI builds — sync your changes to the template (see the Installation chapter).

Layout Core & Components

1. Post Layout Features

1.1 Prev/Next Navigation

Purpose: prev/next post cards at the bottom of a post, auto-picked by date order to guide continuous reading.

Enable (theme config, inside the post block):

post:
  prev_next:
    enable: true

Effect: two cards at the end of the post showing the adjacent posts’ titles and thumbnails, with a gradient background on hover.

Purpose: show copyright and reprint license at the bottom of a post, noting the author, original link and license.

Enable (theme config, inside the post block):

post:
  copyright:
    enable: true          # show copyright notice
    license: 'cc_by_nc_sa'  # default reprint rule (sub-key of copyright, CC BY-NC-SA 4.0)

The default license applies site-wide; a single post can override it with reprintPolicy in front-matter:

reprintPolicy: cc_by_nc   # this post: Attribution-NonCommercial

Available rules (9 total): cc_by, cc_by_nd, cc_by_sa, cc_by_nc, cc_by_nc_nd, cc_by_nc_sa, cc0, noreprint, pay.

Effect: a grey copyright card at the bottom of the post with author, link and license rows.

1.3 Outdated Content Notice

Purpose: show a “content may be outdated” notice at the top of old posts, graded by days (warning/error styles).

Enable (theme config, inside the post block; note the current default is enable: false):

post:
  outdate:
    enable: true      # set to true to enable
    warning_day: 365  # show warning-style note after 365 days
    error_day: 3650   # show error-style note after 3650 days

Effect: an outdated notice note at the top of the post, more prominent the older the post.

1.4 TOC

Purpose: chapter navigation in the post sidebar; click to jump to the heading, current chapter highlighted while scrolling.

Enable (theme config, top-level toc section):

toc:
  enable: true                        # global switch
  headingSelector: "h2,h3,h4,h5,h6"   # heading levels included in the TOC
  collapseDepth: 0                    # collapse depth, 0 = all collapsed

Disable for a single post: add toc: false in front-matter.

Effect: sidebar TOC tree highlights on scroll on desktop; collapsed on mobile.

1.5 Advertisement Slots

Purpose: reserved ad card slots in the post sidebar (same area as TOC); multiple entries, freely toggleable.

Config (theme config, inside the post block; array, add/remove freely):

post:
  advertisements:
    - id: "ad-1"
      text: "Recommended cheap VPS provider"
      link: "https://example.com/your-aff-link"
      image: "/medias_webp/cover/hexo.webp"   # optional: card image
      background: "#6670ff"
      background_dark: "#3a4399"
      colspan: 1     # card column width
      enable: true   # false hides it

Effect: colored ad cards below the sidebar TOC, supporting light/dark background pairs.

2. Page-level Widgets

The following pages are rendered automatically by the theme generators — no config needed, they update with content:

PageWidgetDescription
/categories/Category radarcanvas radar chart showing post count distribution per category
/tags/Tag cloud + word cloudchip-style tag list; canvas word cloud with font size ∝ post count
/archives/Archive timelinevertical year/month timeline, dots + collapsible years
/about/Site statistics chartsmultiple canvas charts (posts/categories/tags/timeline etc.)
/about/Site statistics numberspost/category/tag counts (_partial/post/post-statis, auto-rendered)

3. Responsive Breakpoints

Config (themes/matery/source/css/_variables/base.styl, Stylus variables):

$breakpoint-sm  = 368px   // extra small (phone portrait)
$breakpoint-md  = 768px   // tablet / phone landscape
$breakpoint-lg  = 992px   // desktop
$breakpoint-xl  = 1200px  // large desktop
$breakpoint-fun = 1400px  // effects enable threshold (large screens)

Layout behavior at each breakpoint:

BreakpointLayout
< 768pxnav collapses to hamburger menu, content single column, sidebar hidden
768–992pxtablet layout, sidebar collapsed
> 992pxdesktop multi-column (content + sidebar + TOC)
> 1400pxeffects enabled (sakura/ripples etc.)

The layout adapts via the Materialize grid — no manual maintenance needed.

Content Width Standardization (2026-09-07)

“Proportion + cap”: content area width: 90% + max-width tiers (readability caps).

Width tiers (_variables/base.styl, $content-max-* / $content-wide-max-*):

BreakpointStandard (single-column)Wide (multi-column)
FHD 993–1920px1125px1250px
2K 1921–2560px1525px1750px
4K 2561px+1525px (cap)2250px

Selection rule (mixin content-container(wide)):

  • Single-column (no sidebar): content-container() → standard
  • Multi-column (sidebar/TOC present): content-container(true) → wide
  • Layouts must not hardcode max-width (always use the mixin)

wiki (multi-column) 4K content column grows from 899 to 1500px (+67%). Tested by tools/tests/content-width.test.js (L2m).

4. Galleries

Data source (source/_data/galleries.yml, single source):

2020:
  cover: /medias_webp/images/01.webp
  description: New Year 2020
  photos:
    - url: /medias_webp/images/01.webp      # form 1: full object (url/title/desc)
      title: "New Year 2020"
    - /medias_webp/images/02.webp           # form 2: path only
    - dir: /medias_webp/featureimages/      # form 3: directory, auto-scan all images
      title_prefix: "Image"

Create a gallery:

hexo new gallery <name>

Generates source/galleries/<name>/index.md (front-matter only needs title + layout: gallery). Add password for encrypted galleries — visitors must decrypt to enter.

Effect: /galleries/ listing cards + covers; lightGallery lightbox on inner pages, image titles/descriptions shown as captions.

Feature Integration

Config (theme config, search block, three engines):

search:
  algolia:
    enable: true        # Algolia engine (applicationID/apiKey/indexName in theme config)
    indexName: "blog"
  local:
    enable: true        # local search: generator outputs /search.json at hexo generate
    path: /search.json
    field: post         # search scope: post | page | all
    content: true       # index body content
  pagefind:
    enable: true        # Pagefind static index (client-side search after build)
    output_subdir: pagefind

Effect: search box in the navbar with real-time results popup; /search.json is the local search data source; encrypted posts are automatically excluded from the index.

5.1 Three-Engine Config Keys and Default Engine

The authoritative key names come from the search block in themes/matery/_config.yml (the template userConfig/_config.tmp.yml is the same source):

EngineConfig blockKey paramsNotes
localsearch.local.*enable / path / field / content / format: striptags / preload / trigger / top_n_per_articlethe theme generator outputs search.json at build time; preload/trigger are not yet wired up, so the index is in practice always fetched when the search popup first opens
Algoliasearch.algolia.*enable / applicationID / apiKey / indexName / content_length / excerpt_length / batch_size / hits.per_page / labelsapiKey is the front-end search key (safe to expose); the write key lives in deploy.algolia_write_key (read only during build)
Pagefindsearch.pagefind.*enable / output_subdir: pagefindscans public/ at build time to produce a static index; fully client-side, no external service
  • search.default (value algolia / local / pagefind) selects the default engine; if the configured default is not enabled the front end auto-degrades to the first enabled engine and logs a console.warn.
  • The Algolia client (algoliasearch) is lazy-loaded: it is injected only when the search popup opens, not preloaded site-wide.

5.2 In-Page Engine Switching and Keyboard Shortcuts

(Source: themes/matery/layout/_partial/common/search.ejs, _partial/script/search.ejs)

  • The search popup’s left-side engine icon #searchEngineBtn opens a dropdown #searchEngineMenu to switch between local / algolia / pagefind; the dropdown button auto-hides when only one engine is enabled.
  • Results render in real time for the current engine; switching engines triggers an immediate re-search with the current input value; the active engine is persisted in localStorage (key search_engine).
  • Ctrl+K / Cmd+K opens the search popup, Esc closes the menu; the popup uses the native popover and light-dismisses on outside clicks.
  • IME compatibility: input events skip IME composition mid-states; compositionend falls back to searching with the full value (shared by all three engines).

5.3 Multilingual Filtering and Unified Highlighting

(Source: themes/matery/source/js/search.js)

  • Multilingual filtering: all three engines share applyLangFilter() — results are grouped by path language prefix, and the group belonging to the user’s preferred language (localStorage key wiki_lang, language list from window.MATERY_LANG.langs) is returned first; missing translations fall back to the default language (default_lang); duplicates within a group are removed.
  • Unified highlighting: all three engines share the base class _highlight() — keywords are escaped then wrapped in paired <mark class="search-keyword"> tags (prevents XSS / unclosed tags); highlight HTML returned by Algolia is stripped by _safeHighlight() and re-highlighted locally.
  • Jump-to-page highlighting: result links append ?highlight=<keyword>; the post page’s search.ejs tail script parses the parameter and highlights body text (skipping code blocks and links).

5.4 Pagefind Build Steps

Pagefind must scan public/ after hexo generate and before deployment to produce a static index. It runs during CI (tools/CI.sh, invoked by tools/cicd.sh):

npx --no-install pagefind --site public --output-subdir pagefind \
  || npx --yes pagefind@1.5.2 --site public --output-subdir pagefind

The locally installed version is preferred; if not present, the pinned version pagefind@1.5.2 is downloaded once (prevents npx from pulling a floating latest).

6. Comments

Config (theme config):

waline:
  enable: true
  serverURL: 'https://waline.17lai.site'  # your Waline server
  lang: zh-CN
  dark: 'html[data-user-color-scheme="dark"]'  # follow dark mode
  login: enable
  wordLimit: 1000
  pageSize: 10

Toggle per page via front-matter: comments: true / comments: false.

Effect: Waline comment box with Markdown/Emoji/likes/admin, auto-adapting to light/dark mode.

7. Dark Mode

Config (theme config):

dark_mode:
  enable: true
  default: auto   # auto | light | dark
  • auto: first follows prefers-color-scheme, then auto-enters dark between 18:00–6:00 local time
  • The manual toggle overrides the default (remembers user choice)

Effect: the [data-user-color-scheme="dark"] attribute drives the full set of CSS variables; images, charts and comment widgets all adapt.

8. Encryption

In-house HBE v4 (de-pluginized since 2026-08-09, replacing hexo-blog-encrypt): AES-256-GCM + PBKDF2 with 250k iterations, unified across posts / galleries / snippets / wikis. Front-end WebCrypto decryption; the hexo-blog-decrypt event triggers a theme refresh.

Config (the encrypt section of the theme config themes/matery/_config.yml):

encrypt:
  enable: true
  abstract: Something is encrypted, enter the password to view.
  message: Hello, password required here.
  tags:                     # tag-based encryption: posts with these tags encrypt automatically
    - {name: 私人, password: "********"}
  wrong_pass_message: Sorry, that password doesn't look right. Please try again.
  autoSave: true            # cache the key in localStorage, no password needed on refresh

Usage (four encryption methods):

MethodUsageNotes
Tag encryptionTag a post with one of the tags in encrypt.tagsAutomatically encrypts the full post
Per-post encryptionpassword: "********" in front-matterFull-post encryption
Content snippet encryption{% encrypt password "title" "abstract" %}markdown{% endencrypt %}Paragraph-level encryption, supports nested tags inside (2026-09-07 scheme Y)
Wiki-wide encryptionAdd encrypt_password to {wiki}-sidebar.ymlOne password for the entire wiki, shared DK across pages (2026-09-07)

Wiki-wide encryption config (any {wiki}-sidebar.yml, e.g. source/_data/dev-sidebar.yml; unified encrypt_ prefix):

encrypt_password: "password for the entire wiki"       # required
encrypt_message: "This document is password-protected"  # optional: prompt text
encrypt_wpm: "Wrong password"                           # optional: error message
encrypt_placeholder: "Enter password"                   # optional: input placeholder
encrypt_button_text: "Unlock"                           # optional: button text

Encrypts the content + right sidebar TOC; left navigation is preserved (acts as a title bar). Decrypting any page in the wiki unlocks all language versions (1-day TTL).

Effect: the post page shows a decryption UI (#hbePass); the body renders after the correct password is entered.

  • Asset minification (2026-09-25): hbe encryption assets now live in source/libs/hbe/ and go through the static pipeline, so hexo-minify compresses them (previously bypassed)

Wiki Page front-matter Checklist (2026-09-03)

Wiki pages (layout: wiki) support the same front-matter as posts. Common options:

front-matterEffect
mermaid: trueEnable Mermaid flowcharts / sequence diagrams
mathjax: trueEnable MathJax formulas
toc: falseDisable the page TOC
comment: falseDisable comments on this page
anchorjs: falseDisable heading anchors
treeview: trueEnable Prism treeview code-tree plugin
autolinker: trueEnable Prism autolinker link plugin
diffhighlight: trueEnable Prism diff highlight plugin
echarts: trueEnable ECharts charts (requires chart containers in the body)
password: "********"Encrypt this page (same decryption UX as posts)

Effect: the page shows a decryption UI (#hbePass); the body renders after the correct password is entered.

ECharts Charts (unified 2026-09-03)

  • Enable: add echarts: true in front-matter (loads libs echarts (latest, default v6.1.0) + echarts-gl 2.1.0)
  • Charts in posts: {% echarts width height %} + option JSON (width defaults to 100%, height to 400px; first arg is width, second is height)
  • Dark mode: auto-adapts (init with current theme, rebuilt on toggle)
  • GL charts: options with grid3D/globe components work automatically (echarts-gl is loaded)
  • Note: echarts 6 requires explicit type on xAxis (category/value)

Publishing & Standalone

9. RSS / Sitemap

Config (root _config.yml, hexo-feed + hexo-generator-sitemap):

feed:             # independent switches for the three feed types
  rss:
    enable: true
    output: "rss.xml"
  atom:
    enable: true
    output: "atom.xml"
  jsonFeed:
    enable: true
    output: "feed.json"
  limit: 10

sitemap:          # general sitemap
  path: sitemap.xml

baidusitemap:     # Baidu sitemap
  path: baidusitemap.xml

Output: /rss.xml, /atom.xml, /feed.json feeds; /sitemap.xml, /baidusitemap.xml for search engines.

10. PWA

Config (theme config + template):

pwa:
  enable: true

userConfig/sw.tmp.js is the Service Worker template: CI injects an offline cache version (17lai-cache-{timestamp}) and generates source/sw.js.

Effect: installable (manifest + theme color); offline caching of static assets; auto-refresh on version update.

10.1 SW Version Management Details

(Source: userConfig/sw.tmp.js, themes/matery/layout/_partial/common/head.ejs, tools/cicd.sh)

  • Template → artifact: during CI, tools/cicd.sh’s inject_pwa_sw() copies userConfig/sw.tmp.js to source/sw.js, then uses sed to inject placeholders; the artifact is published as /sw.js at the site root.
  • Cache key / version injection: the template uses the literal 17lai-cache-{uniqueIdentifier} as the cache key (prefix 17lai-cache-); CI replaces the placeholder with a build timestamp (date '+%Y%m%d%H%M%S', i.e. offline_version), producing a unique key like 17lai-cache-20260913181700 — a new version on every build. The same step injects {cdnUrl} / {cdnPathVersion} / {cdnPathLatest} / {urlVersion} placeholders.
  • Client auto-refresh (head.ejs, registered as an async script when theme.pwa.enable is true, scope: '/'; skipped on localhost to avoid stale-cache hits during local development):
    1. After registration, sends a checkIdentifier message to the SW via MessageChannel, retrieves the current cache version, and records the baseline.
    2. On updatefound → new SW installed (after addAll completes), and on controllerchange (SW takes over), triggers checkVersionAndRefresh().
    3. Only reloads (window.location.reload(), delayed 500ms for activate to finish) when the cache version changes; a refreshed guard ensures one reload per page, preventing Vercel redeploy-triggered reload loops — no popup, no manual refresh needed.
  • Activation and old-cache cleanup (SW template): the install event pre-caches cacheList then calls self.skipWaiting(); the activate event iterates caches.keys(), deletes old caches with the 17lai-cache- prefix that don’t match the current key, re-runs addAll, then calls self.clients.claim() to take over all pages.
  • Compressed backup: in release mode the uncompressed sw.js is backed up to userConfig/sw.js; after the build, the compressed version is backed up to userConfig/sw.min.js. In continue mode sw.min.js is reused to keep compressed artifacts consistent (falls back to the template version if missing).

11. Standalone Pages

The following pages have dedicated layout templates in the theme. Create the page under source/ (set layout in front-matter); the page body lives in the md file and the template renders it.

11.1 AV Page (layout: av)

Purpose: a media/resource-sharing page, structured like the About page — music player + video + my gallery + resource-sharing body.

Data sources (theme config):

  • theme.music (music player, see Interaction & Visual §9)
  • theme.video (video player, see Interaction & Visual §10)
  • theme.myGallery (my gallery, see Sidebar Widgets §8)
  • page.content (page body, resource-sharing list)

⚠️ DEPRECATED (2026-09-12): the av layout had no practical use and has been removed
from the theme (themes/matery/layout/av.ejs deleted; source/av/ is no longer created).
The following is kept for historical reference only — do not create this page again.

Create (not yet created in this project):

hexo new page av

source/av/index.md front-matter:

---
title: AV Resources
layout: av
---

11.2 Movies Page (layout: movies)

Purpose: an inspirational short-film page; the template hardcodes a Bilibili iframe player.

Data source: none — the video URL is hardcoded in the iframe src inside themes/matery/layout/movies.ejs; change the film by editing the template.

Create (already exists: source/movies/index.md):

---
title: Videos
layout: movies
---

11.3 Message Board (layout: msg)

Purpose: a message board. Body (blog timeline, etc.) + letter envelope component + comments.

Comments toggle: the template injects comments via inject_point('pageComments'); enable with front-matter comment: waline (or comments: true).

Letter envelope component: _partial/social/letter renders automatically at the end of the body — envelope-open animation + message form hints (styles in letter.styl, copy via i18n letter.*).

Create (already exists: source/msg/index.md):

---
title: Message Board
layout: msg
toc: false          # disable TOC (template checks theme.toc.enable && page.toc !== 'false')
comment: waline
---

11.4 Friends Page (layout: friends)

Purpose: friend-link card wall (Masonry waterfall) + page body + comments.

Data source (source/_data/friends.yml, a plain array read by the template via site.data.friends):

- name: Friend name          # required, card title
  url: https://example.com   # required, link target
  avatar: /favicon.png       # required, avatar
  introduction: One-liner    # required
  title: Visit               # optional, button text

Notes: card background colors cycle frind-card1~frind-card10 by list order; the page body (page.content) renders below the friend list, and comments render via the linksComments injection point.

Create (already exists: source/friends/index.md):

---
title: 'For My Friends!'
layout: friends
comment: waline
---

11.5 Contact Page (layout: contact — ⚠️ not implemented in the active theme, do not use)

Note: contact.ejs does not exist in the active theme themes/matery/layout/ (only the disabled upstream reference theme has it), so layout: contact will not render. The following is kept for historical context only.

Purpose: a contact page — barrage interaction + comments. Page body + barrage form (text/link/speed) + comments.

Barrage: LeanCloud (AV)-backed barrager; the form shows when either valine or waline is enabled (theme.valine.enable or theme.waline.enable).

Customer service widgets: daovoice / tidio / tuxiaochao config lives in Interaction & Visual §14; this section covers the page itself.

Create (not yet created in this project):

hexo new page contact

source/contact/index.md front-matter:

---
title: Contact
layout: contact
---

11.6 404 Page (layout: 404)

Purpose: not-found notice + search entry + countdown redirect to home (8 seconds).

Customize (already exists: source/404.md):

---
title: 404
layout: 404
excerpt: "Oops~,我崩溃了!找不到你想要的页面 :("   # shown as the page description (sample copy is Chinese — change it as needed)
---

Notes: excerpt renders as the page description; the banner image switches daily (/medias_webp/banner/{0-6}.webp).

Appendix: Layout Feature Quick Reference

FeatureLocationConfigDescription
Prev/next navpost bottomtheme post.prev_nextadjacent posts by date
Copyrightpost bottomtheme post.copyright (with license sub-key)per-post override via front-matter reprintPolicy
Outdated noticepost toptheme post.outdateoff by default, graded by days
TOCpost sidebartheme tocper-post off via front-matter toc: false
Ad slotssidebartheme post.advertisementsarray of cards, individually toggleable
Category radar/categories/auto-renderedno config
Tag cloud/word cloud/tags/auto-renderedno config
Archive timeline/archives/auto-renderedno config
About charts/about/auto-renderedno config
Responsive breakpointsglobal$breakpoint-* variables in base.styl368/768/992/1200/1400
Galleries/galleries/source/_data/galleries.ymlpages via hexo new gallery
Searchnavbartheme search (algolia/local/pagefind)three engines independently toggleable
Commentsposts/message pages etc.theme walinefront-matter comments toggles
Dark modeglobaltheme dark_modeauto follows system + time window
Encryptionpost pagesroot encrypttag-based or front-matter password
RSS/Sitemaproot pathsroot feed / sitemap / baidusitemapthree feeds + two sitemaps
PWAglobaltheme pwa + userConfig/sw.tmp.jsoffline cache + installable
Standalone pages/movies/ /msg/ /friends/ /404create page under source/ + front-matter layoutfour dedicated templates, see §11 (/av/ removed, /contact/ template not implemented)
Comments
Data Loading...