将 Obsidian 仓库中的 Markdown 渲染为 HTML,front matter 定义页面标题、副标题、图片、标签和分类
cress — Django 博客模板
作者 chalkstreamdev
Cress 是一个面向 Obsidian 仓库的 Markdown 静态站点与博客生成器,用 Python 编写,通过 YAML 配置将 Markdown 渲染为 HTML,可接入现有前端构建产物的 Vite manifest 来复用样式。
看看你的网站会是什么样
浏览真实页面截图,比较内容排版与手机效果。点击下方缩略图切换,放大查看整页细节。
全部页面与视图 2
截图用于了解视觉与布局,交互效果请以实际项目为准。
值得关注的模板亮点
从已有项目资料中,快速了解它的特点。
默认无样式,通过读取现有构建工具的 Vite manifest 链接样式表,便于嵌入已有站点模板
支持 Obsidian 特性:附件图片提升、Wikilinks、行内标签、标题锚点和标注框
可将仓库构建为博客,生成分页文章列表、分类页、标签页和 RSS Feed
也可构建静态页面模式,生成树状层级和面包屑导航
提供 cress index 命令,在不渲染的情况下输出结构化文章列表供宿主应用使用
模板详细介绍
Cress 是一个从 Obsidian 仓库生成静态博客或文档站点的命令行工具。README 说明它把 Markdown 文件渲染为 HTML 模板,站点配置由单个 YAML 文件定义,页面标题、副标题、图片、标签和分类来自 .md 的 front matter。它不自带主题,默认无样式,设计上用于嵌入现有站点的模板,并读取现有构建工具的 Vite manifest 来链接样式表。
开箱支持的功能包括:从 Obsidian 仓库的附件文件夹提升嵌入图片、指向其他文章的 Wikilinks、行内标签、标题锚点以及 Obsidian 风格标注框。插件系统允许项目编写 Python 插件,挂钩 Markdown 中的短代码。仓库可以转换为博客,生成按最新优先排列的分页文章列表、分类页、标签页和 RSS Feed;也可以创建静态页面,构建树状层级和面包屑导航。所有文章会输出 meta 标签和 Open Graph 属性。
Cress 提供 build、validate、serve、publish、index 等命令。serve 会构建并在本地浏览文件,可选 --live-reload 在 Markdown 或模板变化时重建并刷新浏览器;publish 可将构建产物暂存、提交并推送到线上站点。index 命令可在不渲染的情况下输出结构化文章列表,供宿主应用集成。
README 未提供关于主题外观、性能基准或生产环境适用性的说明;它明确说明自身是高度 opinionated 的工具,主要面向与 Chalkstream 类似的 Django 后端、Vue/React 前端技术栈。
适合你的项目吗?
适用场景
- 将 Obsidian 仓库中的 Markdown 笔记发布为博客文章
- 为产品文档或用户手册构建带层级和面包屑的静态页面
- 在现有 Django + Vue/React 站点中复用前端样式表来渲染博客页面
- 通过 cress index 在宿主应用构建过程中获取文章列表,用于首页最新文章或邮件摘要
- 使用 cress serve --live-reload 在本地写作时实时预览 Markdown 变更
适合人群
- 使用 Obsidian 编写 Markdown 内容并希望生成静态站点的团队
- 已有 Django 后端和 Vue/React 前端、希望复用现有样式与构建流程的项目
- 需要将博客或文档站点与产品仓库放在一起管理的开发者
- 希望通过 Python 插件扩展 Markdown 短代码处理能力的项目
上手与定制指南
结合仓库当前版本的说明,按下面的资料检查内容、配置与运行要求。
- 1
cress 面向 Python 3.14;可通过 uv tool install git+https://github.com/chalkstreamdev/cress 全局安装,或作为 uv 项目依赖添加后使用 uv run cress 调用。
- 2
在目标仓库创建 .cress/config.yaml,至少指定 vault 路径和 output_dir;vault 可以是绝对路径或相对于仓库的路径。
- 3
vault 路径按命令行 --vault、站点配置、用户配置 ~/.config/cress/config.yaml、环境变量 CRESS_VAULT 的顺序解析,只有站点配置会把相对路径解析为相对于仓库。
- 4
使用 cress serve --live-reload 可在本地构建并预览,Markdown 或模板变化时自动重建并刷新浏览器。
- 5
cress publish 会构建、暂存 output_dir 并提交,是否自动推送由 git.auto_push 配置决定。
授权与来源资料
使用条件与限制
免费使用 · 保留许可声明本次收录的是公开仓库中采用 MIT 许可的免费版本;免费范围为该版本代码,不代表外部服务或第三方素材免费。
使用时需要遵守
- 允许免费使用、修改、商用和分发该版本代码。
- 分发代码副本或实质性部分时,保留原作者版权声明和完整 MIT 许可声明。
- 代码按现状提供;担保与责任限制以原许可证及适用法律为准。
额外费用与待核对事项
- 演示图片、字体、图标及其他第三方素材需按各自许可核对,不能仅凭代码的 MIT 许可判断。
- 域名、托管、数据库、支付或 AI 等外部服务可能另外收费。
- 需要 Python 3.14 运行环境,低于该版本的 Python 无法使用。
- 安装方式为从 GitHub 仓库通过 uv 工具安装,需要可访问该仓库并具备 uv 工具链。
- 发布功能依赖用户自身的 git 凭据配置(ssh-agent、系统凭据助手或 CI 注入的认证),未配置时发布将失败。
- 示例配置引用了 Google Fonts 外部样式表,使用该示例会向外部服务发起请求。
- 仅支持 Vite manifest;esbuild 和 webpack 的 manifest 格式不同,需要另行开 issue 扩展。
查看原文依据
Permission is hereby granted, free of charge
许可证原文
cress targets Python 3.14.
README 原文
Cress is a CLI you point at a product repo, so the simplest install is as a global tool straight from GitHub:
README 原文
`cress publish` delegates credentials to the user's git setup — ssh-agent, the system credential helper, or the CI platform's injected auth. cress sets `GIT_TERMINAL_PROMPT=0` so a misconfigured auth stack fails fast rather than hanging, and raises `PublishError` early if `output_dir` is matched by a `.gitignore` rule.
README 原文
- "https://fonts.googleapis.com/css2?family=Inter&display=swap"
README 原文
https://github.com/chalkstreamdev/cress/blob/master/LICENSEhttps://github.com/chalkstreamdev/cress/blob/master/README.mdesbuild and webpack manifests have different shapes — open an issue if you need them; the dispatch behind `vite_manifest` is straightforward to extend.
README 原文
DeepSeek 根据仓库许可证和 README 整理 ·2026-10-15。结果仅覆盖已读取资料,依赖、图片、字体及后续条款变更以各自原文为准。
项目核对记录与介绍依据
模板资料
- 来源平台
- GitHub
- 资料与维护参考分
- 资料较完整 · 85 / 100
- 技术栈
- Django · Vite
- 网站类型
- 博客 · 文档 · 工具站
这个分数如何计算
本站按许可资料(最高30分)、演示链接(12分)、预览图(10分)、Star数(15分)、记录的维护时间(15分)、描述完整度(8分)、功能记录(5分)和分类资料(5分)计算。它不是代码、安全或性能测试,也不是 Google 评分;有链接不代表已实测可用。
这个项目需要核对什么
- 记录的仓库更新时间
- 2026-09-27
- 截图检查日期
- 2026-10-15
已收录演示链接。请用你的内容与屏幕尺寸检查;有演示不代表表单、认证或支付已经可用。
目录记录的主要技术栈是 Django。请通过依赖清单和锁文件核对实际版本与托管要求。这些说明基于收录资料,不代表代码审计或部署实测。
查看具体选型方法 →介绍的制作与核对状态
介绍由 AI 辅助整理目录与仓库资料。仓库作者不等于这篇介绍的审核者。
审核声明: codex-user-requested-batch-review-20261017 · 2026-10-16
查看当时使用的来源资料
SHA-256: 5e86ecf2c53c895166f52ffdc784a94b8a14faf69fa68667aa702bb0c90be62f
2026-10-15T13:03:26.063Z
来源文件 / 仓库# Cress: A markdown static site and blog generator.
Cress is a highly opinionated, Obsidian-vault-to-static site generator for blogs and documentation.
Why you might want to use cress: If you have a similar tech stack to us — Django on the backend,
Vue/React on the frontend — and want to lean on Obsidian's great tooling to write blog posts.
Cress was built specifically for our pipeline at Chalkstream, where we write all our documentation
as simple markdown in Obsidian vaults. Marrying these together into a static site generator made
sense for us over other solutions (Hugo, Astro) which would require more code to fit into our
specific pipeline.
Markdown files are rendered as HTML templates. A single yaml file defines the configuration of the
site. Front matter on `.md` defines page title, subtitle, image, tags and categorization.
Cress doesn't ship with a theme: it is unstyled by design. The idea is that it drops into your existing
site's templates. Cress reads your existing build tool's Vite manifest to link to your stylesheets.
Out of the box there is automatic support for embedded images (hoisted from the attachments folder
of the Obsidian vault), Wikilinks to other posts, in-line tags, header anchors, and
Obsidian-flavored callout boxes. A flexible plugin system allows any project to build simple python
plugins that can hook into shortcodes in the markdown.
Vaults can be converted into blogs, which generates a latest-first list of paginated posts, category
and tag pages, and an RSS Feed. Or you can create static pages, useful for documentation or
user manuals, which builds a full tree-based hierarchy and breadcrumb navigation. All posts are
marked up with correct meta tags and Open Graph properties.
An included `cress serve` will build your full vault and let you browse files locally. An optional
`--live-reload` will rebuild the blog whenever markdown files or templates change. You can also use cress
directly to stage and commit the blog and push it to your live site in one command.
## Install
Cress is a CLI you point at a product repo, so the simplest install is as a global tool straight from GitHub:
```bash
uv tool install git+https://github.com/chalkstreamdev/cress
uv tool update-shell # one-time: puts uv's tool bin dir on PATH
```
That puts a `cress` (and `cress.exe` on Windows) command on your PATH, usable from any directory.
The rest of this README assumes a global `cress`; prefix with `uv run` instead if you installed it
as a project dependency (below).
**Alternative — as a dependency of a uv-managed product repo.** If the repo you're publishing from
is itself a uv project, add cress to it and call it via `uv run`:
```bash
uv add git+https://github.com/chalkstreamdev/cress
uv run cress build
```
**Alternative — clone to develop against cress itself:**
```bash
git clone https://github.com/chalkstreamdev/cress
cd cress
uv sync
uv run cress --help
```
cress targets Python 3.14.
## Quickstart
1. **Create `.cress/config.yaml` in your product repo.** This is the only required step. It points cress at your Obsidian vault and says where to write the output:
```yaml
vault: "../Vault" # path to your Obsidian vault (relative to this repo)
output_dir: "public/blog" # where to write the rendered site (relative to this repo)
site:
title: "My Product Blog"
description: "Shipping notes and deep dives."
base_url: "https://myproduct.com/blog"
```
That's the whole setup — cress is ready to build. By default it publishes the entire vault; to publish only one folder, add `vault_subfolder: "Blogs/MyProduct"` (see [Configuration](#configuration)).
2. **Preview while you write** (builds, serves on localhost, and reloads the browser on every change):
```bash
cress serve --live-reload
```
3. **Build and publish** when you're happy. `build` renders into `output_dir`; `publish` commits it in your repo (and pushes if `git.auto_push: true`):
```bash
cress build # render the site into output_dir
cress publish # commit, and optionally push, the built site
```
Commands run from the product repo root — `--target` defaults to the current directory.
## Telling cress where your vault is
Putting `vault:` in `.cress/config.yaml` (as in the Quickstart) is the simplest setup. The value can be either an **absolute** path or a path **relative to the repo**:
```yaml
# Absolute — points at a fixed location, wherever you run cress from:
vault: "/Users/me/Obsidian/Main"
# Relative — resolved against this repo's root (where .cress/ lives):
vault: "../Vault" # vault sits next to the product repo
vault: "content/vault" # vault lives inside the repo
```
You don't have to keep `vault:` in the site config, though. cress resolves the vault path from the first source available, in this order (most-specific wins):
1. `--vault` on the command line
2. `vault:` in the site config (`<target>/.cress/config.yaml`) — relative paths resolved against the repo
3. `vault:` in the user config (`~/.config/cress/config.yaml`) — use an absolute path
4. the `CRESS_VAULT` environment variable — use an absolute path
Only the site config resolves a relative path against the repo. The user config and `CRESS_VAULT` are read verbatim, so a relative path there would be interpreted from your current working directory — always give those an absolute path. A global default vault for every site you build on a machine goes in the user config:
```yaml
# ~/.config/cress/config.yaml
vault: /Users/me/Obsidian/Main
```
## Commands
| Command | What it does |
| --- | --- |
| `cress build` | Renders the vault into `<output_dir>`. Continues past per-post errors (they surface as warnings). |
| `cress validate` | Parses every post without writing. Exits non-zero on any issue. `--fix` writes missing slugs. |
| `cress serve` | Builds once, serves `<output_dir>` on localhost, rebuilds on source changes. `--live-reload` reloads the browser; `--list-drafts` adds drafts to the article index, linked to their unlisted preview URLs. |
| `cress publish` | Builds, stages `<output_dir>` in the target repo, commits with the configured prefix, and optionally pushes. |
| `cress index` | Emits the post list as structured data without building — for host-app integration. `--drafts` includes drafts; `--json` for the envelope. |
Every command accepts `--config PATH` to build from an alternate config file (default `<target>/.cress/config.yaml`) — this is how one product repo hosts both a blog and a docs site (see [Static pages mode](#static-pages-mode)).
Every command accepts `--json` for a machine-readable envelope:
```json
{
"version": 1,
"ok": true,
"result": {"pages_written": 42, "duration_ms": 312, "skipped_posts": 0},
"warnings": [],
"errors": []
}
```
### Machine-readable post index
`cress index` answers "what posts exist?" without rendering anything — no vite manifest needed,
no disk writes, so a host app can call it before or during its own build (a homepage
latest-posts widget, an email digest, a deploy notification). Without `--json` it prints one
post per line (`<date> <path> <title>`); with `--json` the envelope's `result.posts` holds
one record per post:
```json
{
"version": 1,
"ok": true,
"result": {
"posts": [
{
"title": "Opening rolls, revisited",
"slug": "opening-rolls-revisited",
"path": "/blog/opening-rolls-revisited/",
"url": "https://example.com/blog/opening-rolls-revisited/",
"date": "2026-08-14",
"updated": null,
"summary": "First 160 chars or frontmatter summary…",
"author": "Nick",
"tags": ["study"],
"categories": [],
"reading_time_minutes": 3,
"draft": false,
"image": "/blog/assets/opening-rolls-revisited/3fa9c2d1-hero.png",
"image_alt": "Opening position heatmap"
}
]
},
"warnings": [],
"errors": []
}
```
`path` is prefix-relative (what a same-site consumer embeds in `<a href>`); `url` is absolute
(what RSS emits — for cross-domain consumers). Posts sort newest-first (by `path` in static
pages mode). A post whose frontmatter has no `slug:` yet gets the **same slug `cress build`
will write back later** — the index computes it with the identical pure function, so emitted
URLs are promises that build keeps. `image` is the content-hashed public URL the build will
produce (`null` when absent or missing). Drafts are excluded unless `--drafts` is passed;
included drafts carry their unlisted `_drafts/…` preview path and a ` [draft]` marker in the
human listing.
## Configuration
The full `.cress/config.yaml` schema:
```yaml
# Required
output_dir: "public/blog" # relative to the target repo
# Vault location (see "Telling cress where your vault is" for all sources and
# the absolute-vs-relative rules). A relative path is resolved against this repo.
vault: "../Vault"
# Optional — which folder inside the vault to publish. Omit to publish the
# whole vault; set it to scope a build to one subtree (relative to the vault root).
vault_subfolder: "Blogs/MyProduct"
site:
title: "My Blog"
description: "Fallback meta description"
base_url: "https://myproduct.com/blog"
locale: "en_US"
twitter_handle: "@myproduct"
default_image: "og-default.png"
# og:site_name. Defaults to title; set it when one product ships several cress sites.
name: "My Product"
# Optional
template_dir: "blog-templates"
assets_dir: "public/blog/assets" # must live under output_dir
attachments_subfolder: "_attachments"
paginate: 10
default_author: "Author"
static_pages: false # true → evergreen docs mode (see below)
templates:
post: "blog-templates/my-post.html" # repoint a page type; see "Templates" below
shortcodes:
youtube: "templates/shortcodes/youtube.html"
features:
rss: true
rss_count: 20
sitemap: true
syntax_highlighting: true
json_ld: false
pygments_style: "default"
# Stylesheet wiring (see "Hooking up your build tool's CSS" below)
vite_manifest: "frontend/dist/.vite/manifest.json"
vite_asset_prefix: "/"
extra_stylesheets:
- "https://fonts.googleapis.com/css2?family=Inter&display=swap"
git:
auto_commit: false
auto_push: false
remote: "origin"
commit_prefix: "blog:"
```
### Hooking up your build tool's CSS
The default `base.html` is unstyled — cress doesn't ship a theme. To make blog pages share styles with the rest of your product, point cress at your frontend build's manifest:
```yaml
vite_manifest: "frontend/dist/.vite/manifest.json"
vite_asset_prefix: "/" # default; set to "/app/" if your SPA mounts under a subpath
extra_stylesheets: # optional; appended after the manifest entries
- "https://fonts.googleapis.com/css2?family=Inter&display=swap"
```
cress reads the manifest after every `vite build`, walks every entry chunk (and any chunks they import), and emits one `<link rel="stylesheet">` per resolved CSS asset into `<head>` — in manifest order, with `extra_stylesheets` last. Builds with `cssCodeSplit: false` (including rolldown-vite v8) emit the whole bundle as a top-level `style.css` manifest entry instead of per-chunk `css` arrays; cress picks that up too. No marker comments, no post-build inject script, no duplicated `@theme` blocks. The manifest is read once per build.
`cress serve` mirrors production's URL layout: when `output_dir`'s trailing folders match the `base_url` path (e.g. `dist/blog` served at `/blog`), requests outside the prefix fall back to files under the site root (`dist/`), so manifest-linked CSS, logos, and fonts resolve in preview exactly as they do behind your real web server.
For Tailwind to scan cress's output for utility classes used in markdown bodies, add a `@source` directive in your SPA's CSS pointing at the rendered HTML directory (Tailwind v4 syntax):
```css
@source "../../public/blog/**/*.html";
```
esbuild and webpack manifests have different shapes — open an issue if you need them; the dispatch behind `vite_manifest` is straightforward to extend.
## Templates
cress ships a full set of templates under `defaults/` (`base.html`, `post.html`, `index.html`, `tag.html`, `category.html`, `tag_list.html`, `category_list.html`, the `_meta`/`_nav`/`_breadcrumbs`/`_pagination`/`_post_card` partials, and `sitemap.xml`). These are **real, working templates** — every build renders from them — they're just unstyled, emitting semantic HTML with correct meta tags and Open Graph properties so they inherit your site's CSS. They are not throwaway demos; they're the layer you customise.
Point cress at your own templates with `template_dir` in the config:
```yaml
template_dir: "blog-templates" # relative to the target repo
```
That directory is searched **before** the shipped defaults. From there you have three ways to customise, and they compose freely:
### 1. Extend a default (recommended)
Every shipped page template extends `defaults/base.html` and exposes the blocks `title`, `meta`, `content`, `extra_head`, and `footer`. The shipped `defaults/` are always on the search path, so your template can extend the shipped base and override only the blocks you need:
```django
{# blog-templates/my-post.html #}
{% extends "defaults/base.html" %}
{% block title %}{{ page.title }} — {{ site.title }}{% endblock %}
{% block content %}
<article class="prose">
<h1>{{ page.title }}</h1>
<div class="post-body">{{ page.html|safe }}</div>
</article>
{% endblock %}
```
Then repoint the page type at it via the `templates:` map (see below), or name it `defaults/post.html` to shadow the default outright (see method 3).
### 2. Repoint a page type (`templates:` config map)
Each page type resolves to `defaults/<type>.html` unless you override its name in the `templates:` map:
```yaml
templates:
post: "blog-templates/my-post.html"
index: "blog-templates/landing.html"
```
Recognised keys: `post`, `index`, `tag`, `category`, `tag_list`, `category_list`, and `sitemap`. (RSS is generated programmatically via feedgen and is not a template.) The path is resolved against `template_dir` first, then the shipped defaults.
### 3. Shadow a default (override by same name)
Because `template_dir` is searched first, dropping a file at the **same relative name** as a shipped template overrides it everywhere it's referenced — including the base layout and partials, which the `templates:` map can't reach. For example, to replace the base layout for the whole site:
```
blog-templates/defaults/base.html # shadows the shipped base everywhere
blog-templates/defaults/_meta.html # shadows just the <head> meta partial
```
This is the only way to override `base.html` and the partials — there is no `base` key in the `templates:` map.
## Static pages mode
By default a cress site is a dated, reverse-chronological **blog**. Setting `static_pages: true` flips that whole build to an evergreen **documentation** site:
```yaml
static_pages: true
```
What changes when the flag is on (everything else is identical):
| | Blog (default) | Static pages (`static_pages: true`) |
| --- | --- | --- |
| `date` frontmatter | Required | **Optional** (a dateless page is fine) |
| Output path / URL | Flat `/<slug>/` | **Folder hierarchy preserved** — `guides/install.md` → `/guides/install/` |
| Index order | `date`, newest first | `url_path`, ascending |
| RSS | Generated | **Disabled** (an undated doc set is not a feed) |
| Sitemap | `lastmod` from date | Present; `lastmod` omitted for dateless pages |
| Slug uniqueness | Global | Per-folder — `guides/index.md` and `api/index.md` coexist |
A page's URL mirrors its location under `vault_subfolder`; the file's leaf name is still the slug. `cress validate` reports a dateless page as an informational `missing_date` warning (it does not fail the run). Wikilinks still resolve by filename/title and now point at the target's hierarchical URL.
### Running a blog and a docs site from one repo
Docs live in their **own vault** (or `vault_subfolder`) with their own config and output dir, built as a second, independent cress site that shares the product's CSS bundle exactly as the blog does. Add a second config beside the blog's, e.g. `.cress/docs.config.yaml`:
```yaml
vault_subfolder: "Docs"
output_dir: "public/docs"
static_pages: true
vault: "../DocsVault" # the docs' own Obsidian vault
site:
title: "Docs"
description: "Product documentation"
base_url: "https://myproduct.com/docs"
vite_manifest: "frontend/dist/.vite/manifest.json" # same bundle as the blog
```
Then build each site by selecting its config:
```bash
cress build # the blog (default config)
cress build --config .cress/docs.config.yaml # the docs site
```
Both emit into the same product repo at different paths (`/blog`, `/docs`), each styled by the shared Vite bundle. No "sections" concept — they are simply two cress invocations.
### Navigation (static-pages mode)
In static-pages mode cress reconstructs the vault's folder hierarchy into two navigation primitives, both derived purely from each page's `url_path` and injected into every template's context:
- **`nav`** — a nested tree. The default `base.html` renders it as a recursive sidebar (`_nav.html`), to arbitrary depth.
- **`breadcrumbs`** — the Home → … → current-page trail for the page being rendered (`_breadcrumbs.html`).
Both are gated on `static_pages` in the default templates, so **blog output is byte-for-byte unchanged**. Override `_nav.html` / `_breadcrumbs.html` (or `base.html`) via `template_dir` to restyle them.
How the tree is built:
- **Section landing ↔ folder merge.** A top-level file beside a same-named folder (`position-tagging.md` + `position-tagging/`) becomes **one** node: the file is its clickable page, the folder's contents are its children.
- **Page-less folder.** A folder with no landing file (`api/` with only `api/install.md`) becomes a **non-clickable section header** (`<span>`, no synthesized landing page).
- **Home node.** The auto-generated index is prepended as a synthetic Home node (`title = site.title`), and breadcrumbs always begin with it.
- **Ordering.** Per level: `nav_order` ascending first, then unordered pages alphabetically by label.
Three optional frontmatter fields control a page's place in the tree (all static-pages-only; ignored in blog mode):
| Field | Effect |
| --- | --- |
| `nav_order: <int>` | Sort key within its level (ascending). Unset pages sort after ordered ones, then alphabetically. |
| `nav_title: "<label>"` | Overrides the sidebar/breadcrumb label (falls back to `title`). |
| `nav_hidden: true` | Drops the page from the tree. It still builds and is reachable by URL. |
> Still not in this phase: a dedicated `doc` template type. Static pages render through the existing `post` template, overridable via `templates:` / `template_dir` like any blog.
## Post frontmatter
```yaml
---
# required
title: "Smart Chart Defaults"
date: 2026-04-19
# optional
slug: smart-chart-defaults # cress writes this back if absent
updated: 2026-04-20
author: "Nick"
summary: "One-liner for index and meta description"
image: images/hero.png
image_alt: "Chart picking UI"
og_image: images/share-card.png # social-share card; wins over image for og:image only
categories: [engineering, product]
tags: [charts, defaults, ux]
draft: false # true → builds only to an unlisted /_drafts/ preview URL
canonical: https://example.com/x
# static-pages navigation (ignored in blog mode)
nav_order: 3 # sidebar sort key within its folder level
nav_title: "Phases" # sidebar/breadcrumb label override
nav_hidden: false # true → omit from the sidebar tree (still builds)
---
```
Dates must be ISO 8601. `date` is required in blog mode but optional under [`static_pages`](#static-pages-mode). Missing `slug` values are generated from `title` and written back to the source file at build time. The `nav_*` fields shape the [static-pages sidebar](#navigation-static-pages-mode) and have no effect in blog mode.
## Authoring features
- **Wikilinks:** `[[Target Filename]]` or `[[Target|alias]]` resolve against the site's slug map; broken links render as `<span class="broken-link">` and surface a warning.
- **Embeds:** `![[image.png]]` / `![[video.mp4]]` / `![[audio.mp3]]`, plus `![[another-post.md]]` for single-level markdown transclusion. Obsidian's pipe segment is honored, each `|`-separated piece classified independently: `300` / `300x200` set width/height, `left` / `right` emit a `class="embed-left"`/`"embed-right"` hook (cress ships no float styling — define those classes in your stylesheet), and anything else is alt text — so `![[image.png|Board view|right|300]]` combines all three. Without an alias, alt falls back to the filename stem.
- **Inline tags:** `#tag` in body text is harvested and merged with frontmatter tags (code blocks and ATX heading lines are excluded).
- **Shortcodes:** fenced `` ```name …``` `` blocks dispatch to Python plugins or YAML-rendered Django templates (see `docs/plugins.md`).
- **Heading anchors:** every `<h1..6>` gets a stable `id` slugified from its text.
- **Syntax highlighting:** fenced `` ```python `` renders via pygments and ships a CSS file under `<assets_dir>/pygments-<style>.css`.
## Publishing
`cress publish` delegates credentials to the user's git setup — ssh-agent, the system credential helper, or the CI platform's injected auth. cress sets `GIT_TERMINAL_PROMPT=0` so a misconfigured auth stack fails fast rather than hanging, and raises `PublishError` early if `output_dir` is matched by a `.gitignore` rule.
## Plugins
See `docs/plugins.md` for the six decorators: `@plugin.shortcode`, `@plugin.inline`, `@plugin.template_filter`, `@plugin.template_global`, `@plugin.hook`, `@plugin.page`.
## Development
```bash
uv sync
uv run pytest
uv run mypy --strict src/cress
uv run ruff check .
```
Philosophy, architecture, and the bootstrap plan live under `docs/`.
看看同类模板
对比同技术栈与用途的模板,找到更适合的视觉与内容结构。

django-on-aws
这个仓库与其说是博客模板,不如说是一份把 Django 应用部署到 AWS 的完整示范。
Django · 作品集
django-rest-api-ci-cd-template
这个模板的定位很明确:不是单纯的 Django 项目脚手架,而是把「从代码提交到生产部署」的完整链路打包进来。
Django · 文档

blog-bootstrap
Bootstrap 5 template: Django blog project
Django · 博客

django-blog-culinario
🍲 Blog culinário multipáginas em Django — projeto educacional (modelagem, views, templates, autenticação, adm…
Django · 后台

vituum
⚡⚙️ Fast Prototyping with Template Engines in Vite. Small and fast static site generator for Vite.
Vite · 工具站

tropical
Fast, mostly-just-HTML static sites with islands of client-side JS. Click the "Use this template" button ↖️
Vite · 工具站
