feat: Obsidian 插件支持文章封面解析与同步
continuous-integration/drone/push Build is passing

This commit is contained in:
seahi committed 2026-10-07 10:33:48 +08:00
1 parent 3a543a7ca5
commit 5df6fe45e8
3 files changed
+56 -79

No files matched your search

+29
View File
@@ -11,6 +11,7 @@ export interface PostPayload {
title: string;
slug: string;
content: string;
cover?: string | null;
category_slug?: string;
series_slug?: string;
tags?: string[];
@@ -20,6 +21,7 @@ export interface PostMeta {
id: number;
slug: string;
title: string;
cover?: string | null;
published_at: string;
category_slug?: string;
category_name?: string;
@@ -33,6 +35,33 @@ export interface PostDetail extends PostMeta {
updated_at: string;
}
export function formatPostMarkdown(detail: PostDetail): string {
const frontmatterLines = [
'---',
`title: "${detail.title.replace(/"/g, '\\"')}"`,
`slug: ${detail.slug}`,
`blogflare_published_slug: ${detail.slug}`
];
if (detail.cover) {
frontmatterLines.push(`cover: "${detail.cover.replace(/"/g, '\\"')}"`);
}
if (detail.category_slug) {
frontmatterLines.push(`category_slug: ${detail.category_slug}`);
}
if (detail.series_slug) {
frontmatterLines.push(`series_slug: ${detail.series_slug}`);
}
if (detail.tags && detail.tags.length > 0) {
frontmatterLines.push('tags:');
for (const t of detail.tags) {
frontmatterLines.push(` - ${t}`);
}
}
frontmatterLines.push('---');
return frontmatterLines.join('\n') + '\n\n' + (detail.content || '');
}
export class BlogFlareAPI {
private settings: BlogFlareSettings;
+25 -65
View File
@@ -4,7 +4,7 @@ import {
DEFAULT_SETTINGS,
BlogFlareSettingTab,
} from './settings';
import { BlogFlareAPI } from './api';
import { BlogFlareAPI, formatPostMarkdown, type PostPayload } from './api';
import { MetaSuggestModal } from './suggest';
import { PullSuggestModal } from './pull_modal';
@@ -169,23 +169,7 @@ export default class BlogFlarePlugin extends Plugin {
for (const item of items) {
try {
const detail = await this.api.getPost(item.slug);
// 组装 Frontmatter
const frontmatterLines = [
'---',
`title: "${detail.title.replace(/"/g, '\\"')}"`,
`slug: ${detail.slug}`,
`blogflare_published_slug: ${detail.slug}`
];
if (detail.category_slug) {
frontmatterLines.push(`category_slug: ${detail.category_slug}`);
}
if (detail.series_slug) {
frontmatterLines.push(`series_slug: ${detail.series_slug}`);
}
frontmatterLines.push('---');
const fileContent = frontmatterLines.join('\n') + '\n\n' + (detail.content || '');
const fileContent = formatPostMarkdown(detail);
const baseName = detail.title.replace(/[/\\:]/g, '-');
let dirPath = '';
@@ -329,29 +313,7 @@ export default class BlogFlarePlugin extends Plugin {
for (const item of items) {
try {
const detail = await this.api.getPost(item.slug);
// 组装 Frontmatter
const frontmatterLines = [
'---',
`title: "${detail.title.replace(/"/g, '\\"')}"`,
`slug: ${detail.slug}`,
`blogflare_published_slug: ${detail.slug}`
];
if (detail.category_slug) {
frontmatterLines.push(`category_slug: ${detail.category_slug}`);
}
if (detail.series_slug) {
frontmatterLines.push(`series_slug: ${detail.series_slug}`);
}
if (detail.tags && detail.tags.length > 0) {
frontmatterLines.push(`tags:`);
for (const t of detail.tags) {
frontmatterLines.push(` - ${t}`);
}
}
frontmatterLines.push('---');
const fileContent = frontmatterLines.join('\n') + '\n\n' + (detail.content || '');
const fileContent = formatPostMarkdown(detail);
const existingFile = slugMap.get(item.slug);
if (existingFile) {
@@ -418,12 +380,28 @@ export default class BlogFlarePlugin extends Plugin {
const series_slug = frontmatter?.series_slug as string | undefined;
const publishedSlug = frontmatter?.blogflare_published_slug as string | undefined;
const hasCoverProp = Boolean(
frontmatter &&
('cover' in frontmatter || 'featured_image' in frontmatter || 'banner' in frontmatter)
);
const coverCandidate: unknown = frontmatter?.cover ?? frontmatter?.featured_image ?? frontmatter?.banner;
let coverVal: string | null | undefined = undefined;
if (hasCoverProp) {
if (typeof coverCandidate === 'string') {
coverVal = coverCandidate.trim() || null;
} else if (typeof coverCandidate === 'number' || typeof coverCandidate === 'boolean') {
coverVal = String(coverCandidate).trim() || null;
} else if (!coverCandidate) {
coverVal = null;
}
}
let tags: string[] | undefined = undefined;
if (frontmatter?.tags) {
tags = Array.isArray(frontmatter.tags) ? frontmatter.tags : [frontmatter.tags as string];
}
const payload = {
const payload: PostPayload = {
title,
slug,
content,
@@ -434,6 +412,9 @@ export default class BlogFlarePlugin extends Plugin {
if (publishedSlug) {
// 已发布,进行更新
if (coverVal !== undefined) {
payload.cover = coverVal;
}
const res = await this.api.updatePost(publishedSlug, payload);
if (res.success) {
new Notice('文章更新成功!');
@@ -442,6 +423,7 @@ export default class BlogFlarePlugin extends Plugin {
}
} else {
// 未发布,进行创建
payload.cover = coverVal ?? null;
const res = await this.api.createPost(payload);
if (res.success && res.slug) {
// 写入 blogflare_published_slug
@@ -478,29 +460,7 @@ export default class BlogFlarePlugin extends Plugin {
try {
new Notice('开始下载文章...');
const detail = await this.api.getPost(publishedSlug);
// 组装 Frontmatter
const frontmatterLines = [
'---',
`title: "${detail.title.replace(/"/g, '\\"')}"`,
`slug: ${detail.slug}`,
`blogflare_published_slug: ${detail.slug}`
];
if (detail.category_slug) {
frontmatterLines.push(`category_slug: ${detail.category_slug}`);
}
if (detail.series_slug) {
frontmatterLines.push(`series_slug: ${detail.series_slug}`);
}
if (detail.tags && detail.tags.length > 0) {
frontmatterLines.push(`tags:`);
for (const t of detail.tags) {
frontmatterLines.push(` - ${t}`);
}
}
frontmatterLines.push('---');
const fileContent = frontmatterLines.join('\n') + '\n\n' + (detail.content || '');
const fileContent = formatPostMarkdown(detail);
await this.app.vault.modify(file, fileContent);
new Notice('文章下载并更新成功!');
+2 -14
View File
@@ -1,5 +1,6 @@
import { App, FuzzySuggestModal, Notice, TFile } from 'obsidian';
import type { PostMeta, BlogFlareAPI } from './api';
import { formatPostMarkdown } from './api';
export class PullSuggestModal extends FuzzySuggestModal<PostMeta> {
private items: PostMeta[];
@@ -24,20 +25,7 @@ export class PullSuggestModal extends FuzzySuggestModal<PostMeta> {
void (async () => {
try {
const detail = await this.api.getPost(item.slug);
// 组装 Frontmatter
const frontmatterLines = [
'---',
`title: "${detail.title.replace(/"/g, '\\"')}"`,
`slug: ${detail.slug}`,
`blogflare_published_slug: ${detail.slug}`
];
if (detail.category_slug) {
frontmatterLines.push(`category_slug: ${detail.category_slug}`);
}
frontmatterLines.push('---');
const fileContent = frontmatterLines.join('\n') + '\n\n' + (detail.content || '');
const fileContent = formatPostMarkdown(detail);
// 决定文件名 (过滤掉非法字符)
const baseName = detail.title.replace(/[/\\:]/g, '-');