Qubik's Site

Posts, Taxonomy & Series

Everything that makes the theme a blog: dated posts, tags and categories, the listing pages that index them, and multi-part series.

A post

A post is any Markdown file under src/posts/. Its frontmatter is what turns it into an article rather than a page:

yaml
yaml
---
title: Hello, Terminal
description: A first look at the theme.
date: 2026-07-28
tags: [theme, terminal]
categories: Guides
cover: /images/hello-terminal.svg
---

The body starts here.

You get, automatically: a byline with the date and taxonomy, the cover rendered as a hero banner, an "on this page" panel once the article has enough headings, a license card at the end, and — when a comment server is configured — a comment card with view and comment counts.

Only title is really needed. Without a date the post sorts last and shows as undated; without taxonomy it simply appears in no term listing. Individual articles can drop the license or comment card with license: false / comments: false, or leave the article chrome entirely with article: false.

Post ordering is by date, newest first, everywhere.

Tags and categories

Declare terms as plain names in frontmatter — a single string or a list:

yaml
yaml
tags: [theme, color]
categories: Design

Slugs and URLs derive from the authored name (Design/categories/design), so links are stable. To display a term differently — including per language — map it site-wide in themeConfig.taxonomy; terms without an entry render verbatim:

ts
ts
taxonomy: {
  tags: { theme: { en: "theme", "zh-Hans": "主题" } },
  categories: { Guides: { en: "Guides", "zh-Hans": "指南" } },
},

Categories and tags behave identically; use categories for a few broad buckets and tags for everything else.

Listing pages

Each listing is a one-line Markdown page placing a globally registered component. The demo ships all of them; keep the ones you use.

FileComponentRenders
src/posts.md<PostsIndex />All posts, paginated (10 per page)
src/archives.md<ArchivesList />The full timeline, grouped by date
src/categories.md<CategoriesIndex />Every category with its post count
src/tags.md<TagsIndex />Every tag with its post count
src/series.md<SeriesIndex />Every series with icon, description, article count
md
md
---
title: Posts
---

<PostsIndex />

Three route families are generated at build time from a .paths.mjs loader beside the template — leave these files in place if you want the routes:

TemplateGeneratesComponent
src/page/[num].md/page/2/page/N (page 1 is /posts)<PostsIndex />
src/tags/[name].md/tags/<slug> per tag<TermPosts field="tags" />
src/categories/[name].md/categories/<slug> per category<TermPosts field="categories" />

The loaders import your themeConfig, so the generated routes and what the components display always agree — which matters for the series toggles below.

Series

A series is a folder under src/series/. The folder name is the series' identity and URL segment:

src/series/terminal-internals/
├── series.yml     # optional metadata
├── index.md       # the landing page
├── part-1.md
└── part-2.md

series.yml describes the series (every field optional):

yaml
yaml
icon: nf-fa-terminal
title:
  en: Terminal Internals
  zh-Hans: 终端内幕
description:
  en: A short series on how the theme's shell is built.
order: 0

index.md is the landing page; place <SeriesArticles /> to list the parts automatically:

md
md
---
title: Terminal Internals
---

<SeriesArticles />

The articles are ordinary post frontmatter plus an order (default 0, smaller sorts higher, ties alphabetical):

yaml
yaml
---
title: "Part 1 — The Shell Frame"
date: 2026-07-15
order: 1
---

Every article in the folder shows a banner with the series icon, title, and description, linking back to the landing page.

Series in the general listings

Series articles stay out of the general post listings by default — a series belongs to itself. Opt in per surface:

ts
ts
series: {
  inPosts: false,      // post index + pagination
  inArchives: true,    // archives timeline
  inCategories: true,  // category index + per-category pages
  inTags: true,        // tag index + per-tag pages
},

An admitted article shows its series name before the title (Terminal Internals › Part 1 — The Shell Frame) so it stays recognizable among regular posts.

Cover images

A cover in frontmatter is used in two places, and adapts to each:

yaml
yaml
---
cover: /images/hello-terminal.svg
---
  • In post-list cards it is a full-height panel on the right, cropped to fit and fading into the card. Below 640 px it becomes a full-width strip on top.
  • On the article page the header becomes a hero banner showing the image in full at its natural aspect ratio, fading into the surface only behind the byline.

Covers are lazy-loaded and excluded from the lightbox gallery. Posts without one render exactly as before. Put the files in src/public/ and reference them site-absolutely.

The end-of-article cards

Two cards close every article, both prompt-decorated:

  • License — the article title, permalink, publish date, last-updated date, and author, with the license statement and its icons. Author and license come from themeConfig.author / .license; the "Updated" date comes from an explicit updated frontmatter value or, failing that, git (which needs lastUpdated: true in the site config — it is set in the demo).
  • Comments — a Waline widget plus the view/comment counters shown near the title. It renders only once themeConfig.comments.waline.serverURL points at your deployed Waline server.
ts
ts
comments: { waline: { serverURL: "https://waline.example.com" } },

Drafts

There is no draft flag. The conventional approach is a folder the explorer hides, kept out of src/posts/ so it never enters a listing — the demo does this with src/drafts/ and an explorer.json carrying "showInExplorer": false. Note that such pages still build and remain reachable by URL; delete or move them out of src/ to keep them off the site entirely.

READ~/docs/guide/blogging
TOPdark