This commit is contained in:
1 parent
0f6df281c8
commit
a986d74ef9
2 files changed
+267
-53
No files matched your search
@@ -12,6 +12,7 @@ export interface PostPayload {
|
|||||||
slug: string;
|
slug: string;
|
||||||
content: string;
|
content: string;
|
||||||
category_slug?: string;
|
category_slug?: string;
|
||||||
|
tags?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PostMeta {
|
export interface PostMeta {
|
||||||
@@ -21,6 +22,7 @@ export interface PostMeta {
|
|||||||
published_at: string;
|
published_at: string;
|
||||||
category_slug?: string;
|
category_slug?: string;
|
||||||
category_name?: string;
|
category_name?: string;
|
||||||
|
tags?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PostDetail extends PostMeta {
|
export interface PostDetail extends PostMeta {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Notice, Plugin, MarkdownView } from 'obsidian';
|
import { Notice, Plugin, MarkdownView, Menu, TFile } from 'obsidian';
|
||||||
import {
|
import {
|
||||||
BlogFlareSettings,
|
BlogFlareSettings,
|
||||||
DEFAULT_SETTINGS,
|
DEFAULT_SETTINGS,
|
||||||
@@ -97,64 +97,29 @@ export default class BlogFlarePlugin extends Plugin {
|
|||||||
|
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: 'publish-post',
|
id: 'publish-post',
|
||||||
name: '发布文章',
|
name: '上传当前文章',
|
||||||
checkCallback: (checking: boolean) => {
|
checkCallback: (checking: boolean) => {
|
||||||
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||||
const file = activeView?.file;
|
const file = activeView?.file;
|
||||||
if (activeView && file) {
|
if (activeView && file) {
|
||||||
if (!checking) {
|
if (!checking) {
|
||||||
void (async () => {
|
void this.publishCurrentFile();
|
||||||
try {
|
}
|
||||||
new Notice('开始发布文章...');
|
return true;
|
||||||
const rawContent = await this.app.vault.read(file);
|
}
|
||||||
const cache = this.app.metadataCache.getFileCache(file);
|
return false;
|
||||||
const frontmatter = cache?.frontmatter;
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let content = rawContent;
|
this.addCommand({
|
||||||
const frontmatterPosition = cache?.frontmatterPosition;
|
id: 'pull-current-post',
|
||||||
// 移除前置 YAML
|
name: '下载更新当前文章',
|
||||||
if (frontmatterPosition) {
|
checkCallback: (checking: boolean) => {
|
||||||
content = rawContent.substring(frontmatterPosition.end.offset).replace(/^\n+/, '');
|
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||||
}
|
const file = activeView?.file;
|
||||||
|
if (activeView && file) {
|
||||||
const title = frontmatter?.title as string || file.basename;
|
if (!checking) {
|
||||||
const slug = frontmatter?.slug as string || file.basename.toLowerCase().replace(/\s+/g, '-');
|
void this.pullCurrentFile();
|
||||||
const category_slug = frontmatter?.category_slug as string | undefined;
|
|
||||||
const publishedSlug = frontmatter?.blogflare_published_slug as string | undefined;
|
|
||||||
|
|
||||||
const payload = {
|
|
||||||
title,
|
|
||||||
slug,
|
|
||||||
content,
|
|
||||||
category_slug
|
|
||||||
};
|
|
||||||
|
|
||||||
if (publishedSlug) {
|
|
||||||
// 已发布,进行更新
|
|
||||||
const res = await this.api.updatePost(publishedSlug, payload);
|
|
||||||
if (res.success) {
|
|
||||||
new Notice('文章更新成功!');
|
|
||||||
} else {
|
|
||||||
new Notice(`更新失败: ${res.error || '未知错误'}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 未发布,进行创建
|
|
||||||
const res = await this.api.createPost(payload);
|
|
||||||
if (res.success && res.slug) {
|
|
||||||
// 写入 blogflare_published_slug
|
|
||||||
await this.app.fileManager.processFrontMatter(file, (fm: Record<string, unknown>) => {
|
|
||||||
fm['blogflare_published_slug'] = res.slug;
|
|
||||||
});
|
|
||||||
new Notice('文章发布成功!');
|
|
||||||
} else {
|
|
||||||
new Notice(`发布失败: ${res.error || '未知错误'}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
new Notice('发布请求出错,请检查网络或配置。');
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -285,6 +250,253 @@ export default class BlogFlarePlugin extends Plugin {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 统一的右下角状态栏入口
|
||||||
|
const statusBarItem = this.addStatusBarItem();
|
||||||
|
statusBarItem.setText('BlogFlare 同步');
|
||||||
|
statusBarItem.addClass('mod-clickable');
|
||||||
|
statusBarItem.addEventListener('click', (evt: MouseEvent) => {
|
||||||
|
this.showSyncMenu(evt);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 增加左侧 Ribbon 图标
|
||||||
|
this.addRibbonIcon('cloud-download', 'BlogFlare 同步选项', (evt: MouseEvent) => {
|
||||||
|
this.showSyncMenu(evt);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
showSyncMenu(evt: MouseEvent) {
|
||||||
|
const menu = new Menu();
|
||||||
|
|
||||||
|
menu.addItem((item) =>
|
||||||
|
item
|
||||||
|
.setTitle('上传当前文章')
|
||||||
|
.setIcon('upload-cloud')
|
||||||
|
.onClick(() => {
|
||||||
|
void this.publishCurrentFile();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
menu.addItem((item) =>
|
||||||
|
item
|
||||||
|
.setTitle('下载更新当前文章')
|
||||||
|
.setIcon('download-cloud')
|
||||||
|
.onClick(() => {
|
||||||
|
void this.pullCurrentFile();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
menu.addSeparator();
|
||||||
|
|
||||||
|
menu.addItem((item) =>
|
||||||
|
item
|
||||||
|
.setTitle('下载更新全部文章')
|
||||||
|
.setIcon('refresh-cw')
|
||||||
|
.onClick(() => {
|
||||||
|
void this.pullAllPosts();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
menu.showAtMouseEvent(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
async pullAllPosts() {
|
||||||
|
try {
|
||||||
|
new Notice('开始拉取全量文章列表...');
|
||||||
|
const items = await this.api.getPosts();
|
||||||
|
if (items.length === 0) {
|
||||||
|
new Notice('未找到文章,同步结束。');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
new Notice(`发现 ${items.length} 篇文章,开始同步...`);
|
||||||
|
let successCount = 0;
|
||||||
|
|
||||||
|
// 收集本地已有的 blogflare_published_slug,用于匹配是新建还是覆盖
|
||||||
|
const mdFiles = this.app.vault.getMarkdownFiles();
|
||||||
|
const slugMap = new Map<string, TFile>();
|
||||||
|
for (const f of mdFiles) {
|
||||||
|
const cache = this.app.metadataCache.getFileCache(f);
|
||||||
|
const pubSlug = cache?.frontmatter?.blogflare_published_slug as string | undefined;
|
||||||
|
if (pubSlug) {
|
||||||
|
slugMap.set(pubSlug, f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.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 existingFile = slugMap.get(item.slug);
|
||||||
|
if (existingFile) {
|
||||||
|
// 已存在,执行覆写
|
||||||
|
await this.app.vault.modify(existingFile, fileContent);
|
||||||
|
} else {
|
||||||
|
// 不存在,执行新建
|
||||||
|
const baseName = detail.title.replace(/[/\\:]/g, '-');
|
||||||
|
let dirPath = '';
|
||||||
|
const categoryDir = detail.category_name || detail.category_slug;
|
||||||
|
if (categoryDir) {
|
||||||
|
dirPath = categoryDir.replace(/[/\\:]/g, '-');
|
||||||
|
const folder = this.app.vault.getAbstractFileByPath(dirPath);
|
||||||
|
if (!folder) {
|
||||||
|
await this.app.vault.createFolder(dirPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 确保文件名唯一
|
||||||
|
let fileName = dirPath ? `${dirPath}/${baseName}.md` : `${baseName}.md`;
|
||||||
|
let counter = 1;
|
||||||
|
while (!existingFile && this.app.vault.getAbstractFileByPath(fileName)) {
|
||||||
|
fileName = dirPath ? `${dirPath}/${baseName}-${counter}.md` : `${baseName}-${counter}.md`;
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
await this.app.vault.create(fileName, fileContent);
|
||||||
|
}
|
||||||
|
successCount++;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`拉取文章 ${item.slug} 失败:`, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new Notice(`全量同步完成!成功拉取/更新 ${successCount}/${items.length} 篇文章。`, 5000);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
new Notice('全量同步请求出错,请检查网络或配置。');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async publishCurrentFile() {
|
||||||
|
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||||
|
const file = activeView?.file;
|
||||||
|
if (!file) {
|
||||||
|
new Notice('没有打开的 Markdown 文件');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
new Notice('开始发布文章...');
|
||||||
|
const rawContent = await this.app.vault.read(file);
|
||||||
|
const cache = this.app.metadataCache.getFileCache(file);
|
||||||
|
const frontmatter = cache?.frontmatter;
|
||||||
|
|
||||||
|
let content = rawContent;
|
||||||
|
const frontmatterPosition = cache?.frontmatterPosition;
|
||||||
|
// 移除前置 YAML
|
||||||
|
if (frontmatterPosition) {
|
||||||
|
content = rawContent.substring(frontmatterPosition.end.offset).replace(/^\n+/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
const title = frontmatter?.title as string || file.basename;
|
||||||
|
const slug = frontmatter?.slug as string || file.basename.toLowerCase().replace(/\s+/g, '-');
|
||||||
|
const category_slug = frontmatter?.category_slug as string | undefined;
|
||||||
|
const publishedSlug = frontmatter?.blogflare_published_slug as string | undefined;
|
||||||
|
|
||||||
|
let tags: string[] | undefined = undefined;
|
||||||
|
if (frontmatter?.tags) {
|
||||||
|
tags = Array.isArray(frontmatter.tags) ? frontmatter.tags : [frontmatter.tags as string];
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
title,
|
||||||
|
slug,
|
||||||
|
content,
|
||||||
|
category_slug,
|
||||||
|
tags
|
||||||
|
};
|
||||||
|
|
||||||
|
if (publishedSlug) {
|
||||||
|
// 已发布,进行更新
|
||||||
|
const res = await this.api.updatePost(publishedSlug, payload);
|
||||||
|
if (res.success) {
|
||||||
|
new Notice('文章更新成功!');
|
||||||
|
} else {
|
||||||
|
new Notice(`更新失败: ${res.error || '未知错误'}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 未发布,进行创建
|
||||||
|
const res = await this.api.createPost(payload);
|
||||||
|
if (res.success && res.slug) {
|
||||||
|
// 写入 blogflare_published_slug
|
||||||
|
await this.app.fileManager.processFrontMatter(file, (fm: Record<string, unknown>) => {
|
||||||
|
fm['blogflare_published_slug'] = res.slug;
|
||||||
|
});
|
||||||
|
new Notice('文章发布成功!');
|
||||||
|
} else {
|
||||||
|
new Notice(`发布失败: ${res.error || '未知错误'}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
new Notice('发布请求出错,请检查网络或配置。');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async pullCurrentFile() {
|
||||||
|
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||||
|
const file = activeView?.file;
|
||||||
|
if (!file) {
|
||||||
|
new Notice('没有打开的 Markdown 文件');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cache = this.app.metadataCache.getFileCache(file);
|
||||||
|
const publishedSlug = cache?.frontmatter?.blogflare_published_slug as string | undefined;
|
||||||
|
|
||||||
|
if (!publishedSlug) {
|
||||||
|
new Notice('当前文件尚未发布到 blogflare,无法下载更新。如果想拉取其他文章,请使用"拉取文章"命令。');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.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 || '');
|
||||||
|
|
||||||
|
await this.app.vault.modify(file, fileContent);
|
||||||
|
new Notice('文章下载并更新成功!');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('下载文章失败:', err);
|
||||||
|
new Notice('下载请求出错,请检查网络或配置。');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onunload() {}
|
onunload() {}
|
||||||
|
|||||||
Reference in new issue
Block a user