347 lines
10 KiB
TypeScript
347 lines
10 KiB
TypeScript
import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting, TFile } from 'obsidian';
|
|
import { WolaiSyncSettings } from './src/types';
|
|
import { SyncManager } from './src/SyncManager';
|
|
import { FileWatcher } from './src/FileWatcher';
|
|
import { WolaiSyncSettingTab } from './src/SettingsTab';
|
|
|
|
// Remember to rename these classes and interfaces!
|
|
|
|
const DEFAULT_SETTINGS: WolaiSyncSettings = {
|
|
obsidianFolder: '',
|
|
wolaiDatabaseId: '',
|
|
wolaiAppId: '',
|
|
wolaiAppSecret: '',
|
|
syncInterval: 20,
|
|
autoSync: true,
|
|
enableFileWatcher: false,
|
|
lastSyncTime: 0
|
|
}
|
|
|
|
export default class WolaiSyncPlugin extends Plugin {
|
|
settings: WolaiSyncSettings;
|
|
syncManager: SyncManager;
|
|
fileWatcher: FileWatcher;
|
|
statusBarItemEl: HTMLElement;
|
|
syncIntervalId: number | null = null;
|
|
|
|
async onload() {
|
|
console.log('Plugin: Obsidian Wolai Sync loaded');
|
|
new Notice('Wolai Sync plugin loaded');
|
|
await this.loadSettings();
|
|
|
|
// 初始化同步管理器
|
|
this.syncManager = new SyncManager(this.app.vault, this.settings);
|
|
|
|
// 初始化文件监听器
|
|
this.initializeFileWatcher();
|
|
|
|
// This creates an icon in the left ribbon.
|
|
const ribbonIconEl = this.addRibbonIcon('refresh-ccw-dot', 'Wolai Sync', async (evt: MouseEvent) => {
|
|
// 执行手动同步
|
|
await this.performManualSync();
|
|
});
|
|
// Perform additional things with the ribbon
|
|
ribbonIconEl.addClass('my-plugin-ribbon-class');
|
|
|
|
// This adds a status bar item to the bottom of the app. Does not work on mobile apps.
|
|
this.statusBarItemEl = this.addStatusBarItem();
|
|
this.updateStatusBar('Ready');
|
|
|
|
// This adds a settings tab so the user can configure various aspects of the plugin
|
|
this.addSettingTab(new WolaiSyncSettingTab(this.app, this));
|
|
|
|
// 添加强制同步当前文件的Command
|
|
this.addCommand({
|
|
id: 'force-sync-current-file',
|
|
name: '强制同步当前文件',
|
|
checkCallback: (checking: boolean) => {
|
|
// 检查是否有活动的Markdown文件
|
|
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
|
if (activeView && activeView.file) {
|
|
if (!checking) {
|
|
this.forceSyncCurrentFile();
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
});
|
|
|
|
// // If the plugin hooks up any global DOM events (on parts of the app that doesn't belong to this plugin)
|
|
// // Using this function will automatically remove the event listener when this plugin is disabled.
|
|
// this.registerDomEvent(document, 'click', (evt: MouseEvent) => {
|
|
// console.log('click', evt);
|
|
// });
|
|
|
|
// 启动定时同步
|
|
this.setupScheduledSync();
|
|
|
|
console.log('Wolai Sync plugin initialization completed');
|
|
}
|
|
|
|
onunload() {
|
|
console.log('Plugin: Obsidian Wolai Sync unloaded. Bye bye~');
|
|
|
|
// 清理文件监听器
|
|
if (this.fileWatcher) {
|
|
this.fileWatcher.stopWatching();
|
|
}
|
|
|
|
// 清理定时同步
|
|
if (this.syncIntervalId) {
|
|
window.clearInterval(this.syncIntervalId);
|
|
this.syncIntervalId = null;
|
|
}
|
|
}
|
|
|
|
async loadSettings() {
|
|
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
|
}
|
|
|
|
async saveSettings() {
|
|
await this.saveData(this.settings);
|
|
|
|
// 更新同步管理器设置
|
|
if (this.syncManager) {
|
|
this.syncManager.updateSettings(this.settings);
|
|
}
|
|
|
|
// 重新初始化文件监听器(根据新的设置)
|
|
if (this.fileWatcher) {
|
|
this.fileWatcher.stopWatching();
|
|
}
|
|
this.initializeFileWatcher();
|
|
|
|
// 重新设置定时同步
|
|
this.setupScheduledSync();
|
|
}
|
|
|
|
private initializeFileWatcher(): void {
|
|
this.fileWatcher = new FileWatcher(
|
|
this.app.vault,
|
|
this.settings.obsidianFolder,
|
|
{
|
|
onFileChange: async (filePath: string) => {
|
|
console.log(`File changed: ${filePath}`);
|
|
await this.syncSingleFile(filePath);
|
|
},
|
|
onFileCreate: async (filePath: string) => {
|
|
console.log(`File created: ${filePath}`);
|
|
await this.syncSingleFile(filePath);
|
|
},
|
|
onFileDelete: async (filePath: string) => {
|
|
console.log(`File deleted: ${filePath}`);
|
|
// 从同步记录中移除
|
|
await this.syncManager.removeSyncRecord(filePath);
|
|
}
|
|
}
|
|
);
|
|
|
|
// 只有在启用文件监听器且设置了文件夹时才开始监听
|
|
if (this.settings.enableFileWatcher && this.settings.obsidianFolder) {
|
|
this.fileWatcher.startWatching();
|
|
console.log('File watcher enabled and started');
|
|
} else {
|
|
console.log('File watcher disabled or no folder specified');
|
|
}
|
|
}
|
|
|
|
private async syncSingleFile(filePath: string): Promise<void> {
|
|
try {
|
|
// 检查文件是否需要同步
|
|
const file = this.app.vault.getAbstractFileByPath(filePath) as TFile;
|
|
if (!file || !(file instanceof TFile)) {
|
|
console.log(`File not found or not a markdown file: ${filePath}`);
|
|
return;
|
|
}
|
|
|
|
const content = await this.app.vault.read(file);
|
|
const parsedMarkdown = this.syncManager.markdownParser.parseMarkdown(content);
|
|
|
|
// 只对需要同步的文件执行同步
|
|
if (!this.syncManager.markdownParser.needsSync(parsedMarkdown.frontMatter)) {
|
|
console.log(`File ${filePath} doesn't need sync, skipping...`);
|
|
return;
|
|
}
|
|
|
|
this.updateStatusBar('Syncing...');
|
|
const success = await this.syncManager.syncFile(filePath);
|
|
if (success) {
|
|
this.updateStatusBar('Synced');
|
|
console.log(`Successfully synced: ${filePath}`);
|
|
} else {
|
|
this.updateStatusBar('Sync Failed');
|
|
console.error(`Failed to sync: ${filePath}`);
|
|
}
|
|
} catch (error) {
|
|
console.error(`Error syncing file ${filePath}:`, error);
|
|
this.updateStatusBar('Sync Error');
|
|
}
|
|
}
|
|
|
|
private async performManualSync(): Promise<void> {
|
|
try {
|
|
this.updateStatusBar('Manual Sync...');
|
|
new Notice('开始手动同步...');
|
|
|
|
const result = await this.syncManager.fullSync();
|
|
const totalSynced = result.obsidianToWolai + result.wolaiToObsidian;
|
|
|
|
this.updateStatusBar('Synced');
|
|
|
|
if (totalSynced > 0) {
|
|
new Notice(`手动同步完成: Obsidian→Wolai ${result.obsidianToWolai}个文件, Wolai→Obsidian ${result.wolaiToObsidian}个文件`);
|
|
} else {
|
|
new Notice('没有需要同步的文件');
|
|
}
|
|
|
|
// 更新最后同步时间
|
|
this.settings.lastSyncTime = Date.now();
|
|
await this.saveSettings();
|
|
|
|
} catch (error) {
|
|
console.error('Manual sync failed:', error);
|
|
this.updateStatusBar('Sync Failed');
|
|
new Notice('手动同步失败,请查看控制台日志');
|
|
}
|
|
}
|
|
|
|
private setupScheduledSync(): void {
|
|
// 清理现有的定时器
|
|
if (this.syncIntervalId) {
|
|
window.clearInterval(this.syncIntervalId);
|
|
this.syncIntervalId = null;
|
|
}
|
|
|
|
// 如果启用了自动同步,设置新的定时器
|
|
if (this.settings.autoSync && this.settings.syncInterval > 0) {
|
|
const intervalMs = this.settings.syncInterval * 60 * 1000; // 转换为毫秒
|
|
|
|
this.syncIntervalId = window.setInterval(async () => {
|
|
try {
|
|
console.log('Starting scheduled sync...');
|
|
this.updateStatusBar('Auto Sync...');
|
|
|
|
await this.syncManager.scheduledSync();
|
|
|
|
this.updateStatusBar('Synced');
|
|
console.log('Scheduled sync completed');
|
|
} catch (error) {
|
|
console.error('Scheduled sync failed:', error);
|
|
this.updateStatusBar('Sync Failed');
|
|
}
|
|
}, intervalMs);
|
|
|
|
console.log(`Scheduled sync enabled: every ${this.settings.syncInterval} minutes`);
|
|
}
|
|
}
|
|
|
|
private updateStatusBar(status: string): void {
|
|
if (this.statusBarItemEl) {
|
|
this.statusBarItemEl.setText(`Wolai: ${status}`);
|
|
}
|
|
}
|
|
|
|
// 公共方法供设置页面调用
|
|
async testConnection(): Promise<boolean> {
|
|
if (!this.syncManager) {
|
|
return false;
|
|
}
|
|
return await this.syncManager.validateSync();
|
|
}
|
|
|
|
async manualSyncFromSettings(): Promise<void> {
|
|
await this.performManualSync();
|
|
}
|
|
|
|
getSyncStats(): { total: number; synced: number; pending: number } {
|
|
if (!this.syncManager) {
|
|
return { total: 0, synced: 0, pending: 0 };
|
|
}
|
|
return this.syncManager.getSyncStats();
|
|
}
|
|
|
|
updateSyncManager(): void {
|
|
if (this.syncManager) {
|
|
this.syncManager.updateSettings(this.settings);
|
|
}
|
|
}
|
|
|
|
startScheduledSync(): void {
|
|
this.stopScheduledSync(); // 先停止现有的定时器
|
|
|
|
if (this.settings.autoSync && this.settings.syncInterval > 0) {
|
|
const intervalMs = this.settings.syncInterval * 60 * 1000; // 转换为毫秒
|
|
this.syncIntervalId = window.setInterval(async () => {
|
|
try {
|
|
console.log('Executing scheduled sync...');
|
|
await this.syncManager.scheduledSync();
|
|
} catch (error) {
|
|
console.error('Scheduled sync failed:', error);
|
|
}
|
|
}, intervalMs);
|
|
|
|
console.log(`Scheduled sync started with interval: ${this.settings.syncInterval} minutes`);
|
|
}
|
|
}
|
|
|
|
stopScheduledSync(): void {
|
|
if (this.syncIntervalId) {
|
|
window.clearInterval(this.syncIntervalId);
|
|
this.syncIntervalId = null;
|
|
console.log('Scheduled sync stopped');
|
|
}
|
|
}
|
|
|
|
async forceSyncCurrentFile(): Promise<void> {
|
|
try {
|
|
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
|
if (!activeView || !activeView.file) {
|
|
new Notice('没有打开的Markdown文件');
|
|
return;
|
|
}
|
|
|
|
const filePath = activeView.file.path;
|
|
console.log(`强制同步当前文件: ${filePath}`);
|
|
|
|
// 显示开始同步通知
|
|
new Notice(`开始强制同步: ${activeView.file.basename}`);
|
|
this.updateStatusBar('Force Syncing...');
|
|
|
|
// 使用强制同步方法,绕过所有检查
|
|
const success = await this.syncManager.forceSyncObsidianToWolai(filePath);
|
|
|
|
if (success) {
|
|
this.updateStatusBar('Force Synced');
|
|
new Notice(`✅ 强制同步成功: ${activeView.file.basename}`);
|
|
console.log(`Successfully force synced: ${filePath}`);
|
|
} else {
|
|
this.updateStatusBar('Force Sync Failed');
|
|
new Notice(`❌ 强制同步失败: ${activeView.file.basename}`);
|
|
console.error(`Failed to force sync: ${filePath}`);
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Error force syncing current file:', error);
|
|
this.updateStatusBar('Force Sync Error');
|
|
new Notice(`❌ 强制同步出错,请查看控制台日志`);
|
|
}
|
|
}
|
|
}
|
|
|
|
class SampleModal extends Modal {
|
|
constructor(app: App) {
|
|
super(app);
|
|
}
|
|
|
|
onOpen() {
|
|
const {contentEl} = this;
|
|
contentEl.setText('Woah!');
|
|
}
|
|
|
|
onClose() {
|
|
const {contentEl} = this;
|
|
contentEl.empty();
|
|
}
|
|
}
|