DoIt 测试文章
这是一篇专门用于测试博客渲染能力的示例文章,目标是一次性覆盖常见 Markdown 组件、DoIt 扩展组件以及长代码块的折叠与高亮效果。
1 基础排版
这是普通段落文本。你可以在这里看到 粗体、斜体、删除线、行内代码、以及一个普通链接 Hugo。
同一段里也可以测试转义字符,例如 \*not italic\*、\<div\>、以及路径 /posts/markdown-components-demo/ 的显示效果。
写博客不只是发布结论,更是在沉淀一套可复用的思考过程。
对技术站点来说,好的示例文章本身就是最直接的回归测试。
- 无序列表项一:验证基础项目符号。
- 无序列表项二:验证中英文混排。
- 无序列表项三:验证
inline code出现在列表中的效果。
- 有序列表项一:确认数字序号样式。
- 有序列表项二:确认换行与缩进。
- 有序列表项三:确认渲染与主题间距。
- 任务列表项:Mermaid
- 任务列表项:KaTeX
- 任务列表项:Rust / Go / C++ 长代码块
- 任务列表项:未来继续补更多 shortcode 验收
2 表格与定义列表
| 组件 | 用途 | 本文是否覆盖 |
|---|---|---|
| Markdown 标题 | 测试目录与锚点 | 是 |
| Mermaid | 测试图表 shortcode | 是 |
| KaTeX | 测试公式渲染 | 是 |
| 长代码块 | 测试折叠、行号与高亮 | 是 |
| 图片 | 测试 Markdown 图片与灯箱 | 是 |
DoIt 主题的 goldmark definition list 渲染目前存在 bug
- OAuth 2.0
- 一种授权框架,用于在不暴露用户密码的前提下授予第三方应用受限访问能力。
- PKCE
- Proof Key for Code Exchange,通常和 Authorization Code Flow 一起使用,用于降低授权码被截获后的风险。
- Mermaid
- 一种用接近 Markdown 的文本语法描述流程图、时序图和其他图表的工具。
3 图片与脚注
下面这张图片使用普通 Markdown 图片语法,适合验证远程图片、标题和点击预览行为:
这是一段带脚注的文本,用于验证脚注锚点和回跳行为。OAuth 回调里最容易被忽略的校验通常是 state 与 PKCE 的 code_verifier。1
4 HTML 细节折叠
展开查看额外说明
DoIt 当前配置里启用了 Goldmark 的 unsafe = true,因此类似 details 这样的原生 HTML 片段可以直接写在 Markdown 中。
这对一些轻量交互内容很有帮助,比如 FAQ、可折叠的注意事项和调试备注。
5 Mermaid
时序图
sequenceDiagram
autonumber
actor U as User Browser
participant F as Frontend
participant B as Blog Backend
participant G as Google OAuth
participant API as Google APIs
U->>F: Click "Continue with Google"
F->>B: GET /oauth/google/login
B->>G: 302 Authorization Request\nclient_id + scope + state + PKCE
G-->>U: Render consent page
U->>G: Sign in and approve scopes
G-->>B: 302 /oauth/google/callback?code=...&state=...
B->>B: Validate state and code_verifier
B->>G: POST /token with authorization code
G-->>B: access_token + id_token + refresh_token
B->>API: GET /userinfo with access_token
API-->>B: profile payload
B->>B: Upsert user and create session
B-->>F: Set-Cookie + redirect /
F->>B: GET /api/me with session cookie
B-->>F: Current user profile
F-->>U: Render signed-in state
甘特图
gantt
dateFormat YYYY-MM-DD
title Adding GANTT diagram functionality to mermaid
section A section
Completed task :done, des1, 2014-01-06,2014-01-08
Active task :active, des2, 2014-01-09, 3d
Future task : des3, after des2, 5d
Future task2 : des4, after des3, 5d
section Critical tasks
Completed task in the critical line :crit, done, 2014-01-06,24h
Implement parser and jison :crit, done, after des1, 2d
Create tests for parser :crit, active, 3d
Future task in critical line :crit, 5d
Create tests for renderer :2d
Add to mermaid :1d
类图
classDiagram
Class01 <|-- AveryLongClass : Cool
Class03 *-- Class04
Class05 o-- Class06
Class07 .. Class08
Class09 --> C2 : Where am i?
Class09 --* C3
Class09 --|> Class07
Class07 : equals()
Class07 : Object[] elementData
Class01 : size()
Class01 : int chimp
Class01 : int gorilla
Class08 <--> C2: Cool label
Git 图
gitGraph
commit
branch hotfix
checkout hotfix
commit
branch develop
checkout develop
commit id:"ash" tag:"abc"
branch featureB
checkout featureB
commit type:HIGHLIGHT
checkout main
checkout hotfix
commit type:NORMAL
checkout develop
commit type:REVERSE
checkout featureB
commit
checkout main
merge hotfix
checkout featureB
commit
checkout develop
branch featureA
commit
checkout develop
merge hotfix
checkout featureA
commit
checkout featureB
commit
checkout develop
merge featureA
branch release
checkout release
commit
checkout main
commit
checkout release
merge main
checkout develop
merge release
6 echarts
DoIt 主题支持 JSON、YAML 及 TOML 格式的 echarts
7 KaTeX 公式
行内公式示例:访问令牌有效期可以近似记为 $T_{access} \approx 3600\ \text{s}$,而刷新令牌的生命周期通常远大于访问令牌。
块级公式示例:
8 YouTube 播放器
9 PlantUML
10 WaveDrom
11 Blockquotes
GitHub 风格的 blockquote
Useful information that users should know, even when skimming content.
Helpful advice for doing things better or more easily.
Key information users need to know to achieve their goal.
Urgent info that needs immediate user attention to avoid problems.
Advises about risks or negative outcomes of certain actions.
12 Style
13 Rust 代码块示例
use std::collections::{BTreeMap, HashMap};
const DEFAULT_TTL_SECS: u64 = 3600;
#[derive(Clone, Debug)]
struct User {
id: u64,
name: String,
scopes: Vec<String>,
}
#[derive(Clone, Debug)]
struct TokenRecord {
access_token: String,
ttl_secs: u64,
user: User,
}
struct TokenCache {
store: HashMap<u64, TokenRecord>,
}
impl TokenCache {
fn new() -> Self {
Self { store: HashMap::new() }
}
fn put(&mut self, user: User, access_token: &str, scopes: &str) {
let user_id = user.id;
let scopes = parse_scopes(scopes);
let record = TokenRecord {
access_token: access_token.to_string(),
ttl_secs: DEFAULT_TTL_SECS,
user: User { scopes, ..user },
};
self.store.insert(user_id, record);
}
fn has_scope(&self, user_id: u64, scope: &str) -> bool {
self.store.get(&user_id)
.map(|record| record.user.scopes.iter().any(|value| value == scope))
.unwrap_or(false)
}
fn names(&self) -> Vec<String> {
let mut names = self.store.values().map(|record| record.user.name.clone()).collect::<Vec<_>>();
names.sort();
names
}
}
fn parse_scopes(input: &str) -> Vec<String> {
input.split(',').map(str::trim)
.filter(|value| !value.is_empty())
.map(ToString::to_string)
.collect()
}
fn exchange_code(code: &str) -> Result<String, String> {
if code.len() < 8 {
return Err("authorization code is too short".into());
}
Ok(format!("token_{}", &code[..8]))
}
fn fetch_profile(token: &str) -> Result<User, String> {
if !token.starts_with("token_") {
return Err("invalid token format".into());
}
Ok(User {
id: 42,
name: "demo-user".into(),
scopes: vec!["openid".into(), "email".into()],
})
}
fn build_scope_index(cache: &TokenCache) -> BTreeMap<String, usize> {
let mut stats = BTreeMap::new();
for record in cache.store.values() {
for scope in &record.user.scopes {
*stats.entry(scope.clone()).or_insert(0) += 1;
}
}
stats
}
fn seed_cache() -> TokenCache {
let mut cache = TokenCache::new();
cache.put(User { id: 1, name: "alice".into(), scopes: Vec::new() }, "token_alice", "openid,email,profile");
cache.put(User { id: 2, name: "bob".into(), scopes: Vec::new() }, "token_bob__", "openid");
cache.put(User { id: 3, name: "carol".into(), scopes: Vec::new() }, "token_carol", "openid,calendar");
cache
}
fn main() -> Result<(), String> {
let cache = seed_cache();
println!("known users: {}", cache.names().join(", "));
println!("carol has calendar: {}", cache.has_scope(3, "calendar"));
println!("bob has drive: {}", cache.has_scope(2, "drive"));
for (scope, count) in build_scope_index(&cache) {
println!("{scope}: {count}");
}
let token = exchange_code("abcd1234xyz")?;
let profile = fetch_profile(&token)?;
println!("signed in: {}", profile.name);
println!("granted scopes: {}", profile.scopes.join(", "));
if let Some(record) = cache.store.get(&1) {
println!("alice token preview: {}", &record.access_token[..10]);
}
if let Some(record) = cache.store.get(&1) {
println!("alice ttl: {}", record.ttl_secs);
}
Ok(())
}14 Go 代码块示例
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"sync"
"time"
)
type Session struct {
UserID string `json:"user_id"`
AccessToken string `json:"access_token"`
ExpiresAt time.Time `json:"expires_at"`
}
type Store struct {
mu sync.RWMutex
sessions map[string]Session
}
func NewStore() *Store {
return &Store{sessions: map[string]Session{}}
}
func (s *Store) Save(id string, session Session) {
s.mu.Lock()
defer s.mu.Unlock()
s.sessions[id] = session
}
func (s *Store) Get(id string) (Session, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
session, ok := s.sessions[id]
return session, ok
}
func exchangeCode(ctx context.Context, code string) (string, error) {
_ = ctx
if len(code) < 8 {
return "", fmt.Errorf("authorization code is too short")
}
return "token_" + code[:8], nil
}
func fetchProfile(ctx context.Context, token string) (string, error) {
_ = ctx
if token == "" {
return "", fmt.Errorf("missing token")
}
return "google-user-42", nil
}
func callbackHandler(store *Store) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
code := r.URL.Query().Get("code")
state := r.URL.Query().Get("state")
if code == "" || state == "" {
http.Error(w, "missing code or state", http.StatusBadRequest)
return
}
token, err := exchangeCode(ctx, code)
if err != nil {
http.Error(w, err.Error(), http.StatusBadGateway)
return
}
userID, err := fetchProfile(ctx, token)
if err != nil {
http.Error(w, err.Error(), http.StatusBadGateway)
return
}
session := Session{
UserID: userID,
AccessToken: token,
ExpiresAt: time.Now().Add(1 * time.Hour),
}
store.Save(state, session)
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(session)
}
}
func healthHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
_, _ = w.Write([]byte("ok\n"))
}
func main() {
store := NewStore()
mux := http.NewServeMux()
mux.Handle("/oauth/google/callback", callbackHandler(store))
mux.HandleFunc("/healthz", healthHandler)
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadHeaderTimeout: 3 * time.Second,
}
log.Println("listening on http://127.0.0.1:8080")
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal(err)
}
}
// Long-block check: line numbers should still render clearly.
// Long-block check: syntax highlighting should remain stable.
// Long-block check: default collapsed state should still expand.
// End of Go sample.15 C++ 代码块示例
#include <iostream>
#include <list>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
struct Entry {
std::string key;
int value;
};
class LruCache {
public:
explicit LruCache(std::size_t capacity) : capacity_(capacity) {}
void put(const std::string& key, int value) {
auto it = index_.find(key);
if (it != index_.end()) {
it->second->value = value;
touch(it->second);
return;
}
if (items_.size() == capacity_) {
evict();
}
items_.push_front(Entry{key, value});
index_[key] = items_.begin();
}
std::optional<int> get(const std::string& key) {
auto it = index_.find(key);
if (it == index_.end()) {
return std::nullopt;
}
touch(it->second);
return it->second->value;
}
bool contains(const std::string& key) const {
return index_.find(key) != index_.end();
}
void dump() const {
for (const auto& item : items_) {
std::cout << item.key << ":" << item.value << "\n";
}
}
private:
using List = std::list<Entry>;
using Iter = List::iterator;
void touch(Iter it) {
if (it == items_.begin()) {
return;
}
items_.splice(items_.begin(), items_, it);
}
void evict() {
const auto& last = items_.back();
index_.erase(last.key);
items_.pop_back();
}
std::size_t capacity_;
List items_;
std::unordered_map<std::string, Iter> index_;
};
std::vector<std::string> scenario() {
return {"auth_code", "access_token", "refresh_token", "profile"};
}
int score_key(const std::string& key) {
int sum = 0;
for (char ch : key) {
sum += static_cast<int>(ch);
}
return sum % 100;
}
int main() {
LruCache cache(3);
for (const auto& key : scenario()) {
cache.put(key, score_key(key));
}
cache.put("id_token", 88);
if (auto value = cache.get("profile")) {
std::cout << "profile=" << *value << "\n";
} else {
std::cout << "profile was evicted\n";
}
if (auto value = cache.get("id_token")) {
std::cout << "id_token=" << *value << "\n";
}
cache.put("user_info", 77);
cache.put("session", 66);
std::cout << "contains access_token=" << cache.contains("access_token") << "\n";
std::cout << "contains session=" << cache.contains("session") << "\n";
cache.dump();
return 0;
}
// The following lines intentionally keep the sample long for renderer checks.
// They help verify long block folding in the Hugo theme.
// They also make line number gutters easier to inspect visually.
// For production C++, prefer stronger error handling and tests.
// End of C++ sample.
16 gist 测试
在真实实现里,
state用于抵御 CSRF,code_verifier用于与 PKCE 搭配验证授权码确实由发起登录的客户端申请得到。 ↩︎
