mirror of
https://github.com/svemagie/obsidian-micropub.git
synced 2026-05-15 20:08:51 +02:00
feat: use kebab-case AI frontmatter keys (ai-text-level, ai-tools, etc.)
Micropub properties now sent as kebab-case; camelCase still accepted as input for backward compatibility. README updated accordingly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -68,9 +68,9 @@ Tag any note in Obsidian with a `#garden/*` tag, or set `gardenStage` directly i
|
||||
|
||||
| Obsidian tag | Published property | Blog display |
|
||||
|---|---|---|
|
||||
| `#garden/plant` | `gardenStage: plant` | 🌱 Seedling |
|
||||
| `#garden/cultivate` | `gardenStage: cultivate` | 🌿 Growing |
|
||||
| `#garden/evergreen` | `gardenStage: evergreen` | 🌳 Evergreen |
|
||||
| `#garden/cultivate` | `gardenStage: cultivate` | 🌿 Growing |
|
||||
| `#garden/plant` | `gardenStage: plant` | 🌱 Seedling |
|
||||
| `#garden/question` | `gardenStage: question` | ❓ Open Question |
|
||||
| `#garden/repot` | `gardenStage: repot` | 🪴 Repotting |
|
||||
| `#garden/revitalize` | `gardenStage: revitalize` | ✨ Revitalizing |
|
||||
@@ -84,14 +84,15 @@ The Eleventy blog renders a coloured badge on each post and groups all garden po
|
||||
---
|
||||
title: "On building in public"
|
||||
tags:
|
||||
- garden/cultivate
|
||||
- garden/plant
|
||||
category:
|
||||
- indieweb
|
||||
---
|
||||
|
||||
Some early thoughts on the merits of building in public...
|
||||
```
|
||||
|
||||
After publishing, the frontmatter gains:
|
||||
After publishing, the frontmatter/property in Obsidian gains:
|
||||
|
||||
```yaml
|
||||
mp-url: "https://example.com/articles/2026/on-building-in-public"
|
||||
@@ -108,7 +109,7 @@ mp-url: "https://example.com/articles/2026/on-building-in-public"
|
||||
| `title` | Sets the post `name` (article mode) |
|
||||
| `created` / `date` | Sets `published` date (`created` takes priority — matches Obsidian's default date field) |
|
||||
| `postType` | Force post type: `article` sends a title (uses filename if none set), `note` skips title |
|
||||
| `tags` / `category` | Becomes Micropub `category` (excluding `garden/*` and bare `garden` tags) |
|
||||
| `tags` + `category` | Both merged into Micropub `category` (excluding `garden/*` and bare `garden` tags, deduplicated) |
|
||||
| `summary` / `excerpt` | Sets `summary` property |
|
||||
| `visibility` | `public` / `unlisted` / `private` |
|
||||
| `gardenStage` | Explicit garden stage — see table below |
|
||||
@@ -118,14 +119,14 @@ mp-url: "https://example.com/articles/2026/on-building-in-public"
|
||||
|
||||
### AI disclosure properties
|
||||
|
||||
Use flat top-level properties for best Obsidian compatibility (Obsidian's Properties UI handles them more reliably than nested objects):
|
||||
Use flat kebab-case properties (camelCase fallback supported for backward compatibility):
|
||||
|
||||
| Property | Values | Meaning |
|
||||
|---|---|---|
|
||||
| `aiTextLevel` | `"0"` `"1"` `"2"` `"3"` | None / Editorial / Co-drafted / AI-generated |
|
||||
| `aiCodeLevel` | `"0"` `"1"` `"2"` | None / AI-assisted / AI-generated |
|
||||
| `aiTools` | string | Tools used, e.g. `"Claude"` |
|
||||
| `aiDescription` | string | Free-text disclosure note |
|
||||
| `ai-text-level` | `"0"` `"1"` `"2"` `"3"` | None / Editorial / Co-drafted / AI-generated |
|
||||
| `ai-code-level` | `"0"` `"1"` `"2"` | None / AI-assisted / AI-generated |
|
||||
| `ai-tools` | string | Tools used, e.g. `"Claude"` |
|
||||
| `ai-description` | string | Free-text disclosure note |
|
||||
|
||||
Nested `ai:` objects (e.g. `ai: {textLevel: "1"}`) also work but flat keys are recommended.
|
||||
|
||||
@@ -137,11 +138,14 @@ title: "My Post"
|
||||
created: 2026-03-15T10:00:00
|
||||
postType: article
|
||||
tags:
|
||||
- garden/cultivate
|
||||
- garden/evergreen
|
||||
category:
|
||||
- indieweb
|
||||
aiTextLevel: "1"
|
||||
aiCodeLevel: "0"
|
||||
aiTools: "Claude"
|
||||
- lang/en
|
||||
ai-text-level: "1"
|
||||
ai-code-level: "0"
|
||||
ai-tools: "Claude"
|
||||
ai-description: "AI helped refine the structure"
|
||||
---
|
||||
```
|
||||
|
||||
|
||||
+19
-16
@@ -137,14 +137,19 @@ export class Publisher {
|
||||
props["published"] = [new Date(String(rawDate)).toISOString()];
|
||||
}
|
||||
|
||||
// Categories from frontmatter `category` or `tags` (excluding garden/* tags)
|
||||
const rawTags = this.resolveArray(fm["tags"] ?? fm["category"]);
|
||||
// Categories from frontmatter `category` AND `tags` (excluding garden/* tags).
|
||||
// Merge both fields — `tags` may contain garden/* stages while `category`
|
||||
// holds the actual topic categories sent to Micropub.
|
||||
const rawTags = [
|
||||
...this.resolveArray(fm["tags"]),
|
||||
...this.resolveArray(fm["category"]),
|
||||
];
|
||||
const gardenStageFromTags = this.extractGardenStage(rawTags);
|
||||
const normalTags = rawTags.filter(
|
||||
(t) => !t.startsWith(GARDEN_TAG_PREFIX) && t !== "garden",
|
||||
);
|
||||
if (normalTags.length > 0) {
|
||||
props["category"] = normalTags;
|
||||
props["category"] = [...new Set(normalTags)];
|
||||
}
|
||||
|
||||
// Garden stage — prefer explicit `gardenStage` frontmatter property,
|
||||
@@ -178,22 +183,20 @@ export class Publisher {
|
||||
props["visibility"] = [visibility];
|
||||
}
|
||||
|
||||
// AI disclosure — flatten nested `ai` object into individual top-level
|
||||
// properties so Indiekit writes them as plain scalar frontmatter keys.
|
||||
// Also support top-level `aiTextLevel`, `aiTools`, etc. set directly.
|
||||
// Sending `ai: [{textLevel: "1"}]` makes Indiekit write a YAML array,
|
||||
// but the template reads `aiTextLevel` / `aiCodeLevel` as top-level scalars.
|
||||
// AI disclosure — kebab-case keys (ai-text-level, ai-tools, etc.)
|
||||
// with camelCase fallback for backward compatibility.
|
||||
// Also support nested `ai` object flattening.
|
||||
const aiObj = (fm["ai"] && typeof fm["ai"] === "object")
|
||||
? fm["ai"] as Record<string, unknown>
|
||||
: {};
|
||||
const aiTextLevel = fm["aiTextLevel"] ?? aiObj["textLevel"];
|
||||
const aiCodeLevel = fm["aiCodeLevel"] ?? aiObj["codeLevel"];
|
||||
const aiTools = fm["aiTools"] ?? aiObj["aiTools"] ?? aiObj["tools"];
|
||||
const aiDescription = fm["aiDescription"] ?? aiObj["aiDescription"] ?? aiObj["description"];
|
||||
if (aiTextLevel != null) props["aiTextLevel"] = [String(aiTextLevel)];
|
||||
if (aiCodeLevel != null) props["aiCodeLevel"] = [String(aiCodeLevel)];
|
||||
if (aiTools != null) props["aiTools"] = [String(aiTools)];
|
||||
if (aiDescription != null) props["aiDescription"] = [String(aiDescription)];
|
||||
const aiTextLevel = fm["ai-text-level"] ?? fm["aiTextLevel"] ?? aiObj["textLevel"];
|
||||
const aiCodeLevel = fm["ai-code-level"] ?? fm["aiCodeLevel"] ?? aiObj["codeLevel"];
|
||||
const aiTools = fm["ai-tools"] ?? fm["aiTools"] ?? aiObj["aiTools"] ?? aiObj["tools"];
|
||||
const aiDescription = fm["ai-description"] ?? fm["aiDescription"] ?? aiObj["aiDescription"] ?? aiObj["description"];
|
||||
if (aiTextLevel != null) props["ai-text-level"] = [String(aiTextLevel)];
|
||||
if (aiCodeLevel != null) props["ai-code-level"] = [String(aiCodeLevel)];
|
||||
if (aiTools != null) props["ai-tools"] = [String(aiTools)];
|
||||
if (aiDescription != null) props["ai-description"] = [String(aiDescription)];
|
||||
|
||||
// Photos: prefer structured photo array from frontmatter (with alt text),
|
||||
// fall back to uploaded local images.
|
||||
|
||||
Reference in New Issue
Block a user