feat: added draft field to frontmatter config
e1bd0d82
6 file(s) · +52 −1
| 51 | 51 | | `publishDate` | `string` | Yes | `"publishDate"`, `"pubDate"`, `"date"`, `"createdAt"`, `"created_at"` | Publication date | |
|
| 52 | 52 | | `coverImage` | `string` | No | `"ogImage"` | Cover image filename | |
|
| 53 | 53 | | `tags` | `string[]` | No | `"tags"` | Post tags/categories | |
|
| 54 | + | | `draft` | `boolean` | No | `"draft"` | If `true`, post is skipped during publish | |
|
| 54 | 55 | ||
| 55 | 56 | ### Example |
|
| 56 | 57 | ||
| 61 | 62 | publishDate: 2024-01-15 |
|
| 62 | 63 | ogImage: cover.jpg |
|
| 63 | 64 | tags: [welcome, intro] |
|
| 65 | + | draft: false |
|
| 64 | 66 | --- |
|
| 65 | 67 | ``` |
|
| 66 | 68 | ||
| 72 | 74 | { |
|
| 73 | 75 | "frontmatter": { |
|
| 74 | 76 | "publishDate": "date", |
|
| 75 | - | "coverImage": "thumbnail" |
|
| 77 | + | "coverImage": "thumbnail", |
|
| 78 | + | "draft": "private" |
|
| 76 | 79 | } |
|
| 77 | 80 | } |
|
| 78 | 81 | ``` |
|
| 45 | 45 | ||
| 46 | 46 | The `maxAgeDays` setting prevents flooding your feed when first setting up Sequoia. For example, if you have 40 existing blog posts, only those published within the last 30 days will be posted to Bluesky. |
|
| 47 | 47 | ||
| 48 | + | ## Draft Posts |
|
| 49 | + | ||
| 50 | + | Posts with `draft: true` in their frontmatter are automatically skipped during publishing. This lets you work on content without accidentally publishing it. |
|
| 51 | + | ||
| 52 | + | ```yaml |
|
| 53 | + | --- |
|
| 54 | + | title: Work in Progress |
|
| 55 | + | draft: true |
|
| 56 | + | --- |
|
| 57 | + | ``` |
|
| 58 | + | ||
| 59 | + | If your framework uses a different field name (like `private` or `hidden`), configure it in `sequoia.json`: |
|
| 60 | + | ||
| 61 | + | ```json |
|
| 62 | + | { |
|
| 63 | + | "frontmatter": { |
|
| 64 | + | "draft": "private" |
|
| 65 | + | } |
|
| 66 | + | } |
|
| 67 | + | ``` |
|
| 68 | + | ||
| 48 | 69 | ## Troubleshooting |
|
| 49 | 70 | ||
| 50 | 71 | - If you have files in your markdown directory that should be ignored, use the [`ignore` array in the config](/config#ignoring-files). |
| 138 | 138 | defaultValue: "tags", |
|
| 139 | 139 | placeholder: "tags, categories, keywords, etc.", |
|
| 140 | 140 | }), |
|
| 141 | + | draftField: () => |
|
| 142 | + | text({ |
|
| 143 | + | message: "Field name for draft status:", |
|
| 144 | + | defaultValue: "draft", |
|
| 145 | + | placeholder: "draft, private, hidden, etc.", |
|
| 146 | + | }), |
|
| 141 | 147 | }, |
|
| 142 | 148 | { onCancel }, |
|
| 143 | 149 | ); |
|
| 149 | 155 | ["publishDate", frontmatterConfig.dateField, "publishDate"], |
|
| 150 | 156 | ["coverImage", frontmatterConfig.coverField, "ogImage"], |
|
| 151 | 157 | ["tags", frontmatterConfig.tagsField, "tags"], |
|
| 158 | + | ["draft", frontmatterConfig.draftField, "draft"], |
|
| 152 | 159 | ]; |
|
| 153 | 160 | ||
| 154 | 161 | const builtMapping = fieldMappings.reduce<FrontmatterMapping>( |
|
| 96 | 96 | action: "create" | "update"; |
|
| 97 | 97 | reason: string; |
|
| 98 | 98 | }> = []; |
|
| 99 | + | const draftPosts: BlogPost[] = []; |
|
| 99 | 100 | ||
| 100 | 101 | for (const post of posts) { |
|
| 102 | + | // Skip draft posts |
|
| 103 | + | if (post.frontmatter.draft) { |
|
| 104 | + | draftPosts.push(post); |
|
| 105 | + | continue; |
|
| 106 | + | } |
|
| 107 | + | ||
| 101 | 108 | const contentHash = await getContentHash(post.rawContent); |
|
| 102 | 109 | const relativeFilePath = path.relative(configDir, post.filePath); |
|
| 103 | 110 | const postState = state.posts[relativeFilePath]; |
|
| 123 | 130 | reason: "content changed", |
|
| 124 | 131 | }); |
|
| 125 | 132 | } |
|
| 133 | + | } |
|
| 134 | + | ||
| 135 | + | if (draftPosts.length > 0) { |
|
| 136 | + | log.info(`Skipping ${draftPosts.length} draft post${draftPosts.length === 1 ? "" : "s"}`); |
|
| 126 | 137 | } |
|
| 127 | 138 | ||
| 128 | 139 | if (postsToPublish.length === 0) { |
|
| 99 | 99 | const tagsField = mapping?.tags || "tags"; |
|
| 100 | 100 | frontmatter.tags = raw[tagsField] || raw.tags; |
|
| 101 | 101 | ||
| 102 | + | // Draft mapping |
|
| 103 | + | const draftField = mapping?.draft || "draft"; |
|
| 104 | + | const draftValue = raw[draftField] ?? raw.draft; |
|
| 105 | + | if (draftValue !== undefined) { |
|
| 106 | + | frontmatter.draft = draftValue === true || draftValue === "true"; |
|
| 107 | + | } |
|
| 108 | + | ||
| 102 | 109 | // Always preserve atUri (internal field) |
|
| 103 | 110 | frontmatter.atUri = raw.atUri; |
|
| 104 | 111 |
| 4 | 4 | publishDate?: string; // Field name for publish date (default: "publishDate", also checks "pubDate", "date", "createdAt", "created_at") |
|
| 5 | 5 | coverImage?: string; // Field name for cover image (default: "ogImage") |
|
| 6 | 6 | tags?: string; // Field name for tags (default: "tags") |
|
| 7 | + | draft?: string; // Field name for draft status (default: "draft") |
|
| 7 | 8 | } |
|
| 8 | 9 | ||
| 9 | 10 | // Strong reference for Bluesky post (com.atproto.repo.strongRef) |
|
| 46 | 47 | tags?: string[]; |
|
| 47 | 48 | ogImage?: string; |
|
| 48 | 49 | atUri?: string; |
|
| 50 | + | draft?: boolean; |
|
| 49 | 51 | } |
|
| 50 | 52 | ||
| 51 | 53 | export interface BlogPost { |
|