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 { 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 { 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 { if (!this.syncManager) { return false; } return await this.syncManager.validateSync(); } async manualSyncFromSettings(): Promise { 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 { 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(); } }