<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/rss/rss-styles.xsl"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Danny&#039;s Blog</title>
    <description>Danny&#039;s personal blog, sharing tech notes, project experiences, and life moments.</description>
    <link>https://my-blog-554.pages.dev/</link>
    <language>en</language>
    <managingEditor>Dannyrojas6</managingEditor>
    <webMaster>Dannyrojas6</webMaster>
    <author>Dannyrojas6</author>
    <pubDate>2026-05-21T11:01:15.438Z</pubDate>
    <lastBuildDate>2026-05-21T11:01:15.438Z</lastBuildDate>
    <generator>Astro Litos Theme</generator>
    <atom:link href="https://my-blog-554.pages.dev//rss.xml" rel="self" type="application/rss+xml" />
    
    <item>
      <title>Claude 技能构建完整指南</title>
      <link>https://my-blog-554.pages.dev//posts/claude-skills-guide</link>
      <guid>https://my-blog-554.pages.dev//posts/claude-skills-guide</guid>
      <updated>2026-05-09T00:00:00.000Z</updated>
      <pubDate>2026-05-09T00:00:00.000Z</pubDate>
      <description><![CDATA[Anthropic 官方发布的 Claude Skill 构建指南中文翻译版，涵盖技能结构、规划设计、测试迭代与分发共享]]></description>
      <content:encoded><![CDATA[<img src="https://my-blog-554.pages.dev/_astro/claude-skills-guide-cover.KshHEXkH_Z2tPOFy.webp" alt="Claude 技能构建完整指南" style="width: 100%; height: auto; margin-bottom: 1em;" />
<h2>引言</h2>
<p>技能（Skill）是一套指令——以简单文件夹的形式打包——用于教会 Claude 如何处理特定任务或工作流。技能是自定义 Claude 以满足特定需求的最强大方式之一。无需在每次对话中重复解释您的偏好、流程和领域专业知识，技能让您一次性教会 Claude，并在每次使用中获益。</p>
<p>当您拥有可重复的工作流时，技能尤为强大：从规格生成前端设计、采用一致方法开展研究、创建符合团队风格指南的文档，或编排多步骤流程。它们与 Claude 的内置功能（如代码执行和文档创建）配合良好。对于构建 MCP 集成的开发者而言，技能提供了另一强大层，帮助将原始工具访问转化为可靠、优化的工作流。</p>
<p>本指南涵盖构建有效技能所需的一切知识——从规划和结构到测试与分发。无论您是为自己、团队还是社区构建技能，都能在其中找到实用模式和真实案例。</p>
<p><strong>您将学到：</strong></p>
<ul>
<li>技能结构的技术要求与最佳实践</li>
<li>独立技能与 MCP 增强工作流的模式</li>
<li>我们在不同用例中观察到的有效模式</li>
<li>如何测试、迭代和分发您的技能</li>
</ul>
<p><strong>适用人群：</strong></p>
<ul>
<li>希望 Claude 始终遵循特定工作流的开发者</li>
<li>希望 Claude 始终遵循特定工作流的进阶用户</li>
<li>希望在整个组织内标准化 Claude 使用方式的团队</li>
</ul>
<h3>本指南的两种阅读路径</h3>
<p><strong>构建独立技能？</strong> 请重点阅读「基础知识」、「规划与设计」以及第 1-2 类用例。</p>
<p><strong>增强 MCP 集成？</strong> 请重点阅读「技能 + MCP」部分以及第 3 类用例。</p>
<p>两种路径共享相同的技术要求，您可根据实际用例选择相关内容。</p>
<p><strong>本指南的学习成果：</strong> 学习结束后，您将能够在一次会话中构建一个功能完整的技能。预计使用 skill-creator 构建并测试您的首个可用技能仅需 15–30 分钟。</p>
<p>让我们开始吧。</p>
<hr />
<h2>第 1 章：基础知识</h2>
<h3>什么是技能？</h3>
<p>技能是一个包含以下内容的文件夹：</p>
<ul>
<li><strong>SKILL.md</strong>（必需）：带有 YAML 前置元数据的 Markdown 指令</li>
<li><strong>scripts/</strong>（可选）：可执行代码（Python、Bash 等）</li>
<li><strong>references/</strong>（可选）：按需加载的文档</li>
<li><strong>assets/</strong>（可选）：输出中使用的模板、字体、图标</li>
</ul>
<h3>核心设计原则</h3>
<h4>渐进式披露（Progressive Disclosure）</h4>
<p>技能采用三级系统：</p>
<ul>
<li><strong>第一级（YAML 前置元数据）：</strong> 始终加载到 Claude 的系统提示中。仅提供足够信息，让 Claude 知道何时应使用每个技能，而无需将其全部加载到上下文中。</li>
<li><strong>第二级（SKILL.md 正文）：</strong> 当 Claude 认为该技能与当前任务相关时加载。包含完整指令和指导。</li>
<li><strong>第三级（链接文件）：</strong> 捆绑在技能目录中的其他文件，Claude 可根据需要选择导航和发现。</li>
</ul>
<p>这种渐进式披露可在保持专业知识的同时最大限度减少 token 使用量。</p>
<h4>可组合性（Composability）</h4>
<p>Claude 可以同时加载多个技能。您的技能应能与其他技能良好协作，而非假定自己是唯一可用的功能。</p>
<h4>可移植性（Portability）</h4>
<p>技能在 Claude.ai、Claude Code 和 API 中表现完全一致。只需创建一次技能，即可在所有界面上正常运行（前提是环境支持技能所需的任何依赖项）。</p>
<h3>面向 MCP 构建者：技能 + 连接器</h3>
<blockquote><p><strong>注意：</strong> 构建不含 MCP 的独立技能？请跳至「规划与设计」——您随时可以返回此处。</p></blockquote>
<p>如果您已经拥有可正常运行的 MCP 服务器，那么最困难的部分已经完成。技能是顶层的知识层——捕捉您已知的工作流和最佳实践，从而让 Claude 能够始终如一地应用它们。</p>
<p><strong>厨房类比</strong></p>
<ul>
<li>MCP 提供专业厨房：访问工具、食材和设备。</li>
<li>技能提供食谱：创建有价值成果的逐步指令。</li>
</ul>
<p>两者结合，使用户无需自行梳理每一步即可完成复杂任务。</p>
<h4>它们如何协同工作</h4>





















<table><thead><tr><th>MCP（连接性）</th><th>技能（知识）</th></tr></thead><tbody><tr><td>将 Claude 连接到您的服务（Notion、Asana、Linear 等）</td><td>教会 Claude 如何有效使用您的服务</td></tr><tr><td>提供实时数据访问和工具调用</td><td>捕捉工作流和最佳实践</td></tr><tr><td>Claude <strong>能做什么</strong></td><td>Claude <strong>应该怎么做</strong></td></tr></tbody></table>
<h4>为什么这对您的 MCP 用户很重要</h4>
<p><strong>没有技能时：</strong></p>
<ul>
<li>用户连接您的 MCP 后不知道下一步该做什么</li>
<li>支持工单询问「如何用您的集成完成 X」</li>
<li>每次对话都从零开始</li>
<li>结果不一致，因为用户每次提示方式不同</li>
<li>用户将问题归咎于您的连接器，而实际原因是缺少工作流指导</li>
</ul>
<p><strong>有技能时：</strong></p>
<ul>
<li>预构建的工作流在需要时自动激活</li>
<li>一致、可靠的工具使用</li>
<li>最佳实践嵌入每次交互</li>
<li>降低您的集成的学习曲线</li>
</ul>
<hr />
<h2>第 2 章：规划与设计</h2>
<h3>从用例开始</h3>
<p>在编写任何代码之前，请确定您的技能应支持的 2–3 个具体用例。</p>
<p><strong>良好的用例定义：</strong></p>
<p><strong>用例：</strong> 项目冲刺规划</p>
<p><strong>触发条件：</strong> 用户说「帮我规划这个冲刺」或「创建冲刺任务」</p>
<p><strong>步骤：</strong></p>
<ol>
<li>从 Linear 获取当前项目状态（通过 MCP）</li>
<li>分析团队速度和容量</li>
<li>建议任务优先级</li>
<li>在 Linear 中创建带有正确标签和估算的任务</li>
</ol>
<p><strong>结果：</strong> 完成规划的冲刺，任务已创建</p>
<p><strong>自问：</strong></p>
<ul>
<li>用户希望完成什么？</li>
<li>这需要哪些多步骤工作流？</li>
<li>需要哪些工具（内置或 MCP）？</li>
<li>应嵌入哪些领域知识或最佳实践？</li>
</ul>
<h3>常见技能用例类别</h3>
<p>在 Anthropic，我们观察到三种常见用例：</p>
<h4>第 1 类：文档与资产创建</h4>
<p><strong>用途：</strong> 创建一致、高质量的输出，包括文档、演示文稿、应用、设计、代码等。</p>
<p><strong>真实示例：</strong> frontend-design 技能（另见 docx、pptx、xlsx 和 ppt 技能）</p>
<blockquote><p>「创建具有高设计质量的独特、生产级前端界面。适用于构建 Web 组件、页面、工件、海报或应用程序。」</p></blockquote>
<p><strong>关键技巧：</strong></p>
<ul>
<li>嵌入样式指南和品牌标准</li>
<li>用于一致输出的模板结构</li>
<li>最终确定前的质量检查清单</li>
<li>无需外部工具——使用 Claude 的内置功能</li>
</ul>
<h4>第 2 类：工作流自动化</h4>
<p><strong>用途：</strong> 受益于一致方法的多步骤流程，包括跨多个 MCP 服务器的协调。</p>
<p><strong>真实示例：</strong> skill-creator 技能</p>
<blockquote><p>「创建新技能的交互式指南。引导用户完成用例定义、前置元数据生成、指令编写和验证。」</p></blockquote>
<p><strong>关键技巧：</strong></p>
<ul>
<li>带验证关卡的逐步工作流</li>
<li>常见结构的模板</li>
<li>内置审查和改进建议</li>
<li>迭代优化循环</li>
</ul>
<h4>第 3 类：MCP 增强</h4>
<p><strong>用途：</strong> 增强 MCP 服务器提供的工具访问的工作流指导。</p>
<p><strong>真实示例：</strong> sentry-code-review 技能（来自 Sentry）</p>
<blockquote><p>「使用 Sentry 的错误监控数据（通过其 MCP 服务器）自动分析并修复 GitHub Pull Request 中检测到的错误。」</p></blockquote>
<p><strong>关键技巧：</strong></p>
<ul>
<li>按顺序协调多个 MCP 调用</li>
<li>嵌入领域专业知识</li>
<li>提供用户原本需要指定的上下文</li>
<li>常见 MCP 问题的错误处理</li>
</ul>
<h3>定义成功标准</h3>
<p><strong>如何判断您的技能是否正常工作？</strong></p>
<p>这些是理想目标——粗略基准而非精确阈值。目标是严谨，但接受存在基于「感觉」的评估成分。我们正在积极开发更稳健的测量指南和工具。</p>
<p><strong>定量指标：</strong></p>
<ul>
<li><strong>技能在 90% 的相关查询中触发</strong>
<ul>
<li><em>测量方法：</em> 运行 10–20 个应触发您技能的测试查询。跟踪自动加载次数与需要显式调用的次数。</li>
</ul>
</li>
<li><strong>在 X 个工具调用内完成工作流</strong>
<ul>
<li><em>测量方法：</em> 比较启用和未启用技能时的相同任务。统计工具调用次数和消耗的 token 总量。</li>
</ul>
</li>
<li><strong>每个工作流 0 次失败的 API 调用</strong>
<ul>
<li><em>测量方法：</em> 在测试运行期间监控 MCP 服务器日志。跟踪重试率和错误代码。</li>
</ul>
</li>
</ul>
<p><strong>定性指标：</strong></p>
<ul>
<li><strong>用户无需提示 Claude 下一步做什么</strong>
<ul>
<li><em>评估方法：</em> 测试期间记录您需要重定向或澄清的频率。向 Beta 用户征求反馈。</li>
</ul>
</li>
<li><strong>工作流无需用户修正即可完成</strong>
<ul>
<li><em>评估方法：</em> 对同一请求运行 3–5 次。比较输出的结构一致性和质量。</li>
</ul>
</li>
<li><strong>跨会话结果一致</strong>
<ul>
<li><em>评估方法：</em> 新用户能否在首次尝试时仅需最少指导即可完成任务？</li>
</ul>
</li>
</ul>
<h3>技术要求</h3>
<h4>文件结构</h4>
<pre><code>your-skill-name/
├── SKILL.md              # 必需 - 主技能文件
├── scripts/              # 可选 - 可执行代码
│   ├── process_data.py   # 示例
│   └── validate.sh       # 示例
├── references/           # 可选 - 文档
│   ├── api-guide.md      # 示例
│   └── examples/         # 示例
└── assets/               # 可选 - 模板等
    └── report-template.md # 示例
</code></pre>
<h4>关键规则</h4>
<p><strong>SKILL.md 命名：</strong></p>
<ul>
<li>必须精确为 <code>SKILL.md</code>（区分大小写）</li>
<li>不接受任何变体（<code>SKILL.MD</code>、<code>skill.md</code> 等）</li>
</ul>
<p><strong>技能文件夹命名：</strong></p>
<ul>
<li>使用 kebab-case：<code>notion-project-setup</code> ✅</li>
<li>不能有空格：<code>Notion Project Setup</code> ❌</li>
<li>不能有下划线：<code>notion_project_setup</code> ❌</li>
<li>不能有大写：<code>NotionProjectSetup</code> ❌</li>
</ul>
<p><strong>无 README.md：</strong></p>
<ul>
<li>不要在技能文件夹内包含 <code>README.md</code></li>
<li>所有文档应放在 <code>SKILL.md</code> 或 <code>references/</code> 中</li>
<li><em>注意：</em> 通过 GitHub 分发时，您仍需为人类用户准备仓库级 <code>README</code> —— 详见「分发与共享」。</li>
</ul>
<h3>YAML 前置元数据：最重要的部分</h3>
<p>YAML 前置元数据是 Claude 决定是否加载您技能的方式。请务必正确设置。</p>
<p><strong>最小必需格式</strong></p>
<pre><code>---
name: your-skill-name
description: 技能功能描述。适用于用户询问 [特定短语] 时。
---
</code></pre>
<p>仅需如此即可开始。</p>
<p><strong>字段要求</strong></p>
<ul>
<li>
<p><strong>name</strong>（必需）：</p>
<ul>
<li>仅限 kebab-case</li>
<li>不能有空格或大写字母</li>
<li>应与文件夹名称一致</li>
</ul>
</li>
<li>
<p><strong>description</strong>（必需）：</p>
<ul>
<li><strong>必须同时包含：</strong>
<ul>
<li>技能的功能</li>
<li>何时使用（触发条件）</li>
</ul>
</li>
<li>少于 1024 字符</li>
<li>不能包含 XML 标签（<code>&lt;</code> 或 <code>&gt;</code>）</li>
<li>包含用户可能说的具体任务</li>
<li>如适用，提及文件类型</li>
</ul>
</li>
<li>
<p><strong>license</strong>（可选）：</p>
<ul>
<li>若技能开源则使用</li>
<li>常见：MIT、Apache-2.0</li>
</ul>
</li>
<li>
<p><strong>compatibility</strong>（可选）：</p>
<ul>
<li>1–500 字符</li>
<li>指示环境要求，例如：目标产品、所需系统包、网络访问需求等</li>
</ul>
</li>
<li>
<p><strong>metadata</strong>（可选）：</p>
<ul>
<li>任何自定义键值对</li>
<li>建议：<code>author</code>、<code>version</code>、<code>mcp-server</code></li>
<li>示例：
<pre><code>metadata:
  author: ProjectHub
  version: 1.0.0
  mcp-server: projecthub
</code></pre>
</li>
</ul>
</li>
</ul>
<p><strong>安全限制</strong></p>
<p><strong>前置元数据中禁止：</strong></p>
<ul>
<li>XML 尖括号（<code>&lt; &gt;</code>）</li>
<li>名称中包含 “claude” 或 “anthropic” 的技能（保留字）</li>
</ul>
<p><strong>原因：</strong> 前置元数据会出现在 Claude 的系统提示中。恶意内容可能注入指令。</p>
<h3>编写有效的技能</h3>
<h4>description 字段</h4>
<p>根据 Anthropic 工程博客所述：「此元数据……仅提供足够信息，让 Claude 知道何时应使用每个技能，而无需将其全部加载到上下文中。」这是渐进式披露的第一级。</p>
<p><strong>结构：</strong></p>
<p><code>[技能功能] + [何时使用] + [关键能力]</code></p>
<p><strong>优质 description 示例：</strong></p>
<pre><code># 优质 - 具体且可操作
description: 分析 Figma 设计文件并生成开发者交接文档。适用于用户上传 .fig 文件、询问「设计规格」、「组件文档」或「设计转代码交接」时。
</code></pre>
<pre><code># 优质 - 包含触发短语
description: 管理 Linear 项目工作流，包括冲刺规划、任务创建和状态跟踪。适用于用户提及「sprint」、「Linear tasks」、「project planning」或要求「create tickets」时。
</code></pre>
<pre><code># 优质 - 清晰价值主张
description: PayFlow 的端到端客户入职工作流。处理账户创建、支付设置和订阅管理。适用于用户说「onboard new customer」、「set up subscription」或「create PayFlow account」时。
</code></pre>
<p><strong>劣质 description 示例：</strong></p>
<pre><code># 过于模糊
description: Helps with projects.
</code></pre>
<pre><code># 缺少触发条件
description: Creates sophisticated multi-page documentation systems.
</code></pre>
<pre><code># 过于技术化，无用户触发短语
description: Implements the Project entity model with hierarchical relationships.
</code></pre>
<h4>编写主指令</h4>
<p>在前置元数据之后，用 Markdown 编写实际指令。</p>
<p><strong>推荐结构：</strong></p>
<p>请根据您的技能调整此模板。将方括号部分替换为具体内容。</p>
<pre><code>---
name: your-skill
description: [...]
---
</code></pre>
<h1>您的技能名称</h1>
<h2>指令</h2>
<h3>步骤 1：[第一个主要步骤]</h3>
<p>清晰说明发生什么。</p>
<p><strong>示例：</strong></p>
<pre><code>python scripts/fetch_data.py --project-id PROJECT_ID
</code></pre>
<p>预期输出：[描述成功时的样子]</p>
<p>（根据需要添加更多步骤）</p>
<h3>示例</h3>
<p><strong>示例 1：[常见场景]</strong></p>
<p>用户说：「Set up a new marketing campaign」</p>
<p><strong>操作：</strong></p>
<ol>
<li>通过 MCP 获取现有营销活动</li>
<li>使用提供的参数创建新营销活动</li>
</ol>
<p><strong>结果：</strong> 营销活动已创建，附带确认链接</p>
<p>（根据需要添加更多示例）</p>
<h3>故障排除</h3>
<p><strong>错误：</strong> [常见错误消息]</p>
<p><strong>原因：</strong> [发生原因]</p>
<p><strong>解决方案：</strong> [如何修复]</p>
<p>（根据需要添加更多错误情况）</p>
<h4>指令编写最佳实践</h4>
<p><strong>具体且可操作</strong></p>
<p>✅ <strong>优质：</strong></p>
<pre><code>运行 `python scripts/validate.py --input {filename}` 检查数据格式。

如果验证失败，常见问题包括：
- 缺少必填字段（请添加到 CSV 中）
- 日期格式无效（请使用 YYYY-MM-DD）
</code></pre>
<p>❌ <strong>劣质：</strong></p>
<p>在继续之前验证数据。</p>
<p><strong>包含错误处理</strong></p>
<pre><code>## 常见问题

### MCP 连接失败

如果看到「Connection refused」：

1. 确认 MCP 服务器正在运行：检查「设置 &gt; 扩展」
2. 确认 API 密钥有效
3. 尝试重新连接：设置 &gt; 扩展 &gt; [您的服务] &gt; 重新连接
</code></pre>
<p><strong>清晰引用捆绑资源</strong></p>
<p>在编写查询前，请查阅 <code>references/api-patterns.md</code> 以了解：</p>
<ul>
<li>速率限制指南</li>
<li>分页模式</li>
<li>错误代码及处理方法</li>
</ul>
<p><strong>使用渐进式披露</strong></p>
<p>保持 <code>SKILL.md</code> 聚焦核心指令。将详细文档移至 <code>references/</code> 并链接到它。（详见「核心设计原则」了解三级系统的工作原理。）</p>
<hr />
<h2>第 3 章：测试与迭代</h2>
<p>技能可根据您的需求以不同严谨程度进行测试：</p>
<ul>
<li><strong>在 Claude.ai 中手动测试</strong> —— 直接运行查询并观察行为。迭代速度快，无需设置。</li>
<li><strong>在 Claude Code 中脚本化测试</strong> —— 自动化测试用例，以便在更改后进行可重复验证。</li>
<li><strong>通过 skills API 进行程序化测试</strong> —— 构建针对定义测试集系统运行的评估套件。</li>
</ul>
<p>请选择与您的质量要求和技能可见度相匹配的方法。内部小团队使用的技能与部署给数千企业用户的技能，测试需求不同。</p>
<blockquote><p><strong>专业提示：</strong> 先针对单个任务迭代，再扩展</p><p>我们发现最有效的技能创建者会先针对单个具有挑战性的任务进行迭代，直到 Claude 成功，然后将获胜方法提取到技能中。这利用了 Claude 的上下文学习能力，比广泛测试提供更快信号。一旦拥有可靠基础，再扩展到多个测试用例以实现覆盖。</p></blockquote>
<h3>推荐测试方法</h3>
<p>根据早期经验，有效的技能测试通常涵盖三个领域：</p>
<h4>1. 触发测试</h4>
<p><strong>目标：</strong> 确保您的技能在正确时机加载。</p>
<p><strong>测试用例：</strong></p>
<ul>
<li>✅ 在明显任务上触发</li>
<li>✅ 在改写请求上触发</li>
<li>❌ 在无关主题上不触发</li>
</ul>
<p><strong>示例测试套件：</strong></p>
<p><strong>应触发：</strong></p>
<ul>
<li>“Help me set up a new ProjectHub workspace”</li>
<li>“I need to create a project in ProjectHub”</li>
<li>“Initialize a ProjectHub project for Q4 planning”</li>
</ul>
<p><strong>不应触发：</strong></p>
<ul>
<li>“What’s the weather in San Francisco?”</li>
<li>“Help me write Python code”</li>
<li>“Create a spreadsheet”（除非 ProjectHub 技能处理表格）</li>
</ul>
<h4>2. 功能测试</h4>
<p><strong>目标：</strong> 验证技能产生正确输出。</p>
<p><strong>测试用例：</strong></p>
<ul>
<li>生成有效输出</li>
<li>API 调用成功</li>
<li>错误处理正常</li>
<li>覆盖边缘情况</li>
</ul>
<p><strong>示例：</strong></p>
<p><strong>测试：</strong> 创建包含 5 个任务的项目</p>
<p><strong>给定：</strong> 项目名称「Q4 Planning」、5 个任务描述</p>
<p><strong>当：</strong> 技能执行工作流</p>
<p><strong>则：</strong></p>
<ul>
<li>ProjectHub 中已创建项目</li>
<li>5 个任务已创建且属性正确</li>
<li>所有任务已关联到项目</li>
<li>无 API 错误</li>
</ul>
<h4>3. 性能对比</h4>
<p><strong>目标：</strong> 证明技能相比基线有所改进。</p>
<p>使用「定义成功标准」中的指标。以下是可能的对比示例。</p>
<p><strong>基线对比：</strong></p>
<p><strong>无技能时：</strong></p>
<ul>
<li>用户每次都提供指令</li>
<li>15 次往返消息</li>
<li>3 次失败的 API 调用需要重试</li>
<li>消耗 12,000 tokens</li>
</ul>
<p><strong>有技能时：</strong></p>
<ul>
<li>自动执行工作流</li>
<li>仅 2 个澄清问题</li>
<li>0 次失败的 API 调用</li>
<li>消耗 6,000 tokens</li>
</ul>
<h3>使用 skill-creator 技能</h3>
<p>skill-creator 技能——可在 Claude.ai 的插件目录中使用，或下载用于 Claude Code——可帮助您构建和迭代技能。如果您拥有 MCP 服务器并知道前 2–3 个工作流，通常可在一次会话中（约 15–30 分钟）构建并测试一个功能完整的技能。</p>
<p><strong>创建技能：</strong></p>
<ul>
<li>从自然语言描述生成技能</li>
<li>生成带前置元数据的格式正确的 <code>SKILL.md</code></li>
<li>建议触发短语和结构</li>
</ul>
<p><strong>审查技能：</strong></p>
<ul>
<li>标记常见问题（模糊描述、缺少触发条件、结构问题）</li>
<li>识别潜在的过度/不足触发风险</li>
<li>根据技能所述目的建议测试用例</li>
</ul>
<p><strong>迭代改进：</strong></p>
<ul>
<li>使用技能后遇到边缘情况或失败时，将这些示例带回 skill-creator</li>
<li>示例：「使用本次对话中识别的问题与解决方案，改进技能处理 [特定边缘情况] 的方式」</li>
</ul>
<p><strong>使用方法：</strong></p>
<p>「Use the skill-creator skill to help me build a skill for [您的用例]」</p>
<p><strong>执行问题：</strong></p>
<ul>
<li>结果不一致</li>
<li>API 调用失败</li>
<li>需要用户修正</li>
</ul>
<p><em>注意：</em> skill-creator 帮助您设计和改进技能，但不执行自动化测试套件或生成定量评估结果。</p>
<p><strong>解决方案：</strong> 改进指令，添加错误处理</p>
<h3>根据反馈迭代</h3>
<p>技能是动态文档。请根据以下内容规划迭代：</p>
<p><strong>触发不足信号：</strong></p>
<ul>
<li>技能未在应触发时加载</li>
<li>用户手动启用</li>
<li>关于何时使用的支持问题</li>
</ul>
<p><strong>解决方案：</strong> 在 description 中添加更多细节和细微差别——可能包括技术术语的关键字</p>
<p><strong>过度触发信号：</strong></p>
<ul>
<li>技能为无关查询加载</li>
<li>用户禁用</li>
<li>对用途感到困惑</li>
</ul>
<p><strong>解决方案：</strong> 添加否定触发条件，更具体</p>
<hr />
<h2>第 4 章：分发与共享</h2>
<p>技能让您的 MCP 集成更完整。当用户比较连接器时，带有技能的连接器能更快实现价值，为您带来超越仅 MCP 替代方案的优势。</p>
<h3>当前分发模式（2026 年 1 月）</h3>
<p><strong>个人用户获取技能的方式：</strong></p>
<ol>
<li>下载技能文件夹</li>
<li>压缩文件夹（如果需要）</li>
<li>通过「设置 &gt; 功能 &gt; 技能」上传到 Claude.ai</li>
<li>或放置在 Claude Code 技能目录中</li>
</ol>
<p><strong>组织级技能：</strong></p>
<ul>
<li>管理员可工作区范围部署技能（2025 年 12 月 18 日上线）</li>
<li>自动更新</li>
<li>集中管理</li>
</ul>
<h3>通过 API 使用技能</h3>
<p>对于程序化用例——例如构建利用技能的应用、代理或自动化工作流——API 提供对技能管理和执行的直接控制。</p>
<p><strong>关键功能：</strong></p>
<ul>
<li><code>/v1/skills</code> 端点用于列出和管理技能</li>
<li>通过 <code>container.skills</code> 参数将技能添加到 Messages API 请求</li>
<li>通过 Claude Console 进行版本控制和管理</li>
<li>与 Claude Agent SDK 配合构建自定义代理</li>
</ul>
<p><strong>何时使用 API vs Claude.ai：</strong></p>

































<table><thead><tr><th>用例</th><th>最佳界面</th></tr></thead><tbody><tr><td>最终用户直接与技能交互</td><td>Claude.ai / Claude Code</td></tr><tr><td>开发期间手动测试和迭代</td><td>Claude.ai / Claude Code</td></tr><tr><td>个人、临时工作流</td><td>Claude.ai / Claude Code</td></tr><tr><td>程序化使用技能的应用</td><td>API</td></tr><tr><td>大规模生产部署</td><td>API</td></tr><tr><td>自动化管道和代理系统</td><td>API</td></tr></tbody></table>
<p><em>注意：</em> API 中的技能需要 Code Execution Tool 测试版，该测试版提供技能运行所需的安全环境。</p>
<h3>开放标准</h3>
<p>我们已将 Agent Skills 发布为开放标准。与 MCP 类似，我们相信技能应跨工具和平台可移植——无论您使用 Claude 还是其他 AI 平台，同一技能都应正常工作。不过，某些技能旨在充分利用特定平台的能力；作者可在技能的 <code>compatibility</code> 字段中注明。我们一直在与生态系统成员合作制定该标准，并对早期采用感到兴奋。</p>
<h3>今日推荐方法</h3>
<p>首先在 GitHub 上托管您的技能，使用公开仓库、清晰的 README（供人类访问者使用——这与您的技能文件夹分开，后者不应包含 <code>README.md</code>）以及带截图的使用示例。然后在您的 MCP 文档中添加一节，链接到该技能，解释同时使用两者的价值，并提供快速入门指南。</p>
<p><strong>技能定位</strong></p>
<p>您如何描述技能决定了用户是否理解其价值并真正尝试使用。在编写技能相关内容（README、文档或营销材料）时，请牢记以下原则。</p>
<p><strong>关注结果而非功能：</strong></p>
<p>✅ <strong>优质：</strong></p>
<p>「ProjectHub 技能让团队能够在几秒钟内设置完整的项目工作区——包括页面、数据库和模板——而非手动设置花费 30 分钟。」</p>
<p>❌ <strong>劣质：</strong></p>
<p>「ProjectHub 技能是一个包含 YAML 前置元数据和 Markdown 指令的文件夹，用于调用我们的 MCP 服务器工具。」</p>
<p><strong>突出 MCP + 技能的故事：</strong></p>
<p>「我们的 MCP 服务器让 Claude 能够访问您的 Linear 项目。我们的技能教会 Claude 您团队的冲刺规划工作流。两者结合，实现 AI 驱动的项目管理。」</p>
<p><strong>安装指南（推荐结构）</strong></p>
<ol>
<li>
<p><strong>托管在 GitHub 上</strong></p>
<ul>
<li>面向开源技能的公开仓库</li>
<li>清晰的安装说明 README</li>
<li>使用示例和截图</li>
</ul>
</li>
<li>
<p><strong>在您的 MCP 仓库中记录</strong></p>
<ul>
<li>从 MCP 文档链接到技能</li>
<li>解释同时使用两者的价值</li>
<li>提供快速入门指南</li>
</ul>
</li>
<li>
<p><strong>创建安装指南</strong></p>
<p><strong>安装 [您的服务] 技能</strong></p>
<ol>
<li>下载技能：
<ul>
<li>克隆仓库：<code>git clone https://github.com/yourcompany/skills</code></li>
<li>或从 Releases 下载 ZIP</li>
</ul>
</li>
<li>在 Claude 中安装：
<ul>
<li>打开 Claude.ai &gt; 设置 &gt; 技能</li>
<li>点击「上传技能」</li>
<li>选择技能文件夹（已压缩）</li>
</ul>
</li>
<li>启用技能：
<ul>
<li>开启 [您的服务] 技能</li>
<li>确保您的 MCP 服务器已连接</li>
</ul>
</li>
<li>测试：
<ul>
<li>询问 Claude：「Set up a new project in [您的服务]」</li>
</ul>
</li>
</ol>
</li>
</ol>
<hr />
<h2>第 5 章：模式与故障排除</h2>
<p>这些模式来自早期采用者和内部团队创建的技能。它们代表我们观察到的有效常见方法，而非规定性模板。</p>
<h3>选择方法：问题优先 vs 工具优先</h3>
<p>将其想象成 Home Depot。您可能带着问题走进商店——「我需要修理厨房柜子」——员工会为您指出正确的工具。或者您可能先挑选一把新钻头，然后询问如何用于特定工作。</p>
<p>技能的工作方式相同：</p>
<ul>
<li><strong>问题优先：</strong> 「我需要设置项目工作区」→ 您的技能按正确顺序编排正确的 MCP 调用。用户描述结果；技能处理工具。</li>
<li><strong>工具优先：</strong> 「我已连接 Notion MCP」→ 您的技能教会 Claude 最优工作流和最佳实践。用户拥有访问权限；技能提供专业知识。</li>
</ul>
<p>大多数技能偏向一个方向。了解哪种框架适合您的用例，有助于您选择下方合适的模式。</p>
<h3>模式 1：顺序工作流编排</h3>
<p><strong>适用场景：</strong> 用户需要按特定顺序执行的多步骤流程。</p>
<p><strong>示例结构：</strong></p>
<pre><code># 工作流：新客户入职

## 步骤 1：创建账户

调用 MCP 工具：`create_customer`
参数：name, email, company

## 步骤 2：设置支付

调用 MCP 工具：`setup_payment_method`
等待：支付方式验证

## 步骤 3：创建订阅

调用 MCP 工具：`create_subscription`
参数：plan_id, customer_id（来自步骤 1）

## 步骤 4：发送欢迎邮件

调用 MCP 工具：`send_email`
模板：welcome_email_template
</code></pre>
<p><strong>关键技巧：</strong></p>
<ul>
<li>明确的步骤顺序</li>
<li>步骤之间的依赖关系</li>
<li>每阶段验证</li>
<li>失败回滚指令</li>
</ul>
<h3>模式 2：多 MCP 协调</h3>
<p><strong>适用场景：</strong> 工作流跨越多个服务。</p>
<p><strong>示例：</strong> 设计到开发交接</p>
<pre><code># 阶段 1：设计导出（Figma MCP）

1. 从 Figma 导出设计资产
2. 生成设计规格
3. 创建资产清单

# 阶段 2：资产存储（Drive MCP）

1. 在 Drive 中创建项目文件夹
2. 上传所有资产
3. 生成可共享链接

# 阶段 3：任务创建（Linear MCP）

1. 创建开发任务
2. 将资产链接附加到任务
3. 分配给工程团队

# 阶段 4：通知（Slack MCP）

1. 在 #engineering 发布交接摘要
2. 包含资产链接和任务引用
</code></pre>
<p><strong>关键技巧：</strong></p>
<ul>
<li>清晰的阶段分离</li>
<li>MCP 之间的数据传递</li>
<li>进入下一阶段前的验证</li>
<li>集中错误处理</li>
</ul>
<h3>模式 3：迭代优化</h3>
<p><strong>适用场景：</strong> 输出质量通过迭代提升。</p>
<p><strong>示例：</strong> 报告生成</p>
<pre><code># 迭代报告创建

## 初始草稿

1. 通过 MCP 获取数据
2. 生成第一版报告
3. 保存到临时文件

## 质量检查

1. 运行验证脚本：`scripts/check_report.py`
2. 识别问题：
   - 缺少章节
   - 格式不一致
   - 数据验证错误

## 优化循环

1. 解决每个已识别问题
2. 重新生成受影响章节
3. 重新验证
4. 重复直到达到质量阈值

## 最终确定

1. 应用最终格式
2. 生成摘要
3. 保存最终版本
</code></pre>
<p><strong>关键技巧：</strong></p>
<ul>
<li>明确的质</li>
</ul>
<p><strong>关键技巧：</strong></p>
<ul>
<li>明确的质</li>
</ul>
<p>量标准</p>
<ul>
<li>迭代改进</li>
<li>验证脚本</li>
<li>知道何时停止迭代</li>
</ul>
<h3>模式 4：上下文感知的工具选择</h3>
<p><strong>适用场景：</strong> 相同结果，不同上下文使用不同工具。</p>
<p><strong>示例：</strong> 文件存储</p>
<pre><code># 智能文件存储

## 决策树

1. 检查文件类型和大小
2. 确定最佳存储位置：
   - 大文件（&gt;10MB）：使用云存储 MCP
   - 协作文档：使用 Notion/Docs MCP
   - 代码文件：使用 GitHub MCP
   - 临时文件：使用本地存储

## 执行存储

根据决策：
- 调用相应的 MCP 工具
- 应用特定于服务的元数据
- 生成访问链接

## 向用户提供上下文

解释为何选择该存储方式
</code></pre>
<p><strong>关键技巧：</strong></p>
<ul>
<li>清晰的决策标准</li>
<li>回退选项</li>
<li>选择透明度</li>
</ul>
<h3>模式 5：领域特定智能</h3>
<p><strong>适用场景：</strong> 您的技能添加了超出工具访问的专门知识。</p>
<p><strong>示例：</strong> 金融合规</p>
<pre><code># 带合规的支付处理

## 处理前（合规检查）

1. 通过 MCP 获取交易详情
2. 应用合规规则：
   - 检查制裁名单
   - 验证管辖区允许范围
   - 评估风险级别
3. 记录合规决策

## 处理

如果合规通过：
  - 调用支付处理 MCP 工具
  - 应用适当的欺诈检查
  - 处理交易
否则：
  - 标记待审核
  - 创建合规案例

## 审计轨迹

- 记录所有合规检查
- 记录处理决策
- 生成审计报告
</code></pre>
<p><strong>关键技巧：</strong></p>
<ul>
<li>逻辑中嵌入领域专业知识</li>
<li>操作前合规</li>
<li>全面文档</li>
<li>清晰治理</li>
</ul>
<h3>故障排除</h3>
<h4>技能无法上传</h4>
<p><strong>错误：</strong> “Could not find SKILL.md in uploaded folder”</p>
<p><strong>原因：</strong> 文件未精确命名为 <code>SKILL.md</code></p>
<p><strong>解决方案：</strong></p>
<ul>
<li>重命名为 <code>SKILL.md</code>（区分大小写）</li>
<li>使用 <code>ls -la</code> 验证应显示 <code>SKILL.md</code></li>
</ul>
<p><strong>错误：</strong> “Invalid frontmatter”</p>
<p><strong>原因：</strong> YAML 格式问题</p>
<p><strong>常见错误：</strong></p>
<pre><code># 错误 - 缺少分隔符
name: my-skill
description: Does things
</code></pre>
<pre><code># 错误 - 未闭合引号
name: my-skill
description: "Does things
</code></pre>
<pre><code># 正确
---
name: my-skill
description: Does things
---
</code></pre>
<p><strong>错误：</strong> “Invalid skill name”</p>
<p><strong>原因：</strong> 名称包含空格或大写字母</p>
<pre><code># 错误
name: My Cool Skill

# 正确
name: my-cool-skill
</code></pre>
<h4>技能未触发</h4>
<p><strong>症状：</strong> 技能从未自动加载</p>
<p><strong>修复：</strong> 修改您的 description 字段。参阅「description 字段」中的优质/劣质示例。</p>
<p><strong>快速检查清单：</strong></p>
<ul>
<li>是否过于通用？（“Helps with projects” 无法工作）</li>
<li>是否包含用户实际会说的触发短语？</li>
<li>如适用，是否提及相关文件类型？</li>
</ul>
<p><strong>调试方法：</strong></p>
<p>询问 Claude：「When would you use the [skill name] skill？」Claude 会引用 description 内容。根据缺失部分进行调整。</p>
<h4>技能触发过于频繁</h4>
<p><strong>症状：</strong> 技能为无关查询加载</p>
<p><strong>解决方案：</strong></p>
<ol>
<li>
<p><strong>添加否定触发条件</strong></p>
<pre><code>description: Advanced data analysis for CSV files. Use for statistical modeling, regression, clustering. Do NOT use for simple data exploration (use data-viz skill instead).
</code></pre>
</li>
<li>
<p><strong>更加具体</strong></p>
<pre><code># 过于宽泛
description: Processes documents

# 更具体
description: Processes PDF legal documents for contract review
</code></pre>
</li>
<li>
<p><strong>澄清范围</strong></p>
<pre><code>description: PayFlow payment processing for e-commerce. Use specifically for online payment workflows, not for general financial queries.
</code></pre>
</li>
</ol>
<h4>指令未被遵循</h4>
<p><strong>症状：</strong> 技能加载但 Claude 不遵循指令</p>
<p><strong>常见原因：</strong></p>
<ol>
<li>
<p><strong>指令过于冗长</strong></p>
<ul>
<li>保持指令简洁</li>
<li>使用项目符号和编号列表</li>
<li>将详细参考移至单独文件</li>
</ul>
</li>
<li>
<p><strong>指令被埋没</strong></p>
<ul>
<li>将关键指令放在顶部</li>
<li>使用 <code>## Important</code> 或 <code>## Critical</code> 标题</li>
<li>必要时重复关键点</li>
</ul>
</li>
<li>
<p><strong>语言模糊</strong></p>
<pre><code># 劣质
Make sure to validate things properly

# 优质
CRITICAL: Before calling create_project, verify:
- Project name is non-empty
- At least one team member assigned
- Start date is not in the past
</code></pre>
<p><strong>高级技巧：</strong> 对于关键验证，考虑捆绑一个以编程方式执行检查的脚本，而非依赖语言指令。代码是确定性的；语言解释则不是。参阅 Office 技能中的此模式示例。</p>
</li>
<li>
<p><strong>模型「偷懒」</strong> —— 添加明确鼓励：</p>
<pre><code>## Performance Notes

- Take your time to do this thoroughly
- Quality is more important than speed
- Do not skip validation steps
</code></pre>
<p><em>注意：</em> 将此添加到用户提示中比放在 <code>SKILL.md</code> 中更有效</p>
</li>
</ol>
<h4>MCP 连接问题</h4>
<p><strong>症状：</strong> 技能加载但 MCP 调用失败</p>
<p><strong>检查清单：</strong></p>
<ol>
<li>
<p><strong>验证 MCP 服务器已连接</strong></p>
<ul>
<li>Claude.ai：设置 &gt; 扩展 &gt; [您的服务]</li>
<li>应显示「Connected」状态</li>
</ul>
</li>
<li>
<p><strong>检查身份验证</strong></p>
<ul>
<li>API 密钥有效且未过期</li>
<li>已授予适当权限/范围</li>
<li>OAuth 令牌已刷新</li>
</ul>
</li>
<li>
<p><strong>独立测试 MCP</strong></p>
<ul>
<li>要求 Claude 直接调用 MCP（不使用技能）</li>
<li>“Use [Service] MCP to fetch my projects”</li>
<li>如果失败，问题出在 MCP 而非技能</li>
</ul>
</li>
<li>
<p><strong>验证工具名称</strong></p>
<ul>
<li>技能引用正确的 MCP 工具名称</li>
<li>检查 MCP 服务器文档</li>
<li>工具名称区分大小写</li>
</ul>
</li>
</ol>
<h4>大上下文问题</h4>
<p><strong>症状：</strong> 技能响应缓慢或质量下降</p>
<p><strong>原因：</strong></p>
<ul>
<li>技能内容过大</li>
<li>同时启用的技能过多</li>
<li>加载了全部内容而非渐进式披露</li>
</ul>
<p><strong>解决方案：</strong></p>
<ol>
<li>
<p><strong>优化 SKILL.md 大小</strong></p>
<ul>
<li>将详细文档移至 <code>references/</code></li>
<li>链接到 references 而非内联</li>
<li>保持 <code>SKILL.md</code> 少于 5,000 字</li>
</ul>
</li>
<li>
<p><strong>减少启用的技能</strong></p>
<ul>
<li>评估是否同时启用了超过 20–50 个技能</li>
<li>建议选择性启用</li>
<li>考虑为相关功能创建技能「包」</li>
</ul>
</li>
</ol>
<hr />
<h2>第 6 章：资源与参考</h2>
<p>如果您正在构建首个技能，请先从「最佳实践指南」开始，然后根据需要参考 API 文档。</p>
<h3>官方文档</h3>
<p><strong>Anthropic 资源：</strong></p>
<ul>
<li>最佳实践指南</li>
<li>技能文档</li>
<li>API 参考</li>
<li>MCP 文档</li>
</ul>
<p><strong>博客文章：</strong></p>
<ul>
<li>介绍 Agent Skills</li>
<li>工程博客：为真实世界装备代理</li>
<li>技能详解</li>
<li>如何为 Claude 创建技能</li>
<li>为 Claude Code 构建技能</li>
<li>通过技能改进前端设计</li>
</ul>
<h3>示例技能</h3>
<p><strong>公开技能仓库：</strong></p>
<ul>
<li>GitHub: anthropics/skills</li>
<li>包含 Anthropic 创建的技能，您可以自定义</li>
</ul>
<p><strong>skill-creator 技能：</strong></p>
<ul>
<li>内置于 Claude.ai 并可用于 Claude Code</li>
<li>可从描述生成技能</li>
<li>提供审查和建议</li>
<li>使用：「Help me build a skill using skill-creator」</li>
</ul>
<p><strong>验证：</strong></p>
<ul>
<li>skill-creator 可评估您的技能</li>
<li>询问：「Review this skill and suggest improvements」</li>
</ul>
<h3>获取支持</h3>
<p><strong>技术问题：</strong></p>
<ul>
<li>一般问题：Claude 开发者 Discord 社区论坛</li>
</ul>
<p><strong>错误报告：</strong></p>
<ul>
<li>GitHub Issues: anthropics/skills/issues</li>
<li>请包含：技能名称、错误消息、重现步骤</li>
</ul>
<h3>参考 A：快速检查清单</h3>
<p>使用此检查清单在上传前后验证您的技能。如果希望更快开始，请使用 skill-creator 技能生成初稿，然后逐项检查以确保无遗漏。</p>
<p><strong>开始前</strong></p>
<ul>
<li> 已确定 2–3 个具体用例</li>
<li> 已确定工具（内置或 MCP）</li>
<li> 已阅读本指南和示例技能</li>
<li> 已规划文件夹结构</li>
</ul>
<p><strong>开发期间</strong></p>
<ul>
<li> 文件夹名称使用 kebab-case</li>
<li> SKILL.md 文件存在（精确拼写）</li>
<li> YAML 前置元数据有 <code>---</code> 分隔符</li>
<li> <code>name</code> 字段：kebab-case，无空格，无大写</li>
<li> <code>description</code> 包含「做什么」和「何时用」</li>
<li> 任何位置无 XML 标签（<code>&lt; &gt;</code>）</li>
<li> 指令清晰且可操作</li>
<li> 包含错误处理</li>
<li> 提供示例</li>
<li> 清晰链接 references</li>
</ul>
<p><strong>上传前</strong></p>
<ul>
<li> 已测试在明显任务上触发</li>
<li> 已测试在改写请求上触发</li>
<li> 已验证在无关主题上不触发</li>
<li> 功能测试通过</li>
<li> 工具集成正常（如适用）</li>
<li> 已压缩为 .zip 文件</li>
</ul>
<p><strong>上传后</strong></p>
<ul>
<li> 在真实对话中测试</li>
<li> 监控触发不足/过度情况</li>
<li> 收集用户反馈</li>
<li> 迭代 description 和指令</li>
<li> 更新 metadata 中的版本</li>
</ul>
<h3>参考 B：YAML 前置元数据</h3>
<p><strong>必需字段</strong></p>
<pre><code>---
name: skill-name-in-kebab-case
description: 技能功能描述及使用时机。包含具体触发短语。
---
</code></pre>
<p><strong>所有可选字段</strong></p>
<pre><code>name: skill-name
description: [必需描述]
license: MIT                    # 可选：开源许可证
allowed-tools: "Bash(python:*) Bash(npm:*) WebFetch"  # 可选：限制工具访问
metadata:                       # 可选：自定义字段
  author: Company Name
  version: 1.0.0
  mcp-server: server-name
  category: productivity
  tags: [project-management, automation]
  documentation: https://example.com/docs
  support: support@example.com
</code></pre>
<p><strong>安全说明</strong></p>
<p><strong>允许：</strong></p>
<ul>
<li>任何标准 YAML 类型（字符串、数字、布尔值、列表、对象）</li>
<li>自定义 metadata 字段</li>
<li>长描述（最多 1024 字符）</li>
</ul>
<p><strong>禁止：</strong></p>
<ul>
<li>XML 尖括号（<code>&lt; &gt;</code>）——安全限制</li>
<li>YAML 中的代码执行（使用安全 YAML 解析）</li>
<li>名称以 “claude” 或 “anthropic” 为前缀的技能（保留字）</li>
</ul>
<h3>参考 C：完整技能示例</h3>
<p>以下是展示本指南中模式的完整、生产就绪技能：</p>
<ul>
<li><strong>文档技能</strong> —— PDF、DOCX、PPTX、XLSX 创建</li>
<li><strong>示例技能</strong> —— 各种工作流模式</li>
<li><strong>合作伙伴技能目录</strong> —— 查看来自 Asana、Atlassian、Canva、Figma、Sentry、Zapier 等合作伙伴的技能</li>
</ul>
<p>这些仓库会保持更新，并包含本指南未涵盖的更多示例。克隆它们，根据您的用例修改，并将其用作模板。</p>
<hr />
<p><em>claude.ai</em></p>]]></content:encoded>
      <author>Dannyrojas6</author>
      <category>Claude</category><category>AI</category><category>Guide</category>
    </item>
    <item>
      <title>Edit编辑器：极速轻量体验</title>
      <link>https://my-blog-554.pages.dev//posts/microsoft-edit</link>
      <guid>https://my-blog-554.pages.dev//posts/microsoft-edit</guid>
      <updated>2026-05-09T00:00:00.000Z</updated>
      <pubDate>2026-05-09T00:00:00.000Z</pubDate>
      <description><![CDATA[微软开源了一款基于 Rust 的轻量级终端文本编辑器 Edit，只有几百 KB，致敬经典 MS-DOS Editor 的同时融合了 VS Code 的现代化操作体验。]]></description>
      <content:encoded><![CDATA[
<p>第一次听说 Edit 是在 X，在我的 X 时间线上持续了几天时间吧，后面就再也没看到过了。这个的亮点是基于 Rust 开发，打包后大小只有几百KB，并且支持部分语言语法高亮，令人印象深刻的是轻量+极致性能，作为一个代码编辑器该有的功能都有，支持同时打开多个文件，Ctrl+P 快速切换，Ctrl+R 查找替换。</p>
<p>但是话又说回来，其实 Edit 的使用场景比较尴尬，如果是和轻量的那一档去比，那不如Vim，和大型编辑器比不如 VS Code、Zed 这些，甚至写这篇文章的时候我都快忘记 Edit 有哪些功能了，当然如果你想要一个打开即用的终端可视化编辑器，Edit 也是个不错的选择。</p>
<img src="assets/microsoft-edit-01.png" alt="" />]]></content:encoded>
      <author>Dannyrojas6</author>
      <category>Microsoft</category><category>Rust</category><category>Editor</category>
    </item>
    <item>
      <title>CMD和PowerShell的爱恨情仇</title>
      <link>https://my-blog-554.pages.dev//posts/cmd-and-powershell</link>
      <guid>https://my-blog-554.pages.dev//posts/cmd-and-powershell</guid>
      <updated>2026-05-08T00:00:00.000Z</updated>
      <pubDate>2026-05-08T00:00:00.000Z</pubDate>
      <description><![CDATA[对比 Windows 两大命令行工具 CMD 和 PowerShell 的历史、差异与使用场景]]></description>
      <content:encoded><![CDATA[
<p>从第一次接触电脑那会儿开始，CMD 和 PowerShell 早已在 Windows 系统里共存多年了，简单来说二者设计理念不同，CMD 要比 PowerShell 诞生的早，最早可以追溯到 MS-DOS 时代，作为系统内置的命令解释器，接收字符串输入方式并返回输出，命令执行后直接显示纯文本结果，正因如此 CMD 才能如此启动迅速、资源占用低。但问题也很明显，CMD 只适合完成一次性任务，大规模自动化脚本运行会显得力不从心。</p>
<p>PowerShell 的出现是为了弥补 CMD 的不足，它在 2006 年由 Windows Vista 推出，核心点在于采用面向对象架构，每个命令返回的是包含属性和方法的对象，通过管道操作符，用户可以直接操作对象的具体某一属性。同理 PowerShell 也存在一些缺陷，比如命令非常难记，跟 bash 差别很大，学习成本高。</p>
<p>一句话总结，CMD 的基础命令适合日常快速任务，PowerShell 更适用于复杂环境下的系统自动化。</p>]]></content:encoded>
      <author>Dannyrojas6</author>
      <category>Windows</category><category>CMD</category><category>PowerShell</category>
    </item>
    <item>
      <title>为什么我偏爱Zed</title>
      <link>https://my-blog-554.pages.dev//posts/zed</link>
      <guid>https://my-blog-554.pages.dev//posts/zed</guid>
      <updated>2026-05-08T00:00:00.000Z</updated>
      <pubDate>2026-05-08T00:00:00.000Z</pubDate>
      <description><![CDATA[Zed 是一款为速度而生、支持人与 AI 协作的代码编辑器]]></description>
      <content:encoded><![CDATA[
<p>两年前刷 X 看到有人用 Zed 切标签页，直接顶满MacBook Pro 的 120Hz。当时主力 VS Code，七八十个插件，启动越来越慢，但也没动力换。看到那个视频后，我开始等 Windows 版，等了近一年时间。</p>
<p>下载那天的印象，启动非常快，界面干净，没有像 VS Code 一样的厚重感，这才是一个编辑器该有的样子。</p>
<p>但有些槽点，比如插件生态，扩展太少了，跟 VS Code 完全比不了，虽然官方内置的已经足够用了，但是如果用户想要一些其他编辑器独有的插件，那就做不到，主题也是，热门的那几个我都不太喜欢，好在后面找到Nightfox，就是有些细节还需打磨，比如文本选中状态和背景是统一颜色，这个太搞了。</p>
<p>最难受的是Markdown 预览，这个甚至都不如Windows记事本自带的预览好看，特别离谱，Markdown 渲染的时候，行间的空行会自动删除，导致所有东西全部挤在一起，效果给我的感觉不如我直接看Markdown 源格式，至少层次分明一点，后续估计 Zed 会针对这个单独优化。</p>
<p>其他的没什么了，Git 集成不错，自研的ACP也还可以，就是配置藏的很深，有点逼着用户用Zed AI，至于哪次更新让我惊喜，没有，因为我觉得 Zed 一开始就把大部分事情完成了，后续就是优化迭代，也就是1.0版本推出的Agent对话历史切换和Git Graph支持让我眼前一亮。</p>
<p>至于是否取代 VS Code，那肯定是不可能的，就只论插件生态这方面，Zed 几年内都追不上。</p>
<p>总结下，Zed 是一款优秀的编辑器，基于 Rust 开发，足够快，内存占用少，开箱即用，如果你让我推荐一个新手使用的IDE，毫无疑问是Zed。</p>]]></content:encoded>
      <author>Dannyrojas6</author>
      <category>Editor</category><category>Rust</category>
    </item>
    <item>
      <title>How to get started with this theme</title>
      <link>https://my-blog-554.pages.dev//posts/start</link>
      <guid>https://my-blog-554.pages.dev//posts/start</guid>
      <updated>2025-08-15T00:00:00.000Z</updated>
      <pubDate>2025-08-15T00:00:00.000Z</pubDate>
      <description><![CDATA[Quick start for the Litos theme: prerequisites, fork-or-template setup, and next steps.]]></description>
      <content:encoded><![CDATA[<img src="https://my-blog-554.pages.dev/_astro/cover.7-E_J0u9_Z19SPhO.webp" alt="How to get started with this theme" style="width: 100%; height: auto; margin-bottom: 1em;" />
<p>This theme was developed during my free time, and the documentation may not be comprehensive enough, but I will try my best to explain it.</p>
<h3>Prerequisite</h3>
<p>Before starting, ensure you have the following tools installed in your development environment:</p>
<ul>
<li><a href="https://nodejs.org/en/download" rel="noopener noreferrer" target="_blank">Node.js</a> - Required for running the development environment.</li>
<li><a href="https://pnpm.io/installation" rel="noopener noreferrer" target="_blank">pnpm</a> - Our preferred package manager for dependency management.</li>
<li><a href="https://git-scm.com/" rel="noopener noreferrer" target="_blank">Git</a> - For version control and project management.</li>
<li><a href="https://code.visualstudio.com/" rel="noopener noreferrer" target="_blank">VS Code</a> - Recommended code editor with excellent development experience.</li>
</ul>
<div><div><div></div><div>NOTE</div></div><div><p>If you have other alternative tools, then you don’t need to follow the recommendations in this article.</p></div></div>
<h3>Startup project</h3>
<p>There are two ways to do this:</p>
<ul>
<li><a href="https://github.com/Dnzzk2/Litos/fork" rel="noopener noreferrer" target="_blank">Fork the repository</a></li>
<li><a href="https://github.com/Dnzzk2/Litos/generate" rel="noopener noreferrer" target="_blank">Use this template</a> - In the future, content-free branches will be launched, which do not require deleting and replacing content.</li>
</ul>
<p>After you have your own repository, you can clone it to your local machine.</p>
<h3>Update Theme</h3>
<p>When the theme releases a new version, you can pull the latest changes without losing your content by following these steps:</p>
<p><strong>1. Add upstream remote (only needed once)</strong></p>
<pre><code>git remote add upstream https://github.com/Dnzzk2/Litos.git
</code></pre>
<p><strong>2. Fetch and merge the latest theme</strong></p>
<pre><code>git fetch upstream
git merge upstream/main --no-edit
</code></pre>
<p><strong>3. Resolve conflicts (if any)</strong></p>
<p>Conflicts typically only occur in files you have modified, such as <code>src/config.ts</code>. In that case, just keep your own configuration and accept the rest of the theme updates.</p>
<div><div><div></div><div>TIP</div></div><div><p>To minimize conflicts, try to only modify the following files:</p><ul>
<li><code>src/config.ts</code> — Site configuration</li>
<li><code>src/content/</code> — Your posts and projects</li>
<li><code>public/</code> — Your static assets (favicon, images, etc.)</li>
<li><code>.env</code> — Environment variables</li>
</ul><p>Avoid modifying theme source files (components, styles, layouts) directly. This ensures smooth updates every time.</p></div></div>
<h3>End</h3>
<p>The following documents will be divided into pages, such as readme, posts…</p>]]></content:encoded>
      <author>Dnzzk2</author>
      <category>Litos</category><category>Documentation</category>
    </item>
    <item>
      <title>Readme Page Config</title>
      <link>https://my-blog-554.pages.dev//posts/readme</link>
      <guid>https://my-blog-554.pages.dev//posts/readme</guid>
      <updated>2025-08-14T00:00:00.000Z</updated>
      <pubDate>2025-08-14T00:00:00.000Z</pubDate>
      <description><![CDATA[Configure Readme page modules (site, header/footer links, social, GitHub, skills) with field docs and examples.]]></description>
      <content:encoded><![CDATA[<img src="https://my-blog-554.pages.dev/_astro/cover.DMtFtdkY_ZeJxeL.webp" alt="Readme Page Config" style="width: 100%; height: auto; margin-bottom: 1em;" />
<p>The following files are compatible with this document:</p>
<ul>
<li><code>src/pages/index.astro</code> - readme page.</li>
<li><code>src/config.ts</code> - readme page config.</li>
<li><code>src/components/base</code> - most of the components come from here.</li>
</ul>
<h3>Site Config</h3>
<p>Configure basic site information:</p>
<pre><code>export const SITE: Site = {
  title: 'Litos',
  description: 'Litos is a blog theme built with Astro.js and Dnzzk2.',
  website: 'https://litos.vercel.app/',
  lang: 'en',
  base: '/',
  author: 'Dnzzk2',
  ogImage: '/og-image.jpg',
}
</code></pre>





































<table><thead><tr><th>Property</th><th>Description</th></tr></thead><tbody><tr><td>title</td><td>The title of the site.</td></tr><tr><td>description</td><td>The description of the site.</td></tr><tr><td>website</td><td>Your final, deployed URL. Astro uses this full URL to generate your sitemap and canonical URLs in your final build. It is strongly recommended that you set this configuration to get the most out of Astro.</td></tr><tr><td>lang</td><td>The lang global attribute helps define the language of an element.</td></tr><tr><td>base</td><td>The base path to deploy to. Astro will use this path as the root for your pages and assets both in development and in production build. <br /> When you set it to <code>/docs</code>, astro dev will start your server at <code>/docs</code>.</td></tr><tr><td>author</td><td>The author of the site.</td></tr><tr><td>ogImage</td><td>The Open Graph images of the site, but on the specific post page, you can use other ogImage instead.</td></tr></tbody></table>
<h3>Header Links</h3>
<p>Top jump link component configuration:</p>
<pre><code>export const HEADER_LINKS: Link[] = [
  {
    name: 'Posts',
    url: '/posts',
  },
]
</code></pre>
<h3>Footer Links</h3>
<p>Footer link component configuration:</p>
<pre><code>export const FOOTER_LINKS: Link[] = [
  {
    name: 'Readme',
    url: '/',
  },
]
</code></pre>
<h3>Social Links</h3>
<p>In the readme page, you can see a string of icons below the theme introduction, which can be configured below. Icons are from <a href="https://icon-sets.iconify.design/" rel="noopener noreferrer" target="_blank">Iconify</a>.</p>
<pre><code>export const SOCIAL_LINKS: SocialLink[] = [
  {
    name: 'github',
    url: 'https://github.com/yourname',
    icon: 'icon-[ri--github-fill]',
    count: 11,
  },
  {
    name: 'twitter',
    url: 'https://x.com/yourname',
    icon: 'icon-[ri--twitter-x-fill]',
  },
]
</code></pre>

























<table><thead><tr><th>Property</th><th>Description</th></tr></thead><tbody><tr><td>name</td><td>The name of the social link.</td></tr><tr><td>url</td><td>The URL of the social link.</td></tr><tr><td>icon</td><td>The icon of the social link.</td></tr><tr><td>count</td><td>The number of followers of this social media link. This is an <code>optional</code> field.</td></tr></tbody></table>
<h3>Spotlight</h3>
<pre><code>export const GITHUB_CONFIG: GithubConfig = {
  ENABLED: true,
  GITHUB_USERNAME: 'Dnzzk2',
  TOOLTIP_ENABLED: true,
}
</code></pre>





















<table><thead><tr><th>Property</th><th>Description</th></tr></thead><tbody><tr><td>ENABLED</td><td>Whether to enable GitHub features.</td></tr><tr><td>GITHUB_USERNAME</td><td>The GitHub username to fetch data.</td></tr><tr><td>TOOLTIP_ENABLED</td><td>Whether to enable Github Tooltip (the cursor hover card) features.</td></tr></tbody></table>
<h3>Skills</h3>
<p>On the readme page, you can see a skill display area where you can showcase your skills by configuring the following code:</p>
<pre><code>export const SKILLSSHOWCASE_CONFIG: SkillsShowcaseConfig = {
  SKILLS_ENABLED: true,
  SKILLS_DATA: [
    {
      direction: 'left',
      skills: [
        {
          name: 'JavaScript',
          icon: 'icon-[mdi--language-javascript]',
        },
        {
          name: 'CSS',
          icon: 'icon-[mdi--language-css3]',
        },
        {
          name: 'HTML',
          icon: 'icon-[mdi--language-html5]',
        },
        {
          name: 'TypeScript',
          icon: 'icon-[mdi--language-typescript]',
        },
      ],
    },
  ],
}
</code></pre>

































<table><thead><tr><th>Property</th><th>Description</th></tr></thead><tbody><tr><td>SKILLS_ENABLED</td><td>Whether to enable SkillsShowcase features.</td></tr><tr><td>SKILLS_DATA</td><td>The data of the skills. A single object represents a row.</td></tr><tr><td>    direction</td><td>Each animation runs in two directions: <code>left</code> and <code>right</code>.</td></tr><tr><td>    skills</td><td>The skills array.</td></tr><tr><td>        name</td><td>The name of the skill.</td></tr><tr><td>        icon</td><td>The icon of the skill. Icons are from <a href="https://icon-sets.iconify.design/" rel="noopener noreferrer" target="_blank">Iconify</a>.</td></tr></tbody></table>
<div><div><div></div><div>TIP</div></div><div><p>It is recommended to run the project locally and check its effectiveness. It is suggested to have at least three different skills per line.</p></div></div>
<h3>Posts</h3>
<p>Here, we mainly display pinned posts. If there are no pinned posts, we will display the latest number of <code>size</code> posts based on <code>POSTS_CONFIG</code>.</p>]]></content:encoded>
      <author>Dnzzk2</author>
      <category>Litos</category><category>Documentation</category>
    </item>
    <item>
      <title>Posts Page Config</title>
      <link>https://my-blog-554.pages.dev//posts/posts</link>
      <guid>https://my-blog-554.pages.dev//posts/posts</guid>
      <updated>2025-08-13T00:00:00.000Z</updated>
      <pubDate>2025-08-13T00:00:00.000Z</pubDate>
      <description><![CDATA[Complete Posts configuration: fields, list styles, post metadata layouts, OG image handling, frontmatter examples, markdown syntax, and expressive-code config.]]></description>
      <content:encoded><![CDATA[<img src="https://my-blog-554.pages.dev/_astro/cover.DuEOlrCu_2kyEMp.webp" alt="Posts Page Config" style="width: 100%; height: auto; margin-bottom: 1em;" />
<p>The following files are compatible with this document:</p>
<ul>
<li><code>src/pages/posts/[...id].astro</code> - post specific content display page.</li>
<li><code>src/pages/posts/[...page].astro</code> - post list page.</li>
<li><code>src/content/posts</code> - posts collection</li>
<li><code>src/content.config.ts</code> - posts dataset and frontmatter configuration.</li>
<li><code>ec.config.mjs</code> - expressiveCode config.</li>
<li><code>plugins/index.ts</code> - remark and rehype plugins.</li>
<li><code>src/config.ts</code> - posts page config.</li>
<li><code>src/components/posts</code> - most of the components come from here.</li>
</ul>
<h3>Posts Page Config</h3>
<p>The posts page config is configured in the following code:</p>
<pre><code>export const POSTS_CONFIG: PostConfig = {
  title: 'Posts',
  description: 'Posts by Dnzzk2',
  introduce: 'Here, I will share the usage instructions for this theme to help you quickly use it.',
  author: 'Dnzzk2',
  homePageConfig: {
    size: 3,
    type: 'compact',
  },
  postPageConfig: {
    size: 10,
    type: 'minimal',
  },
  tagsPageConfig: {
    size: 10,
    type: 'time-line',
  },
  ogImageUseCover: false,
  postType: 'metaOnly',
  imageDarkenInDark: true,
  readMoreText: 'Read more',
  prevPageText: 'Previous',
  nextPageText: 'Next',
  tocText: 'On this page',
  backToPostsText: 'Back to Posts',
  nextPostText: 'Next Post',
  prevPostText: 'Previous Post',
  recommendText: 'REC',
}
</code></pre>
<p>There are a few configuration attributes, please refer to the table below for details.</p>





























































































<table><thead><tr><th>Property</th><th>Description</th></tr></thead><tbody><tr><td>title</td><td>Title displayed on browser tags and title displayed on list pages.</td></tr><tr><td>description</td><td>The metadata description in the <code>head</code> element of the list page.</td></tr><tr><td>introduce</td><td>The introduce below the title on the list page.</td></tr><tr><td>author</td><td>The author of the posts.</td></tr><tr><td>homePageConfig</td><td><strong>readme page</strong> configuration.</td></tr><tr><td>    size</td><td>The number of posts displayed on the <strong>readme page</strong>.</td></tr><tr><td>    type</td><td>In the <strong>read page</strong>, the style of displaying data in a list , <code>compact</code> 、<code>minimal</code> 、<code>time-line</code> or <code>image</code>.</td></tr><tr><td>    coverLayout <br />    (optional)</td><td>When the type is image, this attribute can set the position of the image in the card. You can choose left or right. If not set, it will appear alternately on the left and right.</td></tr><tr><td>postPageConfig</td><td>Same as homePageConfig above, but size represents the base number of pages and is used for <strong>post pages</strong>.</td></tr><tr><td>tagsPageConfig</td><td>Same as homePageConfig above, but size represents the base number of pages and is used for <strong>tag pages</strong>.</td></tr><tr><td>ogImageUseCover</td><td>Whether to use the cover image as the Open Graph image.</td></tr><tr><td>postType</td><td>The default display component for the top metadata of the post’s specific content display page.You can configure <code>metaOnly</code>、 <code>coverSplit</code>、<code>coverTop</code>. <br /> Can be replaced by frontmatter settings for content.</td></tr><tr><td>imageDarkenInDark</td><td>Whether to darken the image in the dark mode.</td></tr><tr><td>readMoreText</td><td>The text of the read more button.</td></tr><tr><td>prevPageText</td><td>The text of the previous page button.</td></tr><tr><td>nextPageText</td><td>The text of the next page button.</td></tr><tr><td>tocText</td><td>Title text of the directory</td></tr><tr><td>backToPostsText</td><td>The text of the back to posts button.</td></tr><tr><td>nextPostText</td><td>The text of the next post button.</td></tr><tr><td>prevPostText</td><td>The text of the previous post button.</td></tr><tr><td>recommendText</td><td>The text of the recommend tag.</td></tr></tbody></table>
<h4>Type &amp; PostType</h4>
<p>In the above document, we mentioned configurable <code>types</code> for configuring the display style of list data on the readme page, posts page, and tags page, as well as configurable <code>postTypes</code> for displaying metadata at the top of the posts content page.</p>
<p>Here is the specific style display of <code>type</code>:</p>
<figure><img src="https://my-blog-554.pages.dev/_astro/compact-black.DMCWHrjD_2evXXq.webp" alt="" class="img-light" /><figcaption>compact</figcaption></figure>
<figure><img src="https://my-blog-554.pages.dev/_astro/timeLine-black.jDSZqIn4_Z2hp2Yv.webp" alt="" class="img-light" /><figcaption>time-line</figcaption></figure>
<figure><img src="https://my-blog-554.pages.dev/_astro/minimal-black.Dxsrn9wb_1l0yun.webp" alt="" class="img-light" /><figcaption>minimal</figcaption></figure>
<figure><img src="https://my-blog-554.pages.dev/_astro/image-black.BeMdDApH_ZV1j5H.webp" alt="" class="img-light" /><figcaption>image</figcaption></figure>
<p>Here is the specific style display of <code>postType</code>:</p>
<figure><img src="https://my-blog-554.pages.dev/_astro/metaOnly-black.CAi7aUXH_2gkuT1.webp" alt="" class="img-light" /><figcaption>metaOnly</figcaption></figure>
<figure><img src="https://my-blog-554.pages.dev/_astro/coverTop-black.Cji_8Dx0_2wd6Gt.webp" alt="" class="img-light" /><figcaption>coverTop</figcaption></figure>
<figure><img src="https://my-blog-554.pages.dev/_astro/coverSplit-black.BGSoc6qf_1NdheF.webp" alt="" class="img-light" /><figcaption>coverSplit</figcaption></figure>
<h3>Frontmatter</h3>
<p>After discussing the external list and overall design of the post, let’s take a look at the content of the post together. These contents are all in the <code>src/content/posts</code> folder.</p>
<p>Below is the frontmatter of the post content:</p>
<pre><code>---
title: 'Litos: Posts Page Config'
description: ''
pubDate: 2025-08-13
author: 'Dnzzk2'
recommend: true
tags: ['Litos', 'Documentation']
---
</code></pre>
<p>You can configure the frontmatter of the post content in the following code:</p>
<pre><code>const posts = defineCollection({
  loader: glob({
    pattern: '**/*.{md,mdx}',
    base: './src/content/posts',
  }),
  schema: ({ image }) =&gt;
    z
      .object({
        title: z.string(),
        description: z.string(),
        pubDate: z.date(),
        tags: z.array(z.string()).optional(),
        updatedDate: z.date().optional(),
        author: z.string().default(POSTS_CONFIG.author),
        cover: image().optional(),
        ogImage: image().optional(),
        recommend: z.boolean().default(false),
        postType: z.custom&lt;PostType&gt;().optional(),
        coverLayout: z.custom&lt;CoverLayout&gt;().optional(),
        pinned: z.boolean().default(false),
        draft: z.boolean().default(false),
      })
      .transform((data) =&gt; ({
        ...data,
        ogImage: data.ogImage ? data.ogImage : POSTS_CONFIG.ogImageUseCover &amp;&amp; data.cover ? data.cover : undefined,
      })),
})
</code></pre>





























































<table><thead><tr><th>Property</th><th>Description</th></tr></thead><tbody><tr><td>title</td><td>Title of the post.</td></tr><tr><td>description</td><td>Overview of the content of the post, also used for SEO.</td></tr><tr><td>pubDate</td><td>Post release date.</td></tr><tr><td>tags</td><td>List of tags for posts</td></tr><tr><td>updatedDate</td><td>Latest update date of the post. <br /> In the sorting of the post list, the priority value is greater than the publication date.</td></tr><tr><td>author</td><td>The author of the post.</td></tr><tr><td>cover</td><td>When the type of the list is <code>image</code>, the cover image used for display, or the cover image displayed at the top of the postType <code>coverSplit</code> or <code>coverTop</code></td></tr><tr><td>ogImage</td><td>The Open Graph image of the post.</td></tr><tr><td>recommend</td><td>Whether to display the recommend tag.</td></tr><tr><td>postType</td><td>The display component for the top metadata of the post’s specific content display page. You can configure <code>metaOnly</code>、 <code>coverSplit</code>、<code>coverTop</code></td></tr><tr><td>coverLayout</td><td>When the type is <code>image</code>, this attribute can set the position of the image in the card. You can choose <code>left</code> or <code>right</code>. If not set, it will appear alternately on the left and right.</td></tr><tr><td>pinned</td><td>Whether to top the post.</td></tr><tr><td>draft</td><td>Whether to hide the post.</td></tr></tbody></table>
<div><div><div></div><div>TIP</div></div><div><p>Regarding <code>cover</code> and <code>ogImage</code>.</p><p><code>Cover</code> and <code>ogImage</code> are two independent attributes, and the only one that can connect them is <code>POSTS_CONFIG.ogImageUseCover</code>.</p><p><code>POSTS_CONFIG.ogImageUseCover</code> is enabled by default, so you only need to write <code>cover</code> to configure <code>ogImage</code> at the same time. This applies to situations where the <code>cover</code> and <code>ogImage</code> are the same. If you want to customize <code>ogImage</code>, you can set it separately.</p><p>If <code>POSTS_CONFIG.ogImageUseCover</code> is not enabled, <code>ogImage</code> needs to be set separately. If you do not set it, the site’s <code>ogImage</code> will be used as a fallback.</p><p><code>POSTS_CONFIG.ogImageUseCover</code> &gt; <code>cover</code></p></div></div>
<hr />
<h3>Syntax and Code Style</h3>
<p>This guide will show you how to format text using Markdown through a 3-day city trip itinerary. Learn Markdown while planning your journey!</p>
<h4>Heading Levels</h4>
<p>For travel notes, multiple heading levels help organize days, time blocks, and tips:</p>
<pre><code># 3-Day City Trip Itinerary

## Day 1: Arrival &amp; Old Town

### Morning Plan

#### Coffee Stops

##### Metro Tips

###### Notes
</code></pre>
<h4>Text Formatting</h4>
<p>When writing travel notes, highlight important info:</p>
<p><strong>Must-see spots</strong> should be bold
<em>Flexible time</em> uses italics
<strong><em>Critical cautions</em></strong> can use both
Optional detours use strikethrough</p>
<pre><code>**Must-see spots** should be bold
_Flexible time_ uses italics
**_Critical cautions_** can use both
~~Optional detours~~ use strikethrough
</code></pre>
<h4>Packing List (Unordered List)</h4>
<ul>
<li>Passport, visa</li>
<li>Camera, extra battery
<ul>
<li>Bring a fast charger</li>
<li>Spare SD card</li>
</ul>
</li>
<li>Refillable water bottle</li>
<li>Public transport card</li>
</ul>
<pre><code>- Passport, visa
- Camera, extra battery
  - Bring a fast charger
  - Spare SD card
- Refillable water bottle
- Public transport card
</code></pre>
<h4>Day 1 Schedule (Ordered List)</h4>
<ol>
<li>Airport → hotel check-in</li>
<li>Old Town walking tour</li>
<li>Evening river cruise
<ol>
<li>Arrive 15 min early</li>
<li>Queue at Gate B</li>
<li>Window seats recommended</li>
</ol>
</li>
</ol>
<pre><code>1. Airport → hotel check-in
2. Old Town walking tour
3. Evening river cruise
   1. Arrive 15 min early
   2. Queue at Gate B
   3. Window seats recommended
</code></pre>
<h4>Blockquotes</h4>
<blockquote><p>Traveler tip: Buy a 24‑hour metro pass if you plan 3+ rides in a day.</p><p>Save the hotel address in offline maps for quick access.</p></blockquote>
<pre><code>&gt; Traveler tip: Buy a 24‑hour metro pass if you plan 3+ rides in a day.
&gt;
&gt; Save the hotel address in offline maps for quick access.
</code></pre>
<h4>Code Blocks</h4>
<p>Use simple code to estimate budget:</p>
<pre><code>type Budget = { flight: number; hotel: number; meals: number; transport: number }
export const total = (b: Budget) =&gt; b.flight + b.hotel + b.meals + b.transport

console.log(total({ flight: 1200, hotel: 450, meals: 180, transport: 60 })) // 1890
</code></pre>
<h4>Tables</h4>
<p>Sample schedule:</p>

























<table><thead><tr><th>Time</th><th>Place</th><th>Notes</th></tr></thead><tbody><tr><td>09:00</td><td>Old Town Square</td><td>Guided walking tour</td></tr><tr><td>12:30</td><td>Riverside Cafe</td><td>Lunch + short rest</td></tr><tr><td>18:00</td><td>City Pier</td><td>Sunset cruise</td></tr></tbody></table>
<h4>Links and Images</h4>
<p>More tips: <a href="https://example.com/travel" rel="noopener noreferrer" target="_blank">Official Tourism Board</a></p>
<p>Trip photo:
<img src="https://my-blog-554.pages.dev/_astro/home.DfiDpdST_gTJAi.webp" alt="City skyline" style="width:50%" /></p>
<h4>Horizontal Rule</h4>
<hr />
<h4>Inline Code</h4>
<p>Metro line <code>A</code> runs every <code>5-7 minutes</code> during peak hours.</p>
<h4>Math Formulas</h4>
<p>Daily budget estimation: <span><span>budget=hotel+meals+transportbudget = hotel + meals + transport</span><span><span><span></span><span>b</span><span>u</span><span>d</span><span>g</span><span>e</span><span>t</span><span></span><span>=</span><span></span></span><span><span></span><span>h</span><span>o</span><span>t</span><span>e</span><span>l</span><span></span><span>+</span><span></span></span><span><span></span><span>m</span><span>e</span><span>a</span><span>l</span><span>s</span><span></span><span>+</span><span></span></span><span><span></span><span>t</span><span>r</span><span>an</span><span>s</span><span>p</span><span>or</span><span>t</span></span></span></span></p>
<p>Total trip:</p>
<span><span><span>Total Budget=∑d=13(Hoteld+Mealsd+Transportd)Total\ Budget = \sum_{d=1}^{3} (Hotel_d + Meals_d + Transport_d)</span><span><span><span></span><span>T</span><span>o</span><span>t</span><span>a</span><span>l</span><span> </span><span>B</span><span>u</span><span>d</span><span>g</span><span>e</span><span>t</span><span></span><span>=</span><span></span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>d</span><span>=</span><span>1</span></span></span></span><span><span></span><span><span>∑</span></span></span><span><span></span><span><span><span>3</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span>(</span><span>H</span><span>o</span><span>t</span><span>e</span><span><span>l</span><span><span><span><span><span><span></span><span><span>d</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>+</span><span></span></span><span><span></span><span>M</span><span>e</span><span>a</span><span>l</span><span><span>s</span><span><span><span><span><span><span></span><span><span>d</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>+</span><span></span></span><span><span></span><span>T</span><span>r</span><span>an</span><span>s</span><span>p</span><span>or</span><span><span>t</span><span><span><span><span><span><span></span><span><span>d</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>)</span></span></span></span></span>
<h4>Task Lists</h4>
<p>Pre-trip checklist:</p>
<ul>
<li> Book flights</li>
<li> Reserve hotel</li>
<li> Buy metro pass</li>
<li> Download offline maps</li>
</ul>
<h4>Footnotes</h4>
<p>This itinerary draws on local travel guides(click to the footnote) <sup><a href="#user-content-fn-1">1</a></sup>.</p>
<hr />
<h3>Expressive-code config</h3>
<p>In Markdown documents, we use code blocks to display code snippets and other content. This document explains how to customize the code block configuration.</p>
<p>The code blocks in this theme are configured using <a href="https://expressive-code.com/" rel="noopener noreferrer" target="_blank">Expressive Code</a> with all configuration options defined in the <code>ec.config.mjs</code> file. Below is the main configuration options:</p>
<pre><code>import { defineEcConfig } from 'astro-expressive-code'
import { pluginCollapsibleSections } from '@expressive-code/plugin-collapsible-sections'
import { pluginLineNumbers } from '@expressive-code/plugin-line-numbers'

export default defineEcConfig({
  defaultLocale: 'zh-CN',
  defaultProps: {
    wrap: false,
    collapseStyle: 'collapsible-auto',
    showLineNumbers: false,
    preserveIndent: true,
  },
  minSyntaxHighlightingColorContrast: 0,

  styleOverrides: {
    uiFontFamily: 'GeistMono, Input Mono, Fira Code, ShangguSansSCVF, monospace',
    uiFontSize: '1em',
    codeFontFamily: 'GeistMono, Input Mono, Fira Code, ShangguSansSCVF, monospace',
    codeFontSize: '14px',
    codeLineHeight: '1.4',
    borderRadius: '0',
    codePaddingBlock: '0.8571429em',
    codePaddingInline: '1.1428571em',
    borderColor: ({ theme }) =&gt; (theme.type === 'dark' ? '#24273a' : '#e6e9ef'),

    frames: {
      frameBoxShadowCssValue: false,
      inlineButtonBackgroundActiveOpacity: '0.2',
      inlineButtonBackgroundHoverOrFocusOpacity: '0.1',
    },
    textMarkers: {
      backgroundOpacity: '0.2',
      borderOpacity: '0.4',
    },
  },

  plugins: [
    pluginCollapsibleSections({
      defaultCollapsed: false,
    }),
    pluginLineNumbers(),
  ],

  themes: ['catppuccin-macchiato', 'catppuccin-latte'],
  themeCssSelector: (theme) =&gt; (theme.name === 'catppuccin-macchiato' ? '.dark' : ':root:not(.dark)'),
  useDarkModeMediaQuery: false,
  useStyleReset: false,
})
</code></pre>
<p>You can go to the website to view the configuration options of expressive-code.</p>
<hr />
<h3>Comment System (Gitalk)</h3>
<p>The theme has a built-in <a href="https://github.com/gitalk/gitalk" rel="noopener noreferrer" target="_blank">Gitalk</a> comment system that uses GitHub Issues as the comment backend. Each post automatically gets its own issue for comments.</p>
<h4>Prerequisites</h4>
<p>You need to create a <strong>GitHub OAuth App</strong> first:</p>
<ol>
<li>Go to <a href="https://github.com/settings/developers" rel="noopener noreferrer" target="_blank">GitHub Developer Settings</a> → <strong>OAuth Apps</strong> → <strong>New OAuth App</strong>.</li>
<li>Fill in the form:
<ul>
<li><strong>Application name</strong>: Any name (e.g. <code>My Blog Comments</code>)</li>
<li><strong>Homepage URL</strong>: Your site URL (e.g. <code>https://yourdomain.com</code>)</li>
<li><strong>Authorization callback URL</strong>: Same as your site URL</li>
</ul>
</li>
<li>After creation, copy the <strong>Client ID</strong> and generate a <strong>Client Secret</strong>.</li>
<li>Create a <strong>public repository</strong> on GitHub to store comment issues (e.g. <code>blog-comments</code>).</li>
</ol>
<h4>Configuration</h4>
<p>Configure the comment system in <code>src/config.ts</code>:</p>
<pre><code>export const COMMENT_CONFIG: CommentConfig = {
  enabled: true,
  system: 'gitalk',
  gitalk: {
    clientID: import.meta.env.PUBLIC_GITHUB_CLIENT_ID,
    clientSecret: import.meta.env.PUBLIC_GITHUB_CLIENT_SECRET,
    repo: 'blog-comments',
    owner: 'YourGitHubUsername',
    admin: ['YourGitHubUsername'],
    language: 'en-US',
    perPage: 5,
    pagerDirection: 'last',
    createIssueManually: false,
    distractionFreeMode: false,
    enableHotKey: true,
  },
}
</code></pre>
<p>Then set the environment variables. Create a <code>.env</code> file in the project root (refer to <code>.env.example</code>):</p>
<pre><code>PUBLIC_GITHUB_CLIENT_ID=your-github-client-id
PUBLIC_GITHUB_CLIENT_SECRET=your-github-client-secret
</code></pre>
<h4>Config Properties</h4>





























































<table><thead><tr><th>Property</th><th>Description</th></tr></thead><tbody><tr><td>enabled</td><td>Whether to enable the comment system.</td></tr><tr><td>system</td><td>Comment system to use. Options: <code>gitalk</code>, <code>none</code>.</td></tr><tr><td>clientID</td><td>GitHub OAuth App Client ID.</td></tr><tr><td>clientSecret</td><td>GitHub OAuth App Client Secret.</td></tr><tr><td>repo</td><td>The GitHub repository name for storing comment issues.</td></tr><tr><td>owner</td><td>The GitHub username of the repository owner.</td></tr><tr><td>admin</td><td>Array of GitHub usernames who can initialize comment issues.</td></tr><tr><td>language</td><td>Display language. e.g. <code>en-US</code>, <code>zh-CN</code>, <code>zh-TW</code>.</td></tr><tr><td>perPage</td><td>Number of comments per page.</td></tr><tr><td>pagerDirection</td><td>Comment sorting direction. <code>last</code> (newest first) or <code>first</code> (oldest first).</td></tr><tr><td>createIssueManually</td><td>If <code>true</code>, issues must be created manually. If <code>false</code>, auto-created on first visit by admin.</td></tr><tr><td>distractionFreeMode</td><td>If <code>true</code>, enables full-screen overlay when typing a comment.</td></tr><tr><td>enableHotKey</td><td>Whether to enable <code>Cmd/Ctrl + Enter</code> shortcut to submit comments.</td></tr></tbody></table>
<div><div><div></div><div>NOTE</div></div><div><p>To disable comments entirely, set <code>enabled: false</code> or <code>system: 'none'</code> in <code>COMMENT_CONFIG</code>.</p></div></div>
<div><div><div></div><div>CAUTION</div></div><div><p>The <code>clientID</code> and <code>clientSecret</code> are read from environment variables via <code>import.meta.env</code>. Make sure to add <code>.env</code> to your <code>.gitignore</code> file (already included by default) to avoid leaking secrets.</p></div></div>
<section><h2>Footnotes</h2>
<ol>
<li>
<p>City Tourism Guide, 2024 Edition. (click back to the text) <a href="#user-content-fnref-1">↩</a></p>
</li>
</ol>
</section>]]></content:encoded>
      <author>Dnzzk2</author>
      <category>Litos</category><category>Documentation</category>
    </item>
    <item>
      <title>Markdown enhanced syntax</title>
      <link>https://my-blog-554.pages.dev//posts/enhance</link>
      <guid>https://my-blog-554.pages.dev//posts/enhance</guid>
      <updated>2025-08-12T00:00:00.000Z</updated>
      <pubDate>2025-08-12T00:00:00.000Z</pubDate>
      <description><![CDATA[Enhance Markdown with callouts, Expressive Code blocks, image caption directives, video embeds, styled links, badges, and details.]]></description>
      <content:encoded><![CDATA[<img src="https://my-blog-554.pages.dev/_astro/cover.BKthU1GI_ykCju.webp" alt="Markdown enhanced syntax" style="width: 100%; height: auto; margin-bottom: 1em;" />
<p>This guide has made slight changes based on the <a href="https://astro-antfustyle-theme.vercel.app/blog/markdown-mdx-extended-features/" rel="noopener noreferrer" target="_blank">markdown-mdx-extended-features</a>.</p>
<h2>Callouts</h2>
<p>Supported by the <a href="https://github.com/lin-stephanie/rehype-callouts" rel="noopener noreferrer" target="_blank">rehype-callouts</a> , you can configure the plugin in <code>plugins/index.ts</code>.</p>
<p>If you change the <code>theme</code> configuration (default: <code>'vitepress'</code>), you will also need to update the imported CSS file in <code>src/styles/pro.css</code> (<code>@import 'rehype-callouts/theme/yourconfig'</code>).</p>
<pre><code>&lt;!-- Callout type names are case-insensitive: 'Note', 'NOTE', and 'note' are equivalent. --&gt;

&lt;!-- vitepress --&gt;

&lt;!-- This is a _non-collapsible_ callout --&gt;

&gt; [!note]
&gt; Note content.

&gt; [!tip]
&gt; Tip content.

&gt; [!important]
&gt; Important content.

&gt; [!warning]
&gt; Warning content.

&gt; [!caution]
&gt; Caution content.

&gt; [!caution]- This is a **collapsible** callout
&gt; Caution content.

&gt; [!note]+ This is a **collapsible** callout
&gt; Note content.
</code></pre>
<div><div><div></div><div>NOTE</div></div><div><p>Note <code>content</code>.</p></div></div>
<div><div><div></div><div>TIP</div></div><div><p>Tip <code>content</code>.</p></div></div>
<div><div><div></div><div>IMPORTANT</div></div><div><p>Important <code>content</code>.</p></div></div>
<div><div><div></div><div>WARNING</div></div><div><p>Warning <code>content</code>.</p></div></div>
<div><div><div></div><div>CAUTION</div></div><div><p>Caution <code>content</code>.</p></div></div>
<div></div><div>This is a <strong>collapsible</strong> callout</div><div></div><div><p>Caution content.</p></div>
<div></div><div>This is a <strong>collapsible</strong> callout</div><div></div><div><p>Note content.</p></div>
<h2>Fully-featured Code Blocks</h2>
<p>Supported by <a href="https://github.com/expressive-code/expressive-code/tree/main/packages/astro-expressive-code" rel="noopener noreferrer" target="_blank">astro-expressive-code</a> with <a href="https://expressive-code.com/plugins/collapsible-sections/" rel="noopener noreferrer" target="_blank">@expressive-code/plugin-collapsible-sections</a> and <a href="https://expressive-code.com/plugins/line-numbers/" rel="noopener noreferrer" target="_blank">@expressive-code/plugin-line-numbers</a> plugins to add styling and extra functionality for code blocks.</p>
<p>To customize code block themes or functionality, modify the <code>ec.config.mjs</code> file at the project root after reviewing the <a href="https://expressive-code.com/reference/configuration/" rel="noopener noreferrer" target="_blank">Configuring Expressive Code</a>, such as <a href="https://expressive-code.com/guides/themes/#using-bundled-themes" rel="noopener noreferrer" target="_blank">change themes</a>, <a href="https://expressive-code.com/key-features/word-wrap/#wrap" rel="noopener noreferrer" target="_blank">enable word wrap</a>, or <a href="https://expressive-code.com/plugins/line-numbers/#showlinenumbers" rel="noopener noreferrer" target="_blank">toggle line numbers</a>.</p>
<p>Here’s a quick preview of what’s possible. Check the <a href="https://expressive-code.com/key-features/syntax-highlighting/" rel="noopener noreferrer" target="_blank">detailed guide</a> for more info.</p>
<h4>Syntax highlighting</h4>
<pre><code>console.log('This code is syntax highlighted!')
</code></pre>
<pre><code>ANSI colors:
- Regular: Red Green Yellow Blue Magenta Cyan
- Bold:    Red Green Yellow Blue Magenta Cyan
- Dimmed:  Red Green Yellow Blue Magenta Cyan

256 colors (showing colors 160-177):
160 161 162 163 164 165
166 167 168 169 170 171
172 173 174 175 176 177

Full RGB colors:
ForestGreen - RGB(34, 139, 34)

Text formatting: Bold Dimmed Italic Underline
</code></pre>
<h5>Code editor frames</h5>
<pre><code>// Use `title="my-test-file.js"`
console.log('Title attribute example')
</code></pre>
<pre><code>// src/content/index.ts
// Use `// src/content/index.ts`
console.log('File name comment example')
</code></pre>
<h5>Terminal frames</h5>
<pre><code>echo "This terminal frame has no title"
</code></pre>
<pre><code>Write-Output "This one has a title!"
</code></pre>
<h5>Marking full lines &amp; line ranges</h5>
<pre><code>// Line 1 - targeted by line number
// Line 2
// Line 3
// Line 4 - targeted by line number
// Line 5
// Line 6
// Line 7 - targeted by range "7-8"
// Line 8 - targeted by range "7-8"
</code></pre>
<h5>Selecting line marker types (mark, ins, del)</h5>
<pre><code>function demo() {
  console.log('this line is marked as deleted')
  // This line and the next one are marked as inserted
  console.log('this is the second inserted line')

  return 'this line uses the neutral default marker type'
}
</code></pre>
<h5>Adding labels to line markers</h5>
<pre><code>// labeled-line-markers.jsx
&lt;button role="button" {...props} value={value} className={buttonClassName} disabled={disabled} active={active}&gt;
  {children &amp;&amp; !active &amp;&amp; (typeof children === 'string' ? &lt;span&gt;{children}&lt;/span&gt; : children)}
&lt;/button&gt;
</code></pre>
<h5>Adding long labels on their own lines</h5>
<pre><code>// labeled-line-markers.jsx
&lt;button role="button" {...props} value={value} className={buttonClassName} disabled={disabled} active={active}&gt;
  {children &amp;&amp; !active &amp;&amp; (typeof children === 'string' ? &lt;span&gt;{children}&lt;/span&gt; : children)}
&lt;/button&gt;
</code></pre>
<h5>Using diff-like syntax</h5>
<pre><code>+this line will be marked as inserted
-this line will be marked as deleted
this is a regular line
</code></pre>
<pre><code>  function thisIsJavaScript() {
    // This entire block gets highlighted as JavaScript,
    // and we can still add diff markers to it!
-   console.log('Old code to be removed')
+   console.log('New and shiny code!')
  }
</code></pre>
<h5>Marking individual text inside lines</h5>
<pre><code>// Plaintext search strings
function demo() {
  // Mark any given text inside lines
  return 'Multiple matches of the given text are supported'
}
</code></pre>
<h5>Marking individual text inside lines</h5>
<pre><code>// Regular expressions
console.log('The words yes and yep will be marked.')
</code></pre>
<pre><code># Regular expressions
echo "Test" &gt; /home/test.txt
</code></pre>
<pre><code>// Regular expressions
If you only want to mark certain parts matched by your regular expression, you can use capture groups.

For example, the expression `/ye(s|p)/` will match yes and yep, but only mark the character s or p:
</code></pre>
<pre><code>// Regular expressions
To prevent this special treatment of capture groups, you can convert them to non-capturing groups by adding ?: after the opening parenthesis. For example:

This block uses `/ye(?:s|p)/`, which causes the full
matching words "yes" and "yep" to be marked.
</code></pre>
<pre><code>// Selecting inline marker types (mark, ins, del)
function demo() {
  console.log('These are inserted and deleted marker types')
  // The return statement uses the default marker type
  return true
}
</code></pre>
<h5>Configuring word wrap per block</h5>
<pre><code>// Example with wrap
function getLongString() {
  return 'This is a very long string that will most probably not fit into the available space unless the container is extremely wide'
}
</code></pre>
<pre><code>// Example with wrap=false
function getLongString() {
  return 'This is a very long string that will most probably not fit into the available space unless the container is extremely wide'
}
</code></pre>
<h5>Configuring indentation of wrapped lines</h5>
<pre><code>// Example with preserveIndent (enabled by default)
function getLongString() {
  return 'This is a very long string that will most probably not fit into the available space unless the container is extremely wide'
}
</code></pre>
<pre><code>// Example with preserveIndent=false
function getLongString() {
  return 'This is a very long string that will most probably not fit into the available space unless the container is extremely wide'
}
</code></pre>
<h5>Collapsible sections</h5>
<pre><code>// All this boilerplate setup code will be collapsed
import { someBoilerplateEngine } from '@example/some-boilerplate'
import { evenMoreBoilerplate } from '@example/even-more-boilerplate'

const engine = someBoilerplateEngine(evenMoreBoilerplate())

// This part of the code will be visible by default
engine.doSomething(1, 2, 3, calcFn)

function calcFn() {
  // You can have multiple collapsed sections
  const a = 1
  const b = 2
  const c = a + b

  // This will remain visible
  console.log(`Calculation result: ${a} + ${b} = ${c}`)
  return c
}

// All this code until the end of the block will be collapsed again
engine.closeConnection()
engine.freeMemory()
engine.shutdown({ reason: 'End of example boilerplate code' })
</code></pre>
<h5>Displaying line numbers per block</h5>
<pre><code>// This code block will show line numbers
console.log('Greetings from line 2!')
console.log('I am on line 3')
</code></pre>
<pre><code>// Line numbers are disabled for this block
console.log('Hello?')
console.log('Sorry, do you know what line I am on?')
</code></pre>
<pre><code>// Changing the starting line number
console.log('Greetings from line 5!')
console.log('I am on line 6')
</code></pre>
<h2>Image Caption &amp; Link</h2>
<p>Use the <a href="https://github.com/lin-stephanie/remark-directive-sugar?tab=readme-ov-file#image-" rel="noopener noreferrer" target="_blank"><code>:::image</code></a> directive from <a href="https://github.com/lin-stephanie/remark-directive-sugar" rel="noopener noreferrer" target="_blank">remark-directive-sugar</a> to wrap images in a container for captions, clickable links, and more. Customize via the <code>image</code> option in <code>plugins/index.ts</code> (<code>remarkDirectiveSugar</code>) and style under <code>/* :::image */</code> in <code>src/styles/pro.css</code>.</p>
<h3>image-figure</h3>
<p><code>:::image-figure[caption]{&lt;figcaption&gt; attrs}</code>: The square brackets define the <code>&lt;figcaption&gt;</code> text (defaults to the alt text from <code>![]()</code> if omitted), while the curly braces are used for inline styles or supported attributes to the generated <code>&lt;figcaption&gt;</code> element.</p>
<p><code>![alt](image path)(&lt;img&gt; attrs)</code>: Standard Markdown image with optional attributes in parentheses, enabled by <a href="https://github.com/OliverSpeir/remark-imgattr" rel="noopener noreferrer" target="_blank">remark-imgattr</a>, for customizing the generated <code>&lt;img&gt;</code> element.</p>
<p><code>:::image-figure[caption]{&lt;figcaption&gt; attrs}</code>: The square brackets define the <code>&lt;figcaption&gt;</code> text (defaults to the alt text from <code>![]()</code> if omitted), while the curly braces are used for inline styles or supported attributes to the generated <code>&lt;figcaption&gt;</code> element.</p>
<p><code>![alt](image path)(&lt;img&gt; attrs)</code>: Standard Markdown image with optional attributes in parentheses, enabled by <a href="https://github.com/OliverSpeir/remark-imgattr" rel="noopener noreferrer" target="_blank">remark-imgattr</a>, for customizing the generated <code>&lt;img&gt;</code> element.</p>
<pre><code>:::image-figure[This Is a **Figcaption** with _`&lt;figure&gt;` Attrs_]{style="text-align:center;color:orange"}
![](assets/cover.png)
:::

:::image-figure[This is a **figcaption** with _`&lt;img&gt;` attrs_.]
![](assets/cover.png)(style: width:600px;)
:::

&lt;!-- 💡 Use `(class:no-zoom)` to disable zoom --&gt;

:::image-figure[This is a **figcaption** with `class:no-zoom`.]
![](assets/cover.png)(class:no-zoom)
:::

&lt;!-- 💡 If no `[caption]`, use `[alt]` as figcaption. --&gt;

:::image-figure
![If `[caption]` not set, the alt text from `![]()` will be used as the figcaption.](assets/cover.png)
:::

&lt;!-- 💡 Images for light (img-light) and dark (img-dark) modes --&gt;
&lt;!-- ⚠️ At least one line must separate two image syntaxes (![]()), or won't work. --&gt;

:::image-figure[This example shows different images for light (add `class:img-light`) and dark (add `class:img-dark`) modes.]
![](assets/image-16-9-light.png)(class:img-light)

![](assets/image-16-9-light.png)(class:img-dark)
:::

&lt;!-- ❌ If no text is available for the figcaption, it won't work.  --&gt;

:::image-figure
![](assets/cover.png)
:::
</code></pre>
<figure><img src="https://my-blog-554.pages.dev/_astro/cover.BKthU1GI_ykCju.webp" alt="" /><figcaption>This Is a <strong>Figcaption</strong> with <em><code>&lt;figure&gt;</code> Attrs</em></figcaption></figure>
<figure><img src="https://my-blog-554.pages.dev/_astro/cover.BKthU1GI_ykCju.webp" alt="" style="width:600px" /><figcaption>This is a <strong>figcaption</strong> with <em><code>&lt;img&gt;</code> attrs</em>.</figcaption></figure>
<figure><img src="https://my-blog-554.pages.dev/_astro/cover.BKthU1GI_ykCju.webp" alt="" class="no-zoom" /><figcaption>This is a <strong>figcaption</strong> with <code>class:no-zoom</code>.</figcaption></figure>
<figure><img src="https://my-blog-554.pages.dev/_astro/cover.BKthU1GI_ykCju.webp" alt="If [caption] not set, the alt text from ![]() will be used as the figcaption." /><figcaption>If [caption] not set, the alt text from ![]() will be used as the figcaption.</figcaption></figure>
<figure><img src="https://my-blog-554.pages.dev/_astro/image-black.BeMdDApH_ZV1j5H.webp" alt="" class="img-light" /><figcaption>This example shows different images for light (add <code>class:img-light</code>) and dark (add <code>class:img-dark</code>) modes.</figcaption></figure>
<div><div><div></div><div>WARNING</div></div><div><p>Setting an image’s <code>width</code> attribute directly may cause blurriness. <a href="https://github.com/Dnzzk2/Litos/discussions/17" rel="noopener noreferrer" target="_blank">Learn more</a></p></div></div>
<h3>image-a</h3>
<p>The custom directive wraps an image inside a link, making it clickable.</p>
<p><code>:::image-a{&lt;a&gt; attrs}</code>: Define the link (href), styles, or classes in the curly braces for <code>&lt;a&gt;</code> element.</p>
<p><code>![alt](image path)(&lt;img&gt; attrs)</code>: Same as above.</p>
<pre><code>:::image-a{href="https://github.com/Dnzzk2/Litos"}
![OG image](assets/cover.png)
:::

:::image-a{href="https://github.com/Dnzzk2/Litos" style="display:block" .custom-class}
![OG image](assets/cover.png)(style: margin-bottom: -1rem; transform:scaleX(1.1) scaleY(1.1);, loading: eager)
:::

::::image-a{href="https://github.com/Dnzzk2/Litos"}
:::image-figure[This example shows `:::image-a` wraps around `:::image-figure` (both are interchangeable).]
![OG image](assets/cover.png)
:::
::::

&lt;!-- ❌ No external links provided, it won't work.--&gt;

:::image-a
![OG image](assets/cover.png)
:::
</code></pre>
<a href="https://github.com/Dnzzk2/Litos" rel="noopener noreferrer" target="_blank"><img src="https://my-blog-554.pages.dev/_astro/cover.BKthU1GI_ykCju.webp" alt="OG image" /></a>
<a href="https://github.com/Dnzzk2/Litos" rel="noopener noreferrer" target="_blank"><img src="https://my-blog-554.pages.dev/_astro/cover.BKthU1GI_ykCju.webp" alt="OG image" style="margin-bottom:-1rem;transform:scaleX(1.1) scaleY(1.1)" /></a>
<a href="https://github.com/Dnzzk2/Litos" rel="noopener noreferrer" target="_blank"><figure><img src="https://my-blog-554.pages.dev/_astro/cover.BKthU1GI_ykCju.webp" alt="OG image" style="padding-top:1rem" /><figcaption>This example shows <code>:::image-a</code> wraps around <code>:::image-figure</code> (both are interchangeable).</figcaption></figure></a>
<h3>image-figure-polaroid</h3>
<p>Polaroid style images with a border and shadow.</p>
<p>In order to ensure the style size on the phone, I have set a minimum width of 300px, and you can modify and expand the style in <code>src/styles/picture.css</code>.</p>
<pre><code>:::::image-div-polaroid
:::image-figure-polaroid[This is a **figcaption** with _`&lt;img&gt;` attrs_.]
![OG image](assets/cover.png)
:::
:::::

:::::image-div-polaroid
:::image-figure-polaroid
![OG image](assets/cover.png)

cover.png
:::
:::::

:::::image-div-polaroid
:::image-figure-polaroid
![OG image](assets/cover.png)
:::
:::::

&lt;!-- change style --&gt;

:::::image-div-polaroid
:::image-figure-polaroid{style="width:500px;"}
![OG image](assets/cover.png)
:::
:::::
</code></pre>
<p>This is a <strong>figcaption</strong> with <em><code>&lt;img&gt;</code> attrs</em>.</p><img src="https://my-blog-554.pages.dev/_astro/cover.BKthU1GI_ykCju.webp" alt="OG image" />
<img src="https://my-blog-554.pages.dev/_astro/cover.BKthU1GI_ykCju.webp" alt="OG image" /><p>cover.png</p>
<img src="https://my-blog-554.pages.dev/_astro/cover.BKthU1GI_ykCju.webp" alt="OG image" />
<img src="https://my-blog-554.pages.dev/_astro/cover.BKthU1GI_ykCju.webp" alt="OG image" />
<h2>GitHub Card</h2>
<p>Congratulations from <a href="https://github.com/oopsunix" rel="noopener noreferrer" target="_blank">oopsunix</a></p>
<pre><code>::github{repo="Dnzzk2/Litos"}
</code></pre>
<div>
<a href="https://github.com/Dnzzk2/Litos" target="_blank" rel="noopener noreferrer">
  <div>
    <div>
      <div>
        <div></div>
        <div>Dnzzk2</div>
      </div>
      <div>/</div>
      <div>Litos</div>
    </div>
    <div>
      
        
      
    </div>
  </div>
  <div>Loading...</div>
  <div>
    <div>
      
        
      
      <span>0</span>
    </div>
    <div>
      
        
      
      <span>0</span>
    </div>
    <div>
      
        
      
      <span>-</span>
    </div>
  </div>
</a>

              </div>
<h2>Video Embedding</h2>
<p>Use the <a href="https://github.com/lin-stephanie/remark-directive-sugar?tab=readme-ov-file#video-" rel="noopener noreferrer" target="_blank"><code>::video</code></a> directive from <a href="https://github.com/lin-stephanie/remark-directive-sugar" rel="noopener noreferrer" target="_blank">remark-directive-sugar</a> for consistent video embedding across different platforms. Customize via the <code>video</code> option in <code>plugins/index.ts</code> and style under <code>/* ::video */</code> in <code>src/styles/pro.css</code>.</p>
<p>Say <code>example.md</code> contains:</p>
<pre><code>&lt;!-- Embed a YouTube video --&gt;

::video-youtube{#gxBkghlglTg}

&lt;!-- Embed a Bilibili video with a custom `title` attr --&gt;

::video-bilibili[custom title]{id=BV1MC4y1c7Kv}

&lt;!-- Embed a Vimeo video with class `no-scale` to disable scaling --&gt;

::video-vimeo{id=912831806 class='no-scale'}

&lt;!-- ::video-vimeo{id=912831806 .no-scale} --&gt;

&lt;!-- Embed a custom video URL (must use `id`, not `#`) --&gt;

::video{id=https://www.youtube-nocookie.com/embed/gxBkghlglTg}
</code></pre>
<p>Then <code>example.mdx</code> renders as:</p>




<h2>Styled Link（<code>:link</code>）</h2>
<p>Use the <a href="https://github.com/lin-stephanie/remark-directive-sugar?tab=readme-ov-file#link" rel="noopener noreferrer" target="_blank"><code>:link</code></a> directive from <a href="https://github.com/lin-stephanie/remark-directive-sugar" rel="noopener noreferrer" target="_blank">remark-directive-sugar</a> to add links with avatars or favicons for GitHub, npm, or custom URLs. Customize via the <code>link</code> option in <code>plugins/index.ts</code> and style under <code>/* :link */</code> in <code>src/styles/pro.css</code>.</p>
<p><strong>Link to a GitHub user or organization (prepend <code>id</code> with <code>@</code>)</strong></p>
<p><strong>Example 1</strong>: <code>:link[Dnzzk2]{#@Dnzzk2}</code> links to the GitHub profile of the project maintainer, <a href="https://github.com/Dnzzk2" rel="noopener noreferrer" target="_blank">Dnzzk2</a>.</p>
<p><strong>Example 2</strong>: <code><a href="@vitejs">Vite</a></code> links to the GitHub profile of the <a href="https://github.com/vitejs" rel="noopener noreferrer" target="_blank">Vite</a> organization.</p>
<p><strong>Example 3</strong>: <code>:link{#@Dnzzk2 tab=repositories}</code> links directly to the repositories tab of the GitHub user, like <a href="https://github.com/Dnzzk2?tab=repositories" rel="noopener noreferrer" target="_blank">Dnzzk2</a>. For GitHub users, valid <code>tab</code> options: <code>'repositories','projects', 'packages', 'stars', 'sponsoring', 'sponsors'</code>.</p>
<p><strong>Example 4</strong>: <code>:link{#@vitejs tab=org-people}</code> links directly to the people section of a GitHub organization, like <a href="https://github.com/orgs/vitejs/people" rel="noopener noreferrer" target="_blank">vitejs</a>. For GitHub organizations, valid <code>tab</code> options: <code>'org-repositories', 'org-projects', 'org-packages', 'org-sponsoring', and 'org-people'</code>.</p>
<p><strong>Link to a GitHub repository</strong></p>
<p><strong>Example 5</strong>: <code>:link[Astro]{#withastro/astro}</code> or <code><a href="withastro/astro">Astro</a></code> creates a link to <a href="https://github.com/withastro/astro" rel="noopener noreferrer" target="_blank">Astro</a> repo.</p>
<p><strong>Link to an npm package</strong></p>
<p><strong>Example 6</strong>: <code>:link{#remark-directive-sugar}</code> links to the npm homepage of the <a href="https://www.npmjs.com/package/remark-directive-sugar" rel="noopener noreferrer" target="_blank">remark-directive-sugar</a>.</p>
<p><strong>Example 7</strong>: <code>:link{id=remark-directive-sugar tab=dependencies}</code> links to the dependencies section of the <a href="https://www.npmjs.com/package/remark-directive-sugar?activeTab=dependencies" rel="noopener noreferrer" target="_blank">remark-directive-sugar</a> on npm. For npm package, valid <code>tab</code> options: <code>'readme', 'code', 'dependencies', 'dependents', and 'versions'</code>.</p>
<p><strong>Link to a custom URL (must use <code>id</code>, not <code>#</code>)</strong></p>
<p><strong>Example 8</strong>: <code>:link{id=https://developer.mozilla.org/en-US/docs/Web/JavaScript}</code> creates an external link to the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript" rel="noopener noreferrer" target="_blank">developer.mozilla.org/en-US/docs/Web...</a>.</p>
<p><strong>Example 9</strong>: <code><a href="https://www.google.com/">Google</a></code> creates an external link to the <a href="https://www.google.com/" rel="noopener noreferrer" target="_blank">Google</a>.</p>
<p><strong>Customization</strong></p>
<p><strong>Example 10</strong>: <code><a href="@vitejs url=https://vite.dev/">Vite</a></code> creates a <a href="https://vite.dev/" rel="noopener noreferrer" target="_blank">Vite</a> to <code>https://vite.dev/</code> instead of <code>https://github.com/vitejs</code> by using the <code>url</code>.</p>
<p><strong>Example 11</strong>: <code><a href="@vitejs img=https://vitejs.dev/logo.svg">Vite</a></code> creates a <a href="https://github.com/vitejs" rel="noopener noreferrer" target="_blank">Vite</a> that displays a custom logo by using the <code>img</code>.</p>
<p><strong>Example 12</strong>: <code>:link{id=Dnzzk2/Litos class=github}</code> creates a <a href="https://github.com/Dnzzk2/Litos" rel="noopener noreferrer" target="_blank">Dnzzk2/Litos</a> with <code>class=github</code> (or <code>.github</code>) to override the default style of a GitHub repository.</p>
<p><strong>Example 13</strong>: <code><a href="https://github.com/Dnzzk2/Litos img=https://github.githubassets.com/assets/mona-e50f14d05e4b.png">Litos Themes</a></code> fully customizes a link. <a href="https://github.com/Dnzzk2/Litos" rel="noopener noreferrer" target="_blank">Litos Themes</a></p>
<h2>Badges</h2>
<p>Use the <a href="https://github.com/lin-stephanie/remark-directive-sugar?tab=readme-ov-file#badge-" rel="noopener noreferrer" target="_blank"><code>:badge</code></a> directive from <a href="https://github.com/lin-stephanie/remark-directive-sugar" rel="noopener noreferrer" target="_blank">remark-directive-sugar</a> to display small pieces of information, such as status or category.</p>
<p>The theme provides the following one predefined badges. You can customize them via the <code>badge</code> option in <code>plugins/index.ts</code> and style them under <code>/* :badge */</code> in <code>src/styles/pro.css</code>.</p>
<ul>
<li><code>badge-n</code>: <span>NEW</span></li>
</ul>
<p>Additionally, you can direct use <code>:badge[text]{attrs}</code> for easy visual customization of badges. For example: <code>:badge[ISSUE]{style="background-color: #bef264"}</code> will display as <span>ISSUE</span>. If no color is specified, the default appearance will look like <span>This</span>.</p>
<h2>Details Dropdown</h2>
<pre><code>:::details
::summary[Details Dropdown]

- List item 1
- List item 2
- List item 3
- List item 4
  :::
</code></pre>
Details Dropdown<ul>
<li>List item 1</li>
<li>List item 2</li>
<li>List item 3</li>
<li>List item 4</li>
</ul>
<p>Additionally, it also supports usage similar to the <a href="https://github.com/remarkjs/remark-directive?tab=readme-ov-file#use" rel="noopener noreferrer" target="_blank">examples in remark-directive</a>.</p>]]></content:encoded>
      <author>Dnzzk2</author>
      <category>Litos</category><category>Documentation</category>
    </item>
    <item>
      <title>Project And Tag Page Config</title>
      <link>https://my-blog-554.pages.dev//posts/project-tag</link>
      <guid>https://my-blog-554.pages.dev//posts/project-tag</guid>
      <updated>2025-08-11T00:00:00.000Z</updated>
      <pubDate>2025-08-11T00:00:00.000Z</pubDate>
      <description><![CDATA[Configure Projects and Tags pages: page texts (PROJECTS_CONFIG, TAGS_CONFIG), project content frontmatter, file locations, and usage examples.]]></description>
      <content:encoded><![CDATA[<img src="https://my-blog-554.pages.dev/_astro/cover.Cz2psVoH_Z1OReC1.webp" alt="Project And Tag Page Config" style="width: 100%; height: auto; margin-bottom: 1em;" />
<p>Due to the small size of the Project and tag page configurations, they are merged into one document.</p>
<p>The following files are compatible with this document:</p>
<ul>
<li><code>src/pages/projects/index.astro</code> - project page.</li>
<li><code>src/pages/tags/index.astro</code> - tag statistics page.</li>
<li><code>src/pages/tags/[tag]/[...page].astro</code> - specific tag display post list page.</li>
<li><code>src/config.ts</code> - project and tag page config.</li>
<li><code>src/components/base</code> - most of the components come from here.</li>
</ul>
<h3>Project Page Config</h3>
<p>Configure page texts:</p>
<pre><code>export const PROJECTS_CONFIG: ProjectConfig = {
  title: 'Projects',
  description: 'The examples of my projects.',
  introduce: 'The examples of my projects.',
}
</code></pre>





















<table><thead><tr><th>Property</th><th>Description</th></tr></thead><tbody><tr><td>title</td><td>Title displayed on browser tags and title displayed on page.</td></tr><tr><td>description</td><td>The metadata description in the <code>head</code> element of the page.</td></tr><tr><td>introduce</td><td>The introduce below the title on the page.</td></tr></tbody></table>
<h3>Project content</h3>
<p>The content displayed on the project page comes from <code>/src/content/projects</code>.</p>
<p>Its writing style is similar to posts: one MDX file represents one project.</p>
<h4>Project frontmatter</h4>
<p>Example (<code>src/content/projects/Litos/index.mdx</code>):</p>
<pre><code>---
name: 'Litos'
description: 'A Simple &amp; Modern Blog Theme for Astro.'
githubUrl: 'https://github.com/Dnzzk2/Litos'
website: 'https://litos.vercel.app/'
type: 'image'
icon: '../../../../public/projects/litos.png'
imageClass: 'w-10 h-10'
star: 32
fork: 7
---
</code></pre>













































<table><thead><tr><th>Property</th><th>Description</th></tr></thead><tbody><tr><td>name</td><td>Project name.</td></tr><tr><td>description</td><td>Short description shown with the project card.</td></tr><tr><td>githubUrl</td><td>GitHub repository URL of the project.</td></tr><tr><td>website</td><td>Project website or demo URL.</td></tr><tr><td>type</td><td>Display type for the project card. For now, <code>'image'</code> shows a thumbnail.</td></tr><tr><td>icon</td><td>Icon path for the project (supports paths under <code>public/</code>).</td></tr><tr><td>imageClass</td><td>Extra classes for sizing the image (e.g., Tailwind classes).</td></tr><tr><td>star</td><td>Stars count (optional).</td></tr><tr><td>fork</td><td>Forks count (optional).</td></tr></tbody></table>
<h3>Tag Page Config</h3>
<pre><code>export const TAGS_CONFIG: TagsConfig = {
  title: 'Tags',
  description: 'All tags of Posts',
  introduce: 'All the tags for posts are here, you can click to filter them.',
}
</code></pre>





















<table><thead><tr><th>Property</th><th>Description</th></tr></thead><tbody><tr><td>title</td><td>Title displayed on browser tags and title displayed on the tag statistics page.</td></tr><tr><td>description</td><td>The metadata description in the <code>head</code> element of the tag statistics page.</td></tr><tr><td>introduce</td><td>The introduce below the title on the tag statistics page.</td></tr></tbody></table>]]></content:encoded>
      <author>Dnzzk2</author>
      <category>Litos</category><category>Documentation</category>
    </item>
    <item>
      <title>Photos Page Config</title>
      <link>https://my-blog-554.pages.dev//posts/photos</link>
      <guid>https://my-blog-554.pages.dev//posts/photos</guid>
      <updated>2025-08-10T00:00:00.000Z</updated>
      <pubDate>2025-08-10T00:00:00.000Z</pubDate>
      <description><![CDATA[Current implementation notes for the Photos page, including PHOTOS_CONFIG, PhotosList, and getPhotos().]]></description>
      <content:encoded><![CDATA[<img src="https://my-blog-554.pages.dev/_astro/cover.Do3E5r53_Z1UoMh2.webp" alt="Photos Page Config" style="width: 100%; height: auto; margin-bottom: 1em;" />
<p>This document describes the current implementation of the Photos page.</p>
<h2>Current file structure</h2>
<ul>
<li><code>src/pages/photos/index.astro</code>
<ul>
<li>Reads page text from <code>PHOTOS_CONFIG</code></li>
<li>Reads timeline data from <code>PhotosList</code></li>
</ul>
</li>
<li><code>src/config.ts</code>
<ul>
<li>Stores <code>PHOTOS_CONFIG</code></li>
</ul>
</li>
<li><code>src/lib/photos.ts</code>
<ul>
<li>Stores <code>PhotosList</code></li>
<li>Auto-imports photo files</li>
<li>Converts one folder of images into <code>Photo[]</code> with <code>getPhotos()</code></li>
</ul>
</li>
<li><code>src/types.ts</code>
<ul>
<li>Defines <code>PhotoData</code>, <code>Photo</code>, and <code>PolaroidVariant</code></li>
</ul>
</li>
<li><code>src/components/photos/PolaroidCard.tsx</code>
<ul>
<li>Maps each <code>variant</code> to the actual card size</li>
</ul>
</li>
</ul>
<h2>Page text</h2>
<p>The page title and intro text come from <code>src/config.ts</code>.</p>
<pre><code>export const PHOTOS_CONFIG: PhotosConfig = {
  title: 'Photos',
  description: 'Here I will record some photos taken in daily life.',
  introduce: 'Here I will record some photos taken in daily life.',
}
</code></pre>
<p><code>src/pages/photos/index.astro</code> renders the page like this:</p>
<pre><code>---
import { PHOTOS_CONFIG } from '~/config'
import { PhotosList } from '~/lib/photos'

const { title, description, introduce } = PHOTOS_CONFIG
---

&lt;Layout {title} {description}&gt;
  &lt;PageTitle {title} {introduce} /&gt;
  &lt;PhotoTimeline photoData={PhotosList} /&gt;
&lt;/Layout&gt;
</code></pre>
<h2>PhotosList</h2>
<p><code>PhotosList</code> is defined in <code>src/lib/photos.ts</code>. Each item is one timeline entry.</p>
<pre><code>export const PhotosList: PhotoData[] = [
  {
    title: 'Ningbo - Botanical Garden',
    icon: { type: 'emoji', value: '🌼' },
    description: 'It was early spring, so I went to see the cherry blossoms.',
    date: '2026-03-07',
    travel: '',
    photos: getPhotos(
      '2026-03-07-botanicalGarden',
      'Early spring cherry blossoms at the botanical garden',
      ['3x4', '3x4', '3x4', '3x4', '3x4', '3x4']
    ),
  },
]
</code></pre>
<h3>PhotoData fields</h3>

































<table><thead><tr><th>Field</th><th>Meaning</th></tr></thead><tbody><tr><td><code>title</code></td><td>Timeline title</td></tr><tr><td><code>icon</code></td><td>Left-side timeline icon</td></tr><tr><td><code>description</code></td><td>Optional timeline description</td></tr><tr><td><code>date</code></td><td>Timeline date</td></tr><tr><td><code>travel</code></td><td>Optional extra label</td></tr><tr><td><code>photos</code></td><td>Array of <code>Photo</code> objects</td></tr></tbody></table>
<h2>How <code>getPhotos()</code> works</h2>
<p>Current implementation:</p>
<pre><code>function getPhotos(dir: string, alt: string, variants: PolaroidVariant[]): Photo[] {
  return Object.entries(photoModules)
    .filter(([path]) =&gt; path.includes(`/${dir}/`))
    .sort(([a], [b]) =&gt; a.localeCompare(b))
    .map(([, mod], index) =&gt; {
      const img = mod.default
      return {
        src: img,
        alt,
        width: img.width,
        height: img.height,
        variant: variants[index] || '4x3',
      }
    })
}
</code></pre>
<h3>Parameter meaning</h3>





















<table><thead><tr><th>Parameter</th><th>Meaning</th></tr></thead><tbody><tr><td><code>dir</code></td><td>Folder name under <code>src/assets/photos</code></td></tr><tr><td><code>alt</code></td><td>Shared <code>alt</code> text for every image returned from that folder</td></tr><tr><td><code>variants</code></td><td>Ratio list matched to the sorted images by index</td></tr></tbody></table>
<h3>Important behavior</h3>
<ol>
<li><code>getPhotos()</code> first filters all imported images by folder name.</li>
<li>It then sorts the matched file paths with <code>localeCompare</code>.</li>
<li>It creates the final <code>Photo[]</code> in that sorted order.</li>
<li><code>variants[index]</code> is applied to the photo at the same index.</li>
<li>If one index is missing in <code>variants</code>, that photo falls back to <code>'4x3'</code>.</li>
</ol>
<h3>How the third parameter is matched</h3>
<p>The third parameter is not random metadata. It is position-based.</p>
<p>For this code:</p>
<pre><code>photos: getPhotos(
  '2026-03-07-botanicalGarden',
  'Early spring cherry blossoms at the botanical garden',
  ['3x4', '3x4', '3x4', '3x4', '3x4', '3x4']
)
</code></pre>
<p>Assume the folder <code>src/assets/photos/2026-03-07-botanicalGarden/</code> contains these files after sorting:</p>
<pre><code>01.webp
02.webp
03.webp
04.webp
05.webp
06.webp
</code></pre>
<p>Then the mapping is:</p>

































<table><thead><tr><th>File</th><th>Applied variant</th></tr></thead><tbody><tr><td><code>01.webp</code></td><td>first item in the array -&gt; <code>3x4</code></td></tr><tr><td><code>02.webp</code></td><td>second item in the array -&gt; <code>3x4</code></td></tr><tr><td><code>03.webp</code></td><td>third item in the array -&gt; <code>3x4</code></td></tr><tr><td><code>04.webp</code></td><td>fourth item in the array -&gt; <code>3x4</code></td></tr><tr><td><code>05.webp</code></td><td>fifth item in the array -&gt; <code>3x4</code></td></tr><tr><td><code>06.webp</code></td><td>sixth item in the array -&gt; <code>3x4</code></td></tr></tbody></table>
<p>So if you want the first photo in the folder to use <code>3x4</code>, the first item in the third array must be <code>3x4</code>.</p>
<p>If you want mixed ratios, write them in the same order as the sorted files:</p>
<pre><code>photos: getPhotos('2025-03-01-dongqianhu', 'Ningbo - Dongqian Lake', ['4x5', '1x1', '4x3'])
</code></pre>
<p>That means:</p>
<ul>
<li>first sorted image -&gt; <code>4x5</code></li>
<li>second sorted image -&gt; <code>1x1</code></li>
<li>third sorted image -&gt; <code>4x3</code></li>
</ul>
<p>If the folder contains more photos than the array length:</p>
<pre><code>photos: getPhotos('example-folder', 'Example alt', ['3x4', '4x5'])
</code></pre>
<p>Then:</p>
<ul>
<li>first sorted image -&gt; <code>3x4</code></li>
<li>second sorted image -&gt; <code>4x5</code></li>
<li>third sorted image and later -&gt; default <code>4x3</code></li>
</ul>
<h2>Supported variants</h2>
<p><code>PolaroidVariant</code> is currently:</p>
<pre><code>export type PolaroidVariant = '1x1' | '4x5' | '4x3' | '3x4' | '9x16'
</code></pre>
<p>Current size mapping in <code>src/components/photos/PolaroidCard.tsx</code>:</p>
<pre><code>const polaroidVariants: Record&lt;PolaroidVariant, string&gt; = {
  '1x1': 'w-20 h-20',
  '4x5': 'w-20 h-24',
  '4x3': 'w-20 h-16',
  '3x4': 'w-[4.5rem] h-24',
  '9x16': 'w-20 h-32',
}
</code></pre>
<h2>How to add a new timeline entry</h2>
<ol>
<li>Create a folder under <code>src/assets/photos/</code>, for example <code>2026-04-01-spring-walk</code>.</li>
<li>Put image files into that folder.</li>
<li>Make sure the file names are in the order you want after sorting.</li>
<li>Add one item to <code>PhotosList</code> in <code>src/lib/photos.ts</code>.</li>
<li>Pass the third argument to <code>getPhotos()</code> in the same order as the sorted files.</li>
</ol>
<p>Example:</p>
<pre><code>{
  title: 'Spring Walk',
  icon: { type: 'emoji', value: '🌿' },
  description: 'A short walk with a camera.',
  date: '2026-04-01',
  travel: '',
  photos: getPhotos(
    '2026-04-01-spring-walk',
    'Photos from a spring walk',
    ['3x4', '4x3', '4x5', '4x3']
  ),
}
</code></pre>
<h2>Notes</h2>
<ul>
<li><code>alt</code> is applied to every photo returned from the same folder.</li>
<li>The order is determined by sorted file path, not by import order in the editor.</li>
<li>If you rename files, the sort order may change, and the <code>variants</code> array will then map to different photos.</li>
</ul>]]></content:encoded>
      <author>Dnzzk2</author>
      <category>Litos</category><category>Documentation</category>
    </item>
  </channel>
</rss>