feat(sync): 添加同步状态面板和历史功能
- 创建同步状态面板视图,显示已发布文章列表和快速操作按钮 - 添加同步历史弹窗,记录和展示同步操作记录 - 在侧边栏添加同步图标,支持快速打开面板 - 更新国际化文件,添加中英文同步相关文案 - 编写详细的使用指南文档,说明所有功能使用方法 - 更新插件主程序,注册新命令和视图
This commit is contained in:
@@ -0,0 +1,240 @@
|
||||
import i18next from "i18next";
|
||||
import { ItemView, Notice, WorkspaceLeaf, TFile } from "obsidian";
|
||||
import type HaloPlugin from "../main";
|
||||
|
||||
export const SYNC_STATUS_VIEW_TYPE = "halo-sync-status";
|
||||
|
||||
interface SyncHistoryItem {
|
||||
id: string;
|
||||
action: "publish" | "update" | "pull" | "delete";
|
||||
title: string;
|
||||
timestamp: number;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
export class SyncStatusView extends ItemView {
|
||||
private plugin: HaloPlugin;
|
||||
private history: SyncHistoryItem[] = [];
|
||||
|
||||
constructor(leaf: WorkspaceLeaf, plugin: HaloPlugin) {
|
||||
super(leaf);
|
||||
this.plugin = plugin;
|
||||
this.loadHistory();
|
||||
}
|
||||
|
||||
getViewType(): string {
|
||||
return SYNC_STATUS_VIEW_TYPE;
|
||||
}
|
||||
|
||||
getDisplayText(): string {
|
||||
return i18next.t("sync_panel.title");
|
||||
}
|
||||
|
||||
async onOpen() {
|
||||
this.render();
|
||||
}
|
||||
|
||||
private loadHistory() {
|
||||
const stored = this.plugin.loadData();
|
||||
this.history = stored?.syncHistory || [];
|
||||
}
|
||||
|
||||
private saveHistory() {
|
||||
const data = this.plugin.loadData() || {};
|
||||
data.syncHistory = this.history.slice(-50);
|
||||
this.plugin.saveData(data);
|
||||
}
|
||||
|
||||
addToHistory(action: "publish" | "update" | "pull" | "delete", title: string, success: boolean) {
|
||||
this.history.push({
|
||||
id: Date.now().toString(),
|
||||
action,
|
||||
title,
|
||||
timestamp: Date.now(),
|
||||
success,
|
||||
});
|
||||
this.saveHistory();
|
||||
}
|
||||
|
||||
private getPublishedPosts() {
|
||||
const publishedPosts: Array<{
|
||||
file: TFile;
|
||||
title: string;
|
||||
slug: string;
|
||||
haloName: string;
|
||||
haloSite: string;
|
||||
publishStatus: boolean;
|
||||
}> = [];
|
||||
|
||||
for (const file of this.plugin.app.vault.getFiles()) {
|
||||
if (file.extension !== "md") continue;
|
||||
|
||||
const cache = this.plugin.app.metadataCache.getFileCache(file);
|
||||
if (!cache?.frontmatter?.halo?.name) continue;
|
||||
|
||||
publishedPosts.push({
|
||||
file,
|
||||
title: cache.frontmatter.title || file.basename,
|
||||
slug: cache.frontmatter.slug || "",
|
||||
haloName: cache.frontmatter.halo.name,
|
||||
haloSite: cache.frontmatter.halo.site || "",
|
||||
publishStatus: cache.frontmatter.halo.publish ?? false,
|
||||
});
|
||||
}
|
||||
|
||||
return publishedPosts;
|
||||
}
|
||||
|
||||
private async render() {
|
||||
const container = this.containerEl;
|
||||
container.empty();
|
||||
|
||||
const header = container.createDiv("sync-header");
|
||||
header.createEl("h2", { text: i18next.t("sync_panel.title") });
|
||||
|
||||
const actions = header.createDiv("sync-actions");
|
||||
actions.createEl("button", {
|
||||
text: i18next.t("sync_panel.button_refresh"),
|
||||
cls: "sync-action-btn"
|
||||
}).addEventListener("click", () => this.render());
|
||||
|
||||
actions.createEl("button", {
|
||||
text: i18next.t("sync_panel.button_history"),
|
||||
cls: "sync-action-btn"
|
||||
}).addEventListener("click", () => this.showHistory());
|
||||
|
||||
actions.createEl("button", {
|
||||
text: i18next.t("sync_panel.button_clear_history"),
|
||||
cls: "sync-action-btn danger"
|
||||
}).addEventListener("click", () => this.clearHistory());
|
||||
|
||||
const posts = this.getPublishedPosts();
|
||||
|
||||
if (posts.length === 0) {
|
||||
container.createEl("p", {
|
||||
text: i18next.t("sync_panel.empty"),
|
||||
cls: "sync-empty"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const list = container.createDiv("sync-list");
|
||||
|
||||
for (const post of posts) {
|
||||
const item = list.createDiv("sync-item");
|
||||
|
||||
const statusIcon = item.createSpan({
|
||||
text: post.publishStatus ? "✅" : "📝",
|
||||
cls: "sync-status-icon"
|
||||
});
|
||||
|
||||
const info = item.createDiv("sync-info");
|
||||
info.createEl("span", { text: post.title, cls: "sync-title" });
|
||||
info.createEl("span", { text: `Slug: ${post.slug}`, cls: "sync-slug" });
|
||||
|
||||
const itemActions = item.createDiv("sync-item-actions");
|
||||
|
||||
itemActions.createEl("button", {
|
||||
text: i18next.t("sync_panel.button_update"),
|
||||
cls: "sync-item-btn"
|
||||
}).addEventListener("click", async () => {
|
||||
const site = this.plugin.settings.sites.find(s => s.url === post.haloSite);
|
||||
if (!site) {
|
||||
new Notice(i18next.t("service.error_site_not_match"));
|
||||
return;
|
||||
}
|
||||
|
||||
const { default: HaloService } = await import("../service");
|
||||
const service = new HaloService(this.plugin.app, this.plugin.settings, site);
|
||||
await service.updatePost();
|
||||
this.addToHistory("update", post.title, true);
|
||||
new Notice(i18next.t("command.update_post.success"));
|
||||
this.render();
|
||||
});
|
||||
|
||||
itemActions.createEl("button", {
|
||||
text: i18next.t("sync_panel.button_pull"),
|
||||
cls: "sync-item-btn"
|
||||
}).addEventListener("click", async () => {
|
||||
const site = this.plugin.settings.sites.find(s => s.url === post.haloSite);
|
||||
if (!site) {
|
||||
new Notice(i18next.t("service.error_site_not_match"));
|
||||
return;
|
||||
}
|
||||
|
||||
const { default: HaloService } = await import("../service");
|
||||
const service = new HaloService(this.plugin.app, this.plugin.settings, site);
|
||||
await service.pullPost(post.haloName);
|
||||
this.addToHistory("pull", post.title, true);
|
||||
this.render();
|
||||
});
|
||||
}
|
||||
|
||||
const stats = container.createDiv("sync-stats");
|
||||
stats.createEl("span", { text: i18next.t("sync_panel.total_posts", { count: posts.length }) });
|
||||
}
|
||||
|
||||
private showHistory() {
|
||||
const modal = document.createElement("div");
|
||||
modal.className = "sync-history-modal";
|
||||
modal.style.cssText = `
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0,0,0,0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
`;
|
||||
|
||||
const content = modal.createDiv("sync-history-content");
|
||||
content.style.cssText = `
|
||||
background: var(--background-primary);
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
max-width: 500px;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
`;
|
||||
|
||||
content.createEl("h3", { text: i18next.t("sync_history.title") });
|
||||
|
||||
const closeBtn = content.createEl("button", { text: i18next.t("common.button_close") });
|
||||
closeBtn.style.cssText = "margin-bottom: 15px;";
|
||||
closeBtn.addEventListener("click", () => modal.remove());
|
||||
|
||||
if (this.history.length === 0) {
|
||||
content.createEl("p", { text: i18next.t("sync_history.empty") });
|
||||
} else {
|
||||
const list = content.createDiv("sync-history-list");
|
||||
|
||||
for (const item of [...this.history].reverse()) {
|
||||
const historyItem = list.createDiv("history-item");
|
||||
const time = new Date(item.timestamp).toLocaleString();
|
||||
const actionText = i18next.t(`sync_history.action_${item.action}`);
|
||||
const icon = item.success ? "✅" : "❌";
|
||||
|
||||
historyItem.createEl("span", { text: `${icon} ${actionText}: ${item.title}` });
|
||||
historyItem.createEl("span", { text: time, cls: "history-time" });
|
||||
}
|
||||
}
|
||||
|
||||
modal.addEventListener("click", (e) => {
|
||||
if (e.target === modal) modal.remove();
|
||||
});
|
||||
|
||||
document.body.appendChild(modal);
|
||||
}
|
||||
|
||||
private clearHistory() {
|
||||
if (confirm(i18next.t("sync_history.confirm_clear"))) {
|
||||
this.history = [];
|
||||
this.saveHistory();
|
||||
this.render();
|
||||
new Notice(i18next.t("sync_history.notice_cleared"));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user