feat(sync): 添加同步状态面板和历史功能
- 创建同步状态面板视图,显示已发布文章列表和快速操作按钮 - 添加同步历史弹窗,记录和展示同步操作记录 - 在侧边栏添加同步图标,支持快速打开面板 - 更新国际化文件,添加中英文同步相关文案 - 编写详细的使用指南文档,说明所有功能使用方法 - 更新插件主程序,注册新命令和视图
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
# Obsidian Halo 插件使用指南
|
||||
|
||||
> 完整的 Halo 博客同步插件使用手册
|
||||
|
||||
## 📦 安装和配置
|
||||
|
||||
### 1. 安装插件
|
||||
|
||||
1. 在 Obsidian 设置中进入「社区插件」
|
||||
2. 搜索「Halo」或「obsidian-halo」
|
||||
3. 安装并启用插件
|
||||
|
||||
### 2. 配置站点
|
||||
|
||||
1. 进入插件设置(设置 → 插件选项 → Halo)
|
||||
2. 点击「添加站点」
|
||||
3. 填写以下信息:
|
||||
- **站点名称**:给你的站点起个名字
|
||||
- **站点地址**:你的 Halo 博客地址,如 `https://blog.example.com`
|
||||
- **个人令牌**:在 Halo 后台 → 个人资料 → 个人令牌 中创建
|
||||
|
||||
4. 点击「验证」确保配置正确
|
||||
5. 可以设置一个「默认站点」
|
||||
|
||||
---
|
||||
|
||||
## 🎛️ 图标说明
|
||||
|
||||
插件在左侧 ribbon 栏添加了两个图标:
|
||||
|
||||
| 图标 | 位置 | 功能 |
|
||||
|------|------|------|
|
||||
| 🔵 Halo Logo | Ribbon 栏 | 快速发布当前文档到 Halo |
|
||||
| 🔄 同步图标 | Ribbon 栏 | 打开同步状态面板 |
|
||||
|
||||
---
|
||||
|
||||
## 📋 所有命令列表
|
||||
|
||||
在 Obsidian 中按 `Ctrl+P`(或 `Cmd+P`)打开命令面板,输入「Halo」查找所有命令:
|
||||
|
||||
### 发布和同步
|
||||
|
||||
| 命令 | 说明 | 使用场景 |
|
||||
|------|------|----------|
|
||||
| `Halo: 发布到 Halo` | 发布当前文档到 Halo | 编辑完文章后发布 |
|
||||
| `Halo: 发布到 Halo(使用默认配置)` | 使用默认站点发布 | 快速发布,不选择站点 |
|
||||
| `Halo: 从 Halo 更新内容` | 从 Halo 同步内容到本地 | Halo 端有更新时同步 |
|
||||
| `Halo: 从 Halo 拉取文档` | 打开文章列表选择拉取 | 想要从 Halo 拉取已存在的文章 |
|
||||
|
||||
### 文章管理
|
||||
|
||||
| 命令 | 说明 | 使用场景 |
|
||||
|------|------|----------|
|
||||
| `Halo: 从 Markdown 文件导入` | 从 Vault 中选择文件导入 Halo | 导入已存在的文件 |
|
||||
| `Halo: 删除 Halo 文章` | 删除已发布的文章(需在编辑器中执行)| 删除文章时使用 |
|
||||
| `Halo: 搜索 Halo 文章` | 搜索 Halo 上的文章 | 快速查找文章 |
|
||||
| `Halo: 打开同步状态面板` | 打开同步状态面板 | 查看所有已发布文章的状态 |
|
||||
|
||||
### 导出功能
|
||||
|
||||
| 命令 | 说明 | 使用场景 |
|
||||
|------|------|----------|
|
||||
| `Halo: 导出为 Markdown` | 导出当前文章为 .md 文件 | 备份或迁移内容 |
|
||||
| `Halo: 导出为 JSON` | 导出当前文章为 .json 文件 | 备份元数据和内容 |
|
||||
|
||||
### 分类和标签管理
|
||||
|
||||
| 命令 | 说明 | 使用场景 |
|
||||
|------|------|----------|
|
||||
| `Halo: 管理标签` | 打开标签管理弹窗 | 创建、编辑、删除标签 |
|
||||
| `Halo: 管理分类` | 打开分类管理弹窗 | 创建、编辑、删除分类 |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 首次发布文章
|
||||
|
||||
1. 在 Obsidian 中创建或打开一个 Markdown 文件
|
||||
2. 添加 frontmatter 元数据(可选):
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: 我的第一篇文章
|
||||
slug: my-first-post
|
||||
tags:
|
||||
- 教程
|
||||
- Obsidian
|
||||
categories:
|
||||
- 笔记方法
|
||||
---
|
||||
|
||||
文章内容...
|
||||
```
|
||||
|
||||
3. 点击左侧的 **Halo Logo** 图标,或按 `Ctrl+P` 输入「发布到 Halo」
|
||||
4. 如果有多个站点,选择目标站点
|
||||
5. 等待发布成功提示
|
||||
|
||||
### 从 Halo 拉取文章
|
||||
|
||||
1. 按 `Ctrl+P` 输入「从 Halo 拉取文档」
|
||||
2. 选择站点
|
||||
3. 在列表中找到要拉取的文章
|
||||
4. 点击「拉取」按钮
|
||||
5. 文章将自动创建到 Vault 中
|
||||
|
||||
### 查看同步状态
|
||||
|
||||
1. 点击左侧的 **同步图标**(或按 `Ctrl+P` 输入「同步状态」)
|
||||
2. 右侧面板将显示所有已发布文章
|
||||
3. 可以执行快速操作:更新、拉取
|
||||
|
||||
---
|
||||
|
||||
## 📝 Frontmatter 说明
|
||||
|
||||
发布文章时,可以设置以下 frontmatter:
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: 文章标题(必填,用于显示标题)
|
||||
slug: article-slug(可选,用于 URL)
|
||||
excerpt: 文章摘要(可选)
|
||||
cover: https://example.com/cover.jpg(可选,封面图)
|
||||
tags:
|
||||
- 标签1
|
||||
- 标签2
|
||||
categories:
|
||||
- 分类1
|
||||
- 分类2
|
||||
halo:
|
||||
site: https://blog.example.com(自动填充)
|
||||
name: xxxxx(自动填充,文章ID)
|
||||
publish: true(自动填充,发布状态)
|
||||
---
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 常见问题
|
||||
|
||||
### Q: 为什么我的图片没有上传到 Halo?
|
||||
|
||||
请确保在插件设置中启用了「图片上传」功能:
|
||||
1. 进入插件设置
|
||||
2. 找到「图片上传设置」
|
||||
3. 勾选「启用图片上传」
|
||||
|
||||
### Q: 如何发布后立即发布而不是存为草稿?
|
||||
|
||||
在插件设置中勾选「默认发布文章」。
|
||||
|
||||
### Q: 如何管理标签和分类?
|
||||
|
||||
1. 按 `Ctrl+P` 输入「管理标签」或「管理分类」
|
||||
2. 在弹窗中可以创建、编辑、删除标签和分类
|
||||
|
||||
### Q: 想要批量操作怎么办?
|
||||
|
||||
使用同步状态面板:
|
||||
1. 点击同步图标打开面板
|
||||
2. 查看所有已发布文章
|
||||
3. 可以逐个更新或拉取
|
||||
|
||||
### Q: 导出功能在哪里?
|
||||
|
||||
在命令面板中搜索「导出」:
|
||||
- `Halo: 导出为 Markdown` - 导出为带 frontmatter 的 .md 文件
|
||||
- `Halo: 导出为 JSON` - 导出为 JSON 格式(含元数据)
|
||||
|
||||
### Q: 搜索功能怎么用?
|
||||
|
||||
1. 按 `Ctrl+P` 输入「搜索 Halo」
|
||||
2. 输入关键词搜索标题或 slug
|
||||
3. 可以筛选:全部 / 已发布 / 草稿
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
1. **备份重要数据**:删除操作不可撤销,请谨慎操作
|
||||
2. **图片上传**:首次上传图片需要较长时间,请耐心等待
|
||||
3. **令牌权限**:确保个人令牌有文章管理权限
|
||||
|
||||
---
|
||||
|
||||
## 📞 获取帮助
|
||||
|
||||
如果遇到问题,请:
|
||||
1. 查看控制台错误信息(开发者工具)
|
||||
2. 检查 Halo 站点是否正常运行
|
||||
3. 确认令牌权限是否足够
|
||||
|
||||
---
|
||||
|
||||
最后更新:2024年4月
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"ribbon_icon": {
|
||||
"publish": "Publish current document to Halo"
|
||||
"publish": "Publish current document to Halo",
|
||||
"sync_status": "Sync Status Panel"
|
||||
},
|
||||
"command": {
|
||||
"publish": {
|
||||
@@ -44,6 +45,9 @@
|
||||
"name": "Export as Markdown",
|
||||
"error_not_published": "This document is not published to Halo"
|
||||
},
|
||||
"open_sync_panel": {
|
||||
"name": "Open Sync Status Panel"
|
||||
},
|
||||
"export_json": {
|
||||
"name": "Export as JSON",
|
||||
"error_not_published": "This document is not published to Halo"
|
||||
@@ -249,6 +253,27 @@
|
||||
"error_export_failed": "Export failed",
|
||||
"notice_export_success": "Exported to file: {fileName}"
|
||||
},
|
||||
"sync_panel": {
|
||||
"title": "Sync Status",
|
||||
"button_refresh": "Refresh",
|
||||
"button_history": "History",
|
||||
"button_clear_history": "Clear History",
|
||||
"button_update": "Update",
|
||||
"button_pull": "Pull",
|
||||
"empty": "No published posts yet",
|
||||
"total_posts": "Total {count} posts"
|
||||
},
|
||||
"sync_history": {
|
||||
"title": "Sync History",
|
||||
"empty": "No sync records",
|
||||
"stats": "Total {total} records, {success} successful",
|
||||
"action_publish": "Publish",
|
||||
"action_update": "Update",
|
||||
"action_pull": "Pull",
|
||||
"action_delete": "Delete",
|
||||
"confirm_clear": "Are you sure you want to clear all sync history?",
|
||||
"notice_cleared": "History cleared"
|
||||
},
|
||||
"common": {
|
||||
"error_connection_failed": "Connection failed",
|
||||
"button_close": "Close",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"ribbon_icon": {
|
||||
"publish": "发布当前文档到 Halo"
|
||||
"publish": "发布当前文档到 Halo",
|
||||
"sync_status": "同步状态面板"
|
||||
},
|
||||
"command": {
|
||||
"publish": {
|
||||
@@ -47,6 +48,9 @@
|
||||
"export_json": {
|
||||
"name": "导出为 JSON",
|
||||
"error_not_published": "此文档还未发布到 Halo"
|
||||
},
|
||||
"open_sync_panel": {
|
||||
"name": "打开同步状态面板"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
@@ -249,6 +253,27 @@
|
||||
"error_export_failed": "导出失败",
|
||||
"notice_export_success": "已导出到文件: {fileName}"
|
||||
},
|
||||
"sync_panel": {
|
||||
"title": "同步状态",
|
||||
"button_refresh": "刷新",
|
||||
"button_history": "历史",
|
||||
"button_clear_history": "清除历史",
|
||||
"button_update": "更新",
|
||||
"button_pull": "拉取",
|
||||
"empty": "暂无已发布的文章",
|
||||
"total_posts": "共 {count} 篇文章"
|
||||
},
|
||||
"sync_history": {
|
||||
"title": "同步历史",
|
||||
"empty": "暂无同步记录",
|
||||
"stats": "共 {total} 条记录,成功 {success} 条",
|
||||
"action_publish": "发布",
|
||||
"action_update": "更新",
|
||||
"action_pull": "拉取",
|
||||
"action_delete": "删除",
|
||||
"confirm_clear": "确定要清除所有同步历史吗?",
|
||||
"notice_cleared": "历史已清除"
|
||||
},
|
||||
"common": {
|
||||
"error_connection_failed": "连接失败",
|
||||
"button_close": "关闭",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"ribbon_icon": {
|
||||
"publish": "發佈當前文件到 Halo"
|
||||
"publish": "發佈當前文件到 Halo",
|
||||
"sync_status": "同步狀態面板"
|
||||
},
|
||||
"command": {
|
||||
"publish": {
|
||||
@@ -44,6 +45,9 @@
|
||||
"name": "導出為 Markdown",
|
||||
"error_not_published": "此文件還未發佈到 Halo"
|
||||
},
|
||||
"open_sync_panel": {
|
||||
"name": "打開同步狀態面板"
|
||||
},
|
||||
"export_json": {
|
||||
"name": "導出為 JSON",
|
||||
"error_not_published": "此文件還未發佈到 Halo"
|
||||
@@ -249,6 +253,27 @@
|
||||
"error_export_failed": "導出失敗",
|
||||
"notice_export_success": "已導出到文件: {fileName}"
|
||||
},
|
||||
"sync_panel": {
|
||||
"title": "同步狀態",
|
||||
"button_refresh": "刷新",
|
||||
"button_history": "歷史",
|
||||
"button_clear_history": "清除歷史",
|
||||
"button_update": "更新",
|
||||
"button_pull": "拉取",
|
||||
"empty": "暫無已發佈的文章",
|
||||
"total_posts": "共 {count} 篇文章"
|
||||
},
|
||||
"sync_history": {
|
||||
"title": "同步歷史",
|
||||
"empty": "暫無同步記錄",
|
||||
"stats": "共 {total} 條記錄,成功 {success} 條",
|
||||
"action_publish": "發布",
|
||||
"action_update": "更新",
|
||||
"action_pull": "拉取",
|
||||
"action_delete": "刪除",
|
||||
"confirm_clear": "確定要清除所有同步歷史嗎?",
|
||||
"notice_cleared": "歷史已清除"
|
||||
},
|
||||
"common": {
|
||||
"error_connection_failed": "連接失敗",
|
||||
"button_close": "關閉",
|
||||
|
||||
@@ -8,6 +8,7 @@ import { deletePost } from "./commands/delete-post";
|
||||
import { manageTags, manageCategories } from "./commands/manage-taxonomy";
|
||||
import { exportPostAsMarkdown, exportPostAsJson } from "./commands/export-post";
|
||||
import { searchPosts } from "./commands/search-posts";
|
||||
import { SyncStatusView, SYNC_STATUS_VIEW_TYPE } from "./views/sync-status-view";
|
||||
import { DEFAULT_SETTINGS, type HaloSetting, HaloSettingTab, type HaloSite } from "./settings";
|
||||
import HaloService from "./service";
|
||||
import { openSiteSelectionModal } from "./site-selection-modal";
|
||||
@@ -33,6 +34,10 @@ export default class HaloPlugin extends Plugin {
|
||||
await this.publishCommand();
|
||||
});
|
||||
|
||||
this.addRibbonIcon("sync-icon", i18next.t("ribbon_icon.sync_status"), async (evt: MouseEvent) => {
|
||||
await this.openSyncStatusPanel();
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "publish",
|
||||
name: i18next.t("command.publish.name"),
|
||||
@@ -181,7 +186,24 @@ export default class HaloPlugin extends Plugin {
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "open-sync-status-panel",
|
||||
name: i18next.t("command.open_sync_panel.name"),
|
||||
callback: async () => {
|
||||
await this.openSyncStatusPanel();
|
||||
},
|
||||
});
|
||||
|
||||
this.addSettingTab(new HaloSettingTab(this));
|
||||
|
||||
this.registerView(SYNC_STATUS_VIEW_TYPE, (leaf) => new SyncStatusView(leaf, this));
|
||||
}
|
||||
|
||||
async openSyncStatusPanel() {
|
||||
const leaf = this.app.workspace.getRightLeaf(false);
|
||||
if (leaf) {
|
||||
await leaf.setViewState({ type: SYNC_STATUS_VIEW_TYPE });
|
||||
}
|
||||
}
|
||||
|
||||
onunload() {}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import i18next from "i18next";
|
||||
import { Modal, Notice } from "obsidian";
|
||||
|
||||
export interface SyncHistoryItem {
|
||||
id: string;
|
||||
action: "publish" | "update" | "pull" | "delete";
|
||||
title: string;
|
||||
timestamp: number;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
export function openSyncHistoryModal(history: SyncHistoryItem[]): void {
|
||||
const modal = new SyncHistoryModal(history);
|
||||
modal.open();
|
||||
}
|
||||
|
||||
class SyncHistoryModal extends Modal {
|
||||
constructor(private readonly history: SyncHistoryItem[]) {
|
||||
super(app);
|
||||
}
|
||||
|
||||
async onOpen() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
|
||||
contentEl.createEl("h2", {
|
||||
text: i18next.t("sync_history.title"),
|
||||
});
|
||||
|
||||
const stats = contentEl.createDiv("history-stats");
|
||||
const successCount = this.history.filter(h => h.success).length;
|
||||
stats.createEl("span", {
|
||||
text: i18next.t("sync_history.stats", { total: this.history.length, success: successCount })
|
||||
});
|
||||
|
||||
if (this.history.length === 0) {
|
||||
contentEl.createEl("p", {
|
||||
text: i18next.t("sync_history.empty"),
|
||||
cls: "history-empty"
|
||||
});
|
||||
} else {
|
||||
const list = contentEl.createDiv("history-list");
|
||||
|
||||
for (const item of [...this.history].reverse()) {
|
||||
const itemEl = list.createDiv("history-item");
|
||||
|
||||
const headerEl = itemEl.createDiv("history-header");
|
||||
|
||||
const icon = item.success ? "✅" : "❌";
|
||||
const actionText = this.getActionText(item.action);
|
||||
|
||||
headerEl.createEl("span", {
|
||||
text: `${icon} ${actionText}`,
|
||||
cls: "history-action"
|
||||
});
|
||||
|
||||
headerEl.createEl("span", {
|
||||
text: new Date(item.timestamp).toLocaleString(),
|
||||
cls: "history-time"
|
||||
});
|
||||
|
||||
const contentEl2 = itemEl.createDiv("history-content");
|
||||
contentEl2.createEl("span", {
|
||||
text: item.title,
|
||||
cls: "history-title"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const actions = contentEl.createDiv("history-actions");
|
||||
actions.createEl("button", {
|
||||
text: i18next.t("common.button_close"),
|
||||
cls: "mod-warning"
|
||||
}).addEventListener("click", () => this.close());
|
||||
}
|
||||
|
||||
private getActionText(action: string): string {
|
||||
const actionMap: Record<string, string> = {
|
||||
publish: i18next.t("sync_history.action_publish"),
|
||||
update: i18next.t("sync_history.action_update"),
|
||||
pull: i18next.t("sync_history.action_pull"),
|
||||
delete: i18next.t("sync_history.action_delete"),
|
||||
};
|
||||
return actionMap[action] || action;
|
||||
}
|
||||
|
||||
onClose() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
@@ -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