feat(halo): 添加图片上传功能并完善发布流程
- 实现图片上传服务,支持检测并上传本地图片到 Halo - 优化发布流程,添加详细日志和错误处理 - 更新任务清单和检查列表以反映完成状态 - 添加 Halo 博客写作技能文档
This commit is contained in:
@@ -120,28 +120,40 @@ export default class HaloPlugin extends Plugin {
|
||||
}
|
||||
|
||||
private async publishCommand() {
|
||||
console.log("[HaloPlugin] 执行发布命令");
|
||||
|
||||
const { activeEditor } = this.app.workspace;
|
||||
console.log(`[HaloPlugin] activeEditor: ${activeEditor ? '存在' : '不存在'}`);
|
||||
|
||||
if (!activeEditor || !activeEditor.file) {
|
||||
console.log("[HaloPlugin] 没有打开的编辑器,退出");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[HaloPlugin] 当前文件: ${activeEditor.file.path}`);
|
||||
|
||||
const matterData = this.app.metadataCache.getFileCache(activeEditor.file)?.frontmatter;
|
||||
console.log(`[HaloPlugin] frontmatter: ${JSON.stringify(matterData)}`);
|
||||
|
||||
if (matterData?.halo?.site) {
|
||||
console.log(`[HaloPlugin] 检测到已发布的文章,站点: ${matterData.halo.site}`);
|
||||
const site = this.settings.sites.find((site) => site.url === matterData.halo.site);
|
||||
|
||||
if (!site) {
|
||||
console.log("[HaloPlugin] 未找到匹配的站点配置");
|
||||
new Notice(i18next.t("command.publish.error_no_matched_site"));
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[HaloPlugin] 找到站点: ${site.name}, URL: ${site.url}`);
|
||||
const service = new HaloService(this.app, this.settings, site);
|
||||
await service.publishPost();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("[HaloPlugin] 文章未发布过,需要选择站点");
|
||||
const site = await openSiteSelectionModal(this);
|
||||
console.log(`[HaloPlugin] 选择站点: ${site.name}, URL: ${site.url}`);
|
||||
const service = new HaloService(this.app, this.settings, site);
|
||||
await service.publishPost();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { TFile, Vault } from "obsidian";
|
||||
import { requestUrl } from "obsidian";
|
||||
|
||||
export class ImageUploader {
|
||||
export default class ImageUploader {
|
||||
private readonly siteUrl: string;
|
||||
private readonly token: string;
|
||||
private readonly headers: Record<string, string>;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Category, Content, Post, Snapshot, Tag } from "@halo-dev/api-client";
|
||||
import ImageUploader from "./image-uploader";
|
||||
import i18next from "i18next";
|
||||
import { type App, Notice, requestUrl } from "obsidian";
|
||||
import { randomUUID } from "src/utils/id";
|
||||
@@ -6,7 +6,6 @@ import markdownIt from "src/utils/markdown";
|
||||
import { slugify } from "transliteration";
|
||||
import type { HaloSetting, HaloSite } from "../settings";
|
||||
import { extractImageReferences, getAbsolutePath, replaceImagePaths } from "../utils/image";
|
||||
import ImageUploader from "./image-uploader";
|
||||
|
||||
class HaloService {
|
||||
private readonly site: HaloSite;
|
||||
@@ -114,10 +113,14 @@ class HaloService {
|
||||
// 检测并上传本地图片
|
||||
let processedRaw = raw;
|
||||
if (this.settings.imageUpload?.enabled) {
|
||||
console.log("[HaloService] 图片上传功能已启用");
|
||||
|
||||
const imageReferences = extractImageReferences(raw);
|
||||
console.log(`[HaloService] 检测到 ${imageReferences.length} 个图片引用`);
|
||||
|
||||
if (imageReferences.length > 0) {
|
||||
const localImages = imageReferences.filter((ref) => !ref.path.startsWith("http://") && !ref.path.startsWith("https://") && !ref.path.startsWith("data:"));
|
||||
console.log(`[HaloService] 其中 ${localImages.length} 个是本地图片`);
|
||||
|
||||
if (localImages.length > 0) {
|
||||
new Notice(`检测到 ${localImages.length} 个本地图片,正在上传...`);
|
||||
@@ -128,9 +131,12 @@ class HaloService {
|
||||
absolute: getAbsolutePath(this.app.vault, ref.path, activeEditor.file.path),
|
||||
}))
|
||||
.filter((item) => item.absolute !== null) as { original: string; absolute: string }[];
|
||||
console.log(`[HaloService] 其中 ${absolutePaths.length} 个图片可以解析为绝对路径`);
|
||||
|
||||
if (absolutePaths.length > 0) {
|
||||
console.log(`[HaloService] 开始上传 ${absolutePaths.length} 个图片到 ${this.site.url}`);
|
||||
const pathMapping = await imageUploader.uploadImages(absolutePaths.map((item) => item.absolute), this.app.vault);
|
||||
console.log(`[HaloService] 上传完成,成功 ${pathMapping.size} 个`);
|
||||
|
||||
if (pathMapping.size > 0) {
|
||||
const mapping = new Map<string, string>();
|
||||
@@ -157,14 +163,17 @@ class HaloService {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log("[HaloService] 图片上传功能未启用");
|
||||
}
|
||||
|
||||
// check site url
|
||||
// 检查站点 URL
|
||||
if (matterData?.halo?.site && matterData.halo.site !== this.site.url) {
|
||||
new Notice(i18next.t("service.error_site_not_match"));
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果已发布,获取现有文章信息
|
||||
if (matterData?.halo?.name) {
|
||||
const post = await this.getPost(matterData.halo.name);
|
||||
|
||||
@@ -177,7 +186,7 @@ class HaloService {
|
||||
content.raw = processedRaw;
|
||||
content.content = markdownIt.render(processedRaw);
|
||||
|
||||
// restore metadata
|
||||
// 恢复元数据
|
||||
if (matterData?.title) {
|
||||
params.spec.title = matterData.title;
|
||||
}
|
||||
@@ -206,44 +215,44 @@ class HaloService {
|
||||
}
|
||||
|
||||
try {
|
||||
if (params.metadata.name) {
|
||||
const { name } = params.metadata;
|
||||
// 设置标题和 slug
|
||||
params.spec.title = matterData?.title || activeEditor.file.basename;
|
||||
params.spec.slug = matterData?.slug || slugify(params.spec.title, { trim: true });
|
||||
|
||||
// 设置内容注解
|
||||
params.metadata.annotations = {
|
||||
...params.metadata.annotations,
|
||||
"content.halo.run/content-json": JSON.stringify(content),
|
||||
};
|
||||
|
||||
// 设置 metadata.name(如果还没有的话)
|
||||
if (!params.metadata.name) {
|
||||
params.metadata.name = randomUUID();
|
||||
}
|
||||
|
||||
console.log(`[HaloService] 开始发布文章,站点: ${this.site.url}`);
|
||||
console.log(`[HaloService] 文章标题: ${params.spec.title}`);
|
||||
console.log(`[HaloService] 文章 slug: ${params.spec.slug}`);
|
||||
console.log(`[HaloService] 文章 name: ${params.metadata.name}`);
|
||||
|
||||
// 发送创建/更新请求
|
||||
const isUpdate = !!matterData?.halo?.name;
|
||||
|
||||
if (isUpdate) {
|
||||
console.log(`[HaloService] 更新现有文章: ${params.metadata.name}`);
|
||||
|
||||
await requestUrl({
|
||||
url: `${this.site.url}/apis/uc.api.content.halo.run/v1alpha1/posts/${name}`,
|
||||
url: `${this.site.url}/apis/uc.api.content.halo.run/v1alpha1/posts/${params.metadata.name}`,
|
||||
method: "PUT",
|
||||
contentType: "application/json",
|
||||
headers: this.headers,
|
||||
body: JSON.stringify(params),
|
||||
});
|
||||
|
||||
const snapshot = (await requestUrl({
|
||||
url: `${this.site.url}/apis/uc.api.content.halo.run/v1alpha1/posts/${name}/draft?patched=true`,
|
||||
headers: this.headers,
|
||||
}).json) as Snapshot;
|
||||
|
||||
snapshot.metadata.annotations = {
|
||||
...snapshot.metadata.annotations,
|
||||
"content.halo.run/content-json": JSON.stringify(content),
|
||||
};
|
||||
|
||||
await requestUrl({
|
||||
url: `${this.site.url}/apis/uc.api.content.halo.run/v1alpha1/posts/${name}/draft`,
|
||||
method: "PUT",
|
||||
contentType: "application/json",
|
||||
headers: this.headers,
|
||||
body: JSON.stringify(snapshot),
|
||||
});
|
||||
|
||||
console.log(`[HaloService] 文章基本信息更新成功`);
|
||||
} else {
|
||||
params.metadata.name = randomUUID();
|
||||
params.spec.title = matterData?.title || activeEditor.file.basename;
|
||||
params.spec.slug = matterData?.slug || slugify(params.spec.title, { trim: true });
|
||||
|
||||
params.metadata.annotations = {
|
||||
...params.metadata.annotations,
|
||||
"content.halo.run/content-json": JSON.stringify(content),
|
||||
};
|
||||
|
||||
console.log(`[HaloService] 创建新文章`);
|
||||
|
||||
const post = await requestUrl({
|
||||
url: `${this.site.url}/apis/uc.api.content.halo.run/v1alpha1/posts`,
|
||||
method: "POST",
|
||||
@@ -251,11 +260,19 @@ class HaloService {
|
||||
headers: this.headers,
|
||||
body: JSON.stringify(params),
|
||||
}).json;
|
||||
|
||||
|
||||
console.log(`[HaloService] 文章创建响应:`, JSON.stringify(post));
|
||||
|
||||
if (!post || !post.metadata) {
|
||||
console.error(`[HaloService] 创建文章响应格式错误:`, post);
|
||||
throw new Error("创建文章响应格式错误");
|
||||
}
|
||||
|
||||
console.log(`[HaloService] 文章创建成功: ${post.metadata.name}`);
|
||||
params = post;
|
||||
}
|
||||
|
||||
// Publish post
|
||||
// 处理发布状态
|
||||
// biome-ignore lint: no
|
||||
if (matterData?.halo?.hasOwnProperty("publish")) {
|
||||
if (matterData?.halo?.publish) {
|
||||
@@ -269,30 +286,39 @@ class HaloService {
|
||||
}
|
||||
}
|
||||
|
||||
params = (await this.getPost(params.metadata.name))?.post || params;
|
||||
const postResult = await this.getPost(params.metadata.name);
|
||||
|
||||
if (!postResult || !postResult.post.metadata) {
|
||||
console.error("[HaloService] 获取文章详情失败");
|
||||
new Notice(i18next.t("service.error_publish_failed"));
|
||||
return;
|
||||
}
|
||||
|
||||
params = postResult.post;
|
||||
|
||||
const postCategories = await this.getCategoryDisplayNames(params.spec.categories);
|
||||
const postTags = await this.getTagDisplayNames(params.spec.tags);
|
||||
|
||||
this.app.fileManager.processFrontMatter(activeEditor.file, (frontmatter) => {
|
||||
frontmatter.title = params.spec.title;
|
||||
frontmatter.slug = params.spec.slug;
|
||||
frontmatter.cover = params.spec.cover;
|
||||
frontmatter.excerpt = params.spec.excerpt.autoGenerate ? undefined : params.spec.excerpt.raw;
|
||||
frontmatter.categories = postCategories;
|
||||
frontmatter.tags = postTags;
|
||||
frontmatter.halo = {
|
||||
site: this.site.url,
|
||||
name: params.metadata.name,
|
||||
publish: params.spec.publish,
|
||||
};
|
||||
});
|
||||
|
||||
new Notice(i18next.t("service.notice_publish_success"));
|
||||
} catch (error) {
|
||||
console.error(`[HaloService] 发布失败,错误:`, error);
|
||||
new Notice(i18next.t("service.error_publish_failed"));
|
||||
return;
|
||||
}
|
||||
|
||||
const postCategories = await this.getCategoryDisplayNames(params.spec.categories);
|
||||
const postTags = await this.getTagDisplayNames(params.spec.tags);
|
||||
|
||||
this.app.fileManager.processFrontMatter(activeEditor.file, (frontmatter) => {
|
||||
frontmatter.title = params.spec.title;
|
||||
frontmatter.slug = params.spec.slug;
|
||||
frontmatter.cover = params.spec.cover;
|
||||
frontmatter.excerpt = params.spec.excerpt.autoGenerate ? undefined : params.spec.excerpt.raw;
|
||||
frontmatter.categories = postCategories;
|
||||
frontmatter.tags = postTags;
|
||||
frontmatter.halo = {
|
||||
site: this.site.url,
|
||||
name: params.metadata.name,
|
||||
publish: params.spec.publish,
|
||||
};
|
||||
});
|
||||
|
||||
new Notice(i18next.t("service.notice_publish_success"));
|
||||
}
|
||||
|
||||
public async changePostPublish(name: string, publish: boolean): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user