Loading...

Loading...

This article is both a complete usage tutorial for the matery theme’s layout and page features and an automated testing playground.
Coverage: post layout features, sidebar widgets, responsive breakpoints, galleries, search, comments, dark mode, encryption, RSS/sitemap, PWA.
Automated tests (tools/tests/release-test.sh) assert the rendering markers labeled below.


1. Post Layout Features

1.1 Prev/Next Navigation

Purpose: Display prev/next post cards at the bottom of a post to guide continuous reading.

Configuration (theme _config.yml):

# Prev/next navigation at the bottom of a post (auto-sorted by date)
prenext:
  enable: true

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

Test assertion: The post page contains the #prenext-posts container.

Purpose: Show copyright and license information at the bottom of a post, noting the author and reprint rules.

Configuration (theme _config.yml):

post:
  copyright: true          # Show copyright notice
  license: This article is licensed under CC BY-NC-SA 4.0

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

Test assertion: The post page contains a copyright notice container (#copyright or .post-copyright).

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).

Configuration (theme _config.yml):

post:
  outdate:
    enable: true
    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.

Test assertion: The post page contains an outdated notice element (outdate-related class).

1.4 TOC

Purpose: Display chapter navigation in the right sidebar (or sidebar); click to jump to the corresponding heading, with current chapter highlighted while scrolling.

Configuration (theme _config.yml, top-level toc):

# TOC (top-level config, shared by every layout that has a TOC: post / wiki / msg / bb)
toc:
  enable: true
  headingSelector: "h2,h3,h4,h5,h6"   # heading levels included in the TOC
  collapseDepth: 0                    # collapse depth (0 = all collapsed)

Effect: Desktop: floating TOC tree on the right, highlights on scroll; Mobile: collapsed into the sidebar.

2026-09-25 update: the config moved from post.toc to top-level toc, and the wiki page now shares the same config as the post page (previously wiki hardcoded h1, h2, h3 and ignored the config entirely). The misleading showToggleBtn (it only gated whether the button’s click handler was registered — not a visibility switch) and the unimplemented placement (dead config) were removed.

Test assertion: The post page contains the #toc-content TOC container.

1.5 Advertisement Slots

Purpose: Reserved ad card slots below the TOC (card format).

Configuration: The ad card is rendered by _partial/widget/advertisement.ejs, with content maintained in the template (post 2026-09-08 directory restructuring position).

Effect: Ad card (.tgCard) displayed below the sidebar TOC.

Test assertion: The post page contains the .tgCard ad container.

1.6 Word Count & Reading Time

Purpose: the post header article-info shows the per-post word count and estimated reading time; the footer shows the site-wide total.

Configuration (theme _config.yml, post section):

post:
  wordcount:
    enable: true        # show per-post word count
  min2read:
    enable: true        # show reading time
    awl: 1              # average word length: 1 char = 1 word for CJK
    wpm: 300            # words per minute: 250-350 suits CJK technical content

Implementation (themes/matery/scripts/helpers/wordcount.js): three built-in helpers replace the former hexo-wordcount plugin — same names and signatures, so no template changed:

HelperPurpose
wordcount(page)per-post count (CJK characters + English words)
min2read(page)reading time in minutes = floor(count / (awl × wpm)) + 1
totalcount(site)site-wide total (posts + wiki pages; i18n duplicates de-duplicated; encrypted posts counted by plaintext post.origin)

Effect: article-info shows “word count · reading time”; the footer shows the site-wide total (wiki pages and encrypted posts included).

Test assertion: posts expose the article-info word-count / reading-time markers; the three helpers’ behaviour is covered by tools/tests/unit/helpers.test.js.

⚠️ Historical bug (fixed 2026-09-22): the old default awl: 2 / wpm: 60 equals 120 chars/min — tuned for English — so a 520-character Chinese post showed 5 min; with the new awl: 1 / wpm: 300 the same post shows 2 min.

1.7 Update Time (“Updated on”)

Purpose: the post header shows when the article was last modified (Updated on), distinct from Published on.

Data source (fixed 2026-09-26): the source file’s git last-commit time — neither the file mtime nor the publish date.

CandidateWhy not
file mtimereset by git checkout/clone or bulk scripts (measured: all 142 _posts mtimes fell in the same second) → every post shows the same day
date (publish time)cannot reflect edits; yields “Updated on == Published on”
git commit time ✅frozen at commit time, immune to any working-tree operation

Implementation: scripts/filters/post-updated.js overwrites post.updated in before_generate (before every generator) using the git time from scripts/utils/git-time.js; an explicit updated: in front-matter takes precedence.

Config (root _config.yml):

updated_option: 'date'   # fallback only — 'empty' makes the third-party sitemap template crash on post.updated.toDate()

Effect: the post shows “Updated on …” (different per post); posts never edited are hidden by the compare_date(page.date, page.updated) guard.

Test assertion: tools/tests/unit/unit-updated.test.js (4 cases: CJK path hits / missing returns null / overwritten as moment / author value preserved); the post page contains datetime="…" update.


2. Sidebar Widgets

2.1 Category Radar

Purpose: The /categories/ page uses a canvas radar chart to display post count distribution across categories.

Effect: Canvas radar chart at the top of the categories page, one dimension per category.

Test assertion: /categories/ contains a <canvas> element.

2.2 Tag Cloud

Purpose: The /tags/ page displays all tags in chip style; click to navigate to the corresponding tag page.

Effect: Colorful rounded chips on the tags page, highlighted on hover.

Test assertion: /tags/ contains .tag-chips .chip.

2.3 Word Cloud

Purpose: The /tags/ page uses an ECharts word cloud (2026-09-05 custom spiral → echarts-wordcloud@2, fixing 30s timeout); tag size/color based on weight.

Effect: ECharts word cloud on the tags page (continuous font size range [14,56] ∝ post count; container height auto-calculated as max(theme.wordcloud.minHeight=500, n×theme.wordcloud.heightPerWord=2.2) — zero-config auto-calculation; title Tag Word Cloud i18n; light/dark mode via Matery.echarts.init auto-switch).

Test assertion: /tags/ contains #tag-wordcloud container + echarts loaded and only 1 instance + light/dark switch dispose→reinit (tools/tests/wordcloud.test.js L2g).

2.4 Archive Timeline

Purpose: The /archives/ page archives all posts in a year/month timeline.

Effect: Vertical timeline with dots + collapsible years.

Test assertion: /archives/ contains #cd-timeline.

2.5 About Page Statistics (postCharts)

Purpose: The /about/ page uses three ECharts charts to display site statistics (post trend / category distribution / tag distribution).

Effect: Three groups of canvas charts on the about page (#posts-chart / #categories-chart / #tags-chart), auto-adapting to light/dark mode.

Test assertion: /about/ contains the #postCharts container + multiple <canvas> elements.

2.6 ArtiTalk (Optional, Disabled by Default)

Purpose: Embed the ArtiTalk microblog component on the /about/ page (a LeanCloud-based personal status wall for posting short updates).

Configuration (theme _config.yml):

artitalk:
  enable: false    # Disabled by default
  appId: # LeanCloud App ID
  appKey: # LeanCloud App Key

Effect: Renders a status list on /about/, requires LeanCloud backend. Disabled by default; enable by filling in appId/appKey.

Test assertion: Does not render in the default state.

2.7 My Projects (myProjects)

Purpose: Display a card list of personal projects on the /about/ page (project name / icon / description / link).

Configuration (theme _config.yml):

myProjects:
  enable: true
  data:
    ProjectName:
      icon: fab fa-linux
      iconBackground: 'linear-gradient(...)'
      url: https://example.com
      desc: Project description

Effect: Three-column project cards on the about page, with icons + gradient background + title + description.

Test assertion: /about/ contains the .my-projects container.

2.8 My Skills (mySkills)

Purpose: Display skill progress bars on the /about/ page (skill name / percentage / color gradient).

Configuration (theme _config.yml):

mySkills:
  enable: true
  data:
    C:
      background: 'linear-gradient(to right, #2196F3 0%, #42A5F5 100%)'
      percent: 90%

Effect: Two-column skill bars on the about page, with colorful gradient backgrounds + percentage numbers.

Test assertion: /about/ contains the .my-skills container.

2.9 Archive Calendar (postCalendar)

Purpose: Display a calendar heatmap on the /archives/ page showing monthly post publication counts.

Configuration (theme _config.yml):

postCalendar: true    # Show post calendar widget on the archives page

Effect: Calendar component (#post-calendar) on the archives page; color intensity represents post count.

Test assertion: /archives/ contains the #post-calendar container.

2.10 Sidebar Advertisement (advertisement)

Purpose: Display ad cards (text / image / link) below the TOC on post pages.

Configuration (theme _config.yml):

post:
  advertisements:
    - id: "ad-1"
      text: "Ad copy"
      link: "https://example.com"
      background: "#ff6666"
      background_dark: "#8b3a3a"
      enable: true

Effect: Renders .tgCard ad cards below the TOC; supports dual color values for dark mode. Outputs nothing when no ads are enabled.

Test assertion: Post page contains the .tgCard ad container.


3. Responsive Breakpoints

Configuration (themes/matery/source/css/_variables/base.styl)

$breakpoint-sm: 368px   // Extra small (phone portrait)
$breakpoint-md: 768px   // Tablet / phone landscape
$breakpoint-lg: 992px   // Desktop
$breakpoint-fun: 1400px // Effects enable threshold (large screens)

Layout at Each Breakpoint

BreakpointLayout
<768pxNav collapses to hamburger menu, content single column
768–992pxTablet layout, sidebar collapsed
>992pxDesktop three-column (content + sidebar + TOC)
>1400pxEffects enabled (sakura/ripples, etc.)

Implementation effect: Bootstrap/Materialize grid auto-adapts; sidebar auto-hides on narrow screens; TOC collapses on mobile.

Test assertion: <768px nav collapses; >992px desktop three-column; >1400px effects enabled.


4. Content Width Unification (Added 2026-09-07)

Design Principle

“Percentage + Max-width”: Content area width: 90% (relative to screen) + max-width steps (readability limit). Not pure 90% — at 4K, 90% = 3456px is unreadable.

Width Steps (_variables/base.styl)

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

Selection Rules (mixin content-container(wide))

  • Single-column (content without sidebar): content-container() → standard width
  • Multi-column (content + sidebar/TOC): content-container(true) → wide width
  • No hand-written max-width in layout (unified via mixin)

Implementation effect: Wiki (multi-column) 4K content column 899px → 1500px (+67%); same width steps as post-container.

Automated testing: tools/tests/content-width.test.js (L2m) covers: W1 4K wiki==post-container / W3 FHD / W4 2K / W5 single-column capped at 1525px.

FA Resource Version Stamp ({urlVersion})

Purpose: Ensure browsers/CDN fetch new resources after a version release, avoiding stale caches.

Mechanism: Resource URLs carry a version stamp placeholder {urlVersion} (e.g., /libs/awesome/webfonts/fa-solid-900.woff2?v=13.13). During build, tools/cicd.sh sed-replaces it with the actual version number (from tools/.VERSION).

Affected resources (userConfig/_config.tmp.yml, static_prefix.libs section):

ResourceConfig KeyExample
FA WebFontfaBrands/faRegular/faSolid/libs/awesome/webfonts/fa-solid-900.woff2{urlVersion}
Theme CSSmatery/mycss/main/css/main.css{urlVersion}
Highlight CSShighlight/highlightDark/css/highlight.css{urlVersion}

No manual action required: Version is injected automatically by CI. Local preview (hexo server) does not replace the placeholder; resources have no version suffix (functionality unaffected).

⚠️ Do not manually edit the {urlVersion} placeholders in themes/matery/_config.yml — that file is a build artifact overwritten by userConfig/_config.tmp.yml during CI.


5. Multilingual Fallback Mechanism (Added 2026-09-08, Revised 2026-09-10)

Language Configuration (preference section)

  • default_lang: Default language (no prefix path), shared by all components
  • fallback_lang: Fallback intermediate layer — when the requested language doesn’t exist, fall back to this language first
  • lang_meta: Language capability table (9 languages) + enabled switch (disabled languages don’t generate fallback pages / don’t appear in dropdown)

Fallback Chain (Three Layers)

Requested language exists? → Use it
→ Doesn't exist → fallback_lang exists? → Fall back to fallback_lang
→ Doesn't exist → default_lang exists? → Fall back to default_lang
→ Doesn't exist → Any available language

Behavior by Page Type (Revised 2026-09-10)

Page TypeMultilingual VersionNotice Banner
Post✅ If translation exists, use it; if not, generate via fallback chain✅ Show “xx doesn’t exist, displaying yy language version”
Wiki page✅ Same as above✅ Same as above
Aggregation page (tag/category/archive)✅ Generate per enabled languages❌ Generate only, no notice
Standalone page (about/musics/movies/friends/msg/bb/galleries etc.)✅ Generates /{lang}/{page}/ (UI text goes through languages/{lang}.yml i18n)❌ None
Homepage✅ /en/, /en/page/N/❌ None

Standalone pages (including gallery sub-pages and historical pages with layout:'post' in the pages collection, such as dash) are generated by standalone-page-generator.js using an exclusion method (layout ∉ {wiki, tags, categories, 404} → generate; new standalone pages are automatically covered without registering to a list).

Real Translation Mapping (post / wiki)

Translation files share the same filename as the default language, placed in the language subdirectory:

TypeTranslation Source Pathfront-matter
Postsource/{lang}/_posts/{same-name}.mdKeep the same abbrlink + add lang: en
Wikisource/{lang}/docs/{same-name}.mdSame (wiki pages have no abbrlink, mapped by same-name path)

Chinese-English association has two modes: with abbrlink plugin → same abbrlink associates; without plugin → same-name file association (with filesystem fallback check). With real translation → generates /{lang}/posts/{abbrlink}/ real content page; without translation → fallback chain generates.

Note: Hexo natively only recognizes source/_posts/ (en/_posts/ is ignored); article translations are generated by the theme’s i18n-post-generator.js.

Title i18n (page_title_i18n, 2026-09-10)

<title> and the cover headline (bg-cover-content.ejs) share page_title_i18n from scripts/helpers/page-title.js: tags/categories/about map by title, the rest map by layout to i18n keys; when it returns null the caller falls back to the original title — multilingual page titles switch with the language.

Client-Side Language Strategy (Scheme A: site-wide follow, 2026-09-10)

ScenarioBehavior
First visitNo local preference and browser language matches an enabled language → popup asking to switch; if accepted, remember and redirect; if rejected, remember default language (won’t ask again)
Panel switchLanguage dropdown selection → write local preference + redirect to corresponding language version
Page load / refreshHas local preference → load corresponding language version directly (head.ejs inline redirect, before render with no flash); crawlers skipped (SEO); no multilingual version path skipped (prevent 404)
Panel displayLanguage dropdown shows local preference (localStorage.wiki_lang)

Added 2026-09-27 (i18n batch)

ItemChange
404 pageSwitched to client-side language detection (single file) — copy is chosen dynamically from the browser language / local preference; no longer statically generated per path (/en/404.html etc. retired)
archives aggregate pageUnified pagination across all languages — no longer diverges per language
Encrypted page languageEncryption prompt / button copy follows the page language (hbe-bundle fallback copy goes through MATERY_I18N, E7)
Runtime copySearch / dialog / codeLang runtime copy is i18n-ised (P0/P1)
In-content linksurl_for_lang points to the matching language version (E2)

Test coverage: lang-client-strategy.test.js (client language strategy, 7 assertions, L11); i18n.test.js (MATERY_I18N injection + button copy, L11)

Language Prefix Rules (url_for_lang)

Resources (css/js/libs/medias/images/fonts etc.) and static files (xml/txt/json etc.) do not get language prefixes; all other content paths always get the prefix. Exemption rules come from a single source: scripts/utils/url-lang-blacklist.js. Standalone page navigation thus carries the /en/ prefix pointing to the corresponding language version.

Implementation Effect

Visiting /de/docs/setup/ (de enabled, no translation): if en translation exists → content uses en + notice banner “de doesn’t exist, displaying en”; if no en → content uses zh-cn + notice banner. Standalone page navigation (url_for_lang) doesn’t add language prefix, always points to default language version.

Automated testing: tools/tests/lang-fallback.test.js (L13) covers fallback chain 4 levels + configuration compatibility/filtering/validation (13 assertions); tools/tests/release-test.sh L2x covers MATERY_LANG injection / fallback pages / 9 standalone pages multilingual (including gallery sub-pages and dash) 200 / aggregation page multilingual; tools/tests/unit-standalone.test.js (L13, 22 assertions) covers standalone page exclusion judgment + language prefix exemption; tools/tests/lang-client-strategy.test.js (L11, 7 assertions) covers client-side language strategy (site-wide follow / crawler skip / panel display).


6. Wiki Multilingual (docs/api/tutorial sub-sites, revised 2026-09-10)

Purpose

Wiki documentation sub-sites (docs/api/tutorial) with three-column layout + Chinese/English bilingual + fallback mechanism, sharing the same multilingual system as blog posts.

Core Conventions (required front-matter)

---
layout: wiki        # Required, otherwise the three-column layout is not rendered
wiki: tutorial       # Required, sub-site name (docs/api/tutorial)
---

Directory Structure

LanguagePath
Chinese (default)source/{wiki}/ (no prefix)
Englishsource/en/{wiki}/ (/en/ prefix)

Fallback Mechanism

No English version → /en/... falls back to showing Chinese + top notice banner (“no en version”) + noindex.

# source/_data/docs-sidebar.yml
docs:
  - key: guide
    children:
      - key: writing
        title: Writing Guide
  • Entry keys are English snake_case, display text goes through languages/*.yml translations
  • Whole-wiki encryption: configure encrypt_password at the top of the sidebar (unified encrypt_ prefix: message/placeholder/button_text optional) → all pages under that wiki are encrypted, enter the password to decrypt and render; after decryption the derived key (dk) is stored in localStorage (wiki.<wiki name>, TTL 1 day), other pages of the same wiki auto-decrypt without re-entering; encrypt_-prefixed config keys are skipped during sidebar rendering (not shown as table of contents items)

Absolute paths + .html (/tutorial/guide/writing.html), language prefix added automatically.

Section Numbering

Wiki pages with hand-written numbering must set closeAutoTocNum: true (same as the blog, prevents double numbering from CSS auto-numbering).

Adding a New Wiki Page

  1. Write Chinese source/{wiki}/xxx.md (front-matter layout: wiki + wiki: <sub-site>)
  2. Add English source/en/{wiki}/xxx.md
  3. Update source/_data/{wiki}-sidebar.yml
  4. Add translation keys for 9 languages (languages/*.yml)
  5. hexo clean rebuild

Wiki Tag-Component Styles & Layout Width

Wiki pages have additional layout considerations for tag components (note / ::: containers / tabs):

Style scoping: Tag component CSS is entirely scoped under .post-container descendant selectors. The wiki <main> must carry both post-container and wiki-main classes for components to receive styles. If new tag component CSS is added without a .post-container prefix, it will be invisible on wiki pages.

Layout width: Wiki <main> requires explicit width: 100% (_base/media.styl .wiki-main { width: 100% }), otherwise the flex item’s auto margin triggers shrink-to-fit, causing inconsistent widths across wiki pages (pages with less content become narrower). max-width is controlled by the global .post-container rule, keeping all wiki pages at uniform width.

Test verification: readmode-integration.test.js asserts wiki <main> contains post-container class and .wiki-layout width is consistent across wiki pages.

Automated Testing

tools/tests/release-test.sh L2b: en page 200 + fallback notice banner + comment path normalization + default language no prefix; L2y: wiki encrypted page decryption + sidebar has no encrypt_ junk titles.


Data Source (source/_data/galleries.yml)

Album Name:
  photos: Single image object | string path | directory path  # Three forms
  cover: Cover image
  description: Description

Page Structure

  • /galleries/ — Album list page (cards + covers)
  • /galleries/<name>/ — Single album page (lightbox + caption)
  • Encrypted album: Add password to front-matter, requires decryption to enter

Implementation Effect

List cards + inner page lightGallery lightbox browsing; encrypted albums display after decryption.

Test assertions:

  • /galleries/ album list
  • /galleries/2020/ lightbox + caption
  • /galleries/private/ encrypted decryption UI

8. Search Feature

Configuration (root _config.yml search block)

Three engines: local (local search.json) + algolia (Algolia API) + pagefind (Pagefind index).

Interaction

  • Popup: Opens on click/focus of the navbar search box; Ctrl+K (Mac ⌘+K) keyboard shortcut opens it
  • Engine switching: Dropdown inside the popup switches between local / Algolia / Pagefind
  • Multi-language filtering: Filters by language prefix + deduplicates via fallback (applyLangFilter)
  • Unified highlighting: All three engines inherit the same base class, sharing _highlight / _escapeHtml (consistent result highlighting)

Implementation Effect

  • Navbar search box (#searchInput)
  • Search popup with real-time results
  • /search.json is the local search data source

Test assertion: Page contains #searchInput; /search.json is accessible; popup can be opened.


9. Comment System

Configuration (root _config.yml)

comment: waline    # Comment engine
waline:
  serverURL: https://waline.17lai.site
  ...

Page front-matter control: comment: waline (post/message/friends/timeline pages).

Implementation Effect

Waline comment box (Markdown/Emoji/likes/admin backend).

Test assertion: Post/msg/friends/bb pages contain #comments; waline initialization script exists.


10. Dark Mode

Configuration (theme _config.yml)

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
  • Toggle button: Manual switch overrides default mode

Implementation Effect

[data-user-color-scheme="dark"] attribute drives full CSS variable switching; images/charts adapt automatically.

Test assertion: CSS contains [data-user-color-scheme="dark"] rules; toggle button can switch light/dark.


11. Encrypted Posts

Configuration (root _config.yml)

encrypt:
  tags:
    - Private    # Encrypt by tag
  default_abstract: This post is encrypted
  ...

Posts only need the encryption tag; readers must enter a password to view.

Implementation Effect

Post page displays decryption UI (#hbePass); content renders after correct password entry.

Test assertion: Tag-encrypted post contains #hbePass decryption UI.

Unified Refresh After Decryption (Added 2026-09-09)

After encrypted content is decrypted, components in the content need re-initialization (before decryption it’s ciphertext, DOM lacks these elements):

ComponentAction After Decryption
Code blocks (code-area)codeWidget() scans new pre elements to generate toolbar (language label/copy/collapse/fullscreen)
Image lightboxarticleInit() wraps img → .img-item + lightGallery binding (shared for post/wiki)
Mermaid diagramsevents.refresh() triggers refresh callback to re-render (data-processed idempotent)
Layout (AOS/Masonry)boot.refresh() → refreshLayout re-arranges

Unified trigger point: hbe-bundle onSuccess (all encryption scenarios: post/gallery/wiki/snippet shared). Each listener retains only its unique handling:

  • Post: tocbot reinit (post TOC)
  • Wiki: TOC encryption state removal + tocbot reinit
  • Gallery: initGallery()

Idempotent protection: codeWidget data-code-processed, mermaid data-processed, articleInit destroys old instance and rebuilds — repeated calls don’t produce duplicate elements.

Test assertion: After decryption, .code-toolbar count equals .code-area count; image wrapping .img-item + container lg-uid attribute exists.


12. RSS / Sitemap

Configuration (root _config.yml)

feed:            # hexo-feed generation
  type: atom
  path: atom.xml
sitemap:         # hexo-generator-sitemap
  path: sitemap.xml

Output

  • /rss.xml, /atom.xml, /feed.json — Subscription feeds
  • /sitemap.xml, /baidusitemap.xml — Search engine crawling

Test assertion: /rss.xml, /atom.xml, /feed.json, /sitemap.xml are all accessible.


13. PWA

Configuration (root _config.yml + userConfig/sw.tmp.js)

pwa:
  enable: true
  manifest: ...   # Name/icons/theme color

sw.tmp.js is the Service Worker template; CI injects offline cache version 17lai-cache-{timestamp} to generate source/sw.js.

Implementation Effect

  • Installable (manifest)
  • Offline caching of static assets
  • Auto-refresh on version update

Test assertion: /sw.js is accessible, SW registration works (localhost exempted).

SW Version Comparison & Auto-Refresh

The CI-injected cache key 17lai-cache-{timestamp} is treated as a new version whenever it changes. When registering the SW, updatefound (new SW installed) and controllerchange are monitored; the old and new cacheStorageKey are compared: if changed, the page auto-refreshes once (no prompt, the user doesn’t need to manually refresh). sw.tmp.js uses skipWaiting on install + clients.claim() on activation to take over immediately, and cleans up old 17lai-cache-* caches.


14. SEO Structured Data (JSON-LD + Open Graph)

Purpose

Provide structured metadata for search engines and social platforms: JSON-LD rich results + Open Graph / Twitter Card share previews.

Configuration (layout/_partial/common/structured-data.ejs + head.ejs)

No standalone toggle; output by head.ejs:

  • JSON-LD @graph single script: WebSite (with SearchAction search box) + Person (author) + Organization (site); post pages add BlogPosting
  • @id cross-references (publisher/author point to #organization/#person), descriptions truncated after strip_html + 210 characters
  • OG/Twitter: og:type/title/url/site_name/description/image + twitter:card=summary_large_image

Implementation Effect

  • Page contains <script type="application/ld+json"> rich result data
  • Sharing to social platforms shows title/description/cover image preview

Test assertion: Page contains application/ld+json; post page contains BlogPosting; contains og:type / twitter:card tags.


15. Design Tokens & CSS Variable Architecture

Purpose

Unify design tokens (border-radius / shadows / transitions / spacing / font sizes) through a three-layer delivery chain to CSS custom properties for site-wide reference, avoiding hardcoded values.

Configuration (theme _config.yml design section)

design:
  radius_sm: 3px          # Buttons / small labels
  radius_md: 8px          # Cards / notes
  radius_lg: 12px         # Modals
  shadow_md: "0 2px 5px 0 rgba(0,0,0,0.16)"
  shadow_lg: "0 5px 11px 0 rgba(0,0,0,0.18)"
  transition_fast: 0.2s
  space_1: 4px            # 4px base increment
  font_size_md: 1.125rem  # rem-based, scales with --font-scale

Three-Layer Delivery Chain

_config.yml design.* → theme-config() → Stylus variables → :root CSS custom properties → components var(--*)
  • Scheme layer: source/css/_schemes/matery.styl (aligned with NexT _schemes/ mechanism, extensible for future schemes)
  • Font size: root font size calc(var(--font-size-base) * var(--font-scale)), preference panel adjusts --font-scale for site-wide rem proportional scaling

Implementation Effect

Components reference tokens uniformly (consistent border-radius / shadows / spacing); changing one value in the design section applies site-wide.

Test assertion: CSS contains --font-scale rule and root font size calc(... * var(--font-scale)).


⚠️ Known Pitfalls

Pitfall 1: Double Section Numbering

Symptom: Headings render as “1. 1. Overview” (two numbers).

Cause: Theme CSS auto-adds numbers to h2–h6 by default; hand-written numbers (e.g. ## 1. Overview) stack on top.

Fix: Pages with hand-written numbering add closeAutoTocNum: true in front-matter; pages without hand-written numbering must not add this field.

This article already correctly sets closeAutoTocNum: true (front-matter line 19).

Pitfall 2: Encryption ↔ TOC Conflict

Symptom: When a wiki is encrypted as a whole, the TOC is hidden by design.

Cause: In encryption mode the TOC component is skipped (intended behavior).

Fix: Test assertions must not hard-code “TOC must exist”. Encrypted wiki pages not showing TOC is normal behavior, not a bug.

Pitfall 3: Wiki English Pages Missing lang: en

Symptom: Sidebar links point to /docs/ instead of /en/docs/.

Cause: wiki_sidebar uses page.lang || default_lang; omitting lang: en falls back to the default language path.

Fix: All pages under source/en/tutorial/ must include lang: en. Verify: for f in source/en/tutorial/**/*.md; do grep -L 'lang: en' "$f"; done.

Pitfall 4: _config.tmp.yml Comment Lines Must Be Preserved

Symptom: Attempted to delete comment lines to slim the file, was vetoed and fully reverted.

Cause: User explicitly declared comment lines are useful reserves (may be restored in the future).

Fix: Never delete or modify comment lines in userConfig/_config.tmp.yml.

Pitfall 5: Config Changes Require Clean Build

Symptom: Changed configuration but the page didn’t update.

Cause: Hexo’s caching mechanism doesn’t auto-detect configuration changes.

Fix: After changing config or source/_data/*, you must run npm run clean && npm run build. Build logs showing INFO Deleted database. and INFO Deleted public folder. confirm clean ran successfully.

Pitfall 6: source/_data/* Incremental Cache Misjudgment

Symptom: After modifying source/_data/friends.yml and other data files, the page is blank or data doesn’t update.

Cause: Hexo’s incremental cache incorrectly judges “cached, unchanged” (Cache hash updated but Data model not written).

Fix:

  • Local: rm -f db.json && npm run build
  • Container: tools/cicd.sh -d (includes clean + generate + config injection)
  • In-container pm2 restart is not enough: pm2 caches old db.json content in memory

Appendix: Layout Feature Quick Reference

FeatureLocationKey ConfigurationAutomated Assertion Marker
Prev/nextPost bottomprenext#prenext-posts
CopyrightPost bottompost.copyright#copyright
Outdated noticePost toppost.outdateoutdate
TOCRight sidebartoc#toc-content
Ad slotBelow TOCpost.advertisements.tgCard
Category radar/categories/Autocanvas (Matery.echarts light/dark switch)
Tag cloud/word cloud/tags/Auto.tag-chips / #tag-wordcloud
ArtiTalk/about/artitalk.enable: false (disabled by default)LeanCloud backend
My Projects/about/myProjects.enable: true.my-projects
My Skills/about/mySkills.enable: true.my-skills
Archive calendar/archives/postCalendar: true#post-calendar
Online music/musics/musics + failover.aplayer (tools/tests/musics-integration.test.js L2i)
Archive timeline/archives/Auto#cd-timeline
About page charts/about/Autocanvas
Galleries/galleries/galleries.ymlLightbox + encryption UI
SearchNavbarsearch#searchInput / search.json
CommentsPost/msg etc.comment: waline#comments
Dark modeGlobaldark_modedata-user-color-scheme
EncryptionTag-drivenencrypt.tags#hbePass
RSS/SitemapRoot pathsfeed / sitemap/rss.xml etc.
PWAGlobalpwa + sw.tmp.js/sw.js
SEO<head>structured-data.ejs + head.ejsapplication/ld+json / og:type
Design tokensGlobaldesign section + _schemes/--font-scale

References

系列教程

全部文章RSS订阅

Hexo 主题功能测试系列


Hexo Theme Feature Test - Layout & Pages
Posted on
August 7, 2026
reprint policy. If reproduced, please indicate source
Support
Comments
Data Loading...
 Previous

Read More

Hexo Theme Feature Test - Content Tags
Hexo Theme Feature Test - Content Tags Hexo Theme Feature Test - Content Tags
Complete tutorial and test for matery theme content tag plugins: note callouts, timeline, tabs, labels, buttons, githubC
2026-08-07
Current 

Read More

Hexo Theme Feature Test - Layout & Pages
Hexo Theme Feature Test - Layout & Pages Hexo Theme Feature Test - Layout & Pages
Complete tutorial and test for matery theme layout and page features: post layout features (prev/next navigation, copyri
2026-08-07