diff --git a/src/lang/locale/en.ts b/src/lang/locale/en.ts index 23eb1b8..ea43964 100644 --- a/src/lang/locale/en.ts +++ b/src/lang/locale/en.ts @@ -35,6 +35,11 @@ export const en = { NotionUserText: "Enter your notion ID", NotionLinkDisplay: "Notion Link Display", NotionLinkDisplayDesc: "Default is ON, if you want to hide the link in the front matter, please turn it off", + AutoSync: "Auto Sync", + AutoSyncDesc: "Automatically sync to Notion when frontmatter or content is modified (requires existing NotionID)", + AutoSyncDelay: "Auto Sync Delay (seconds)", + AutoSyncDelayDesc: "How many seconds to wait after document modification before triggering auto sync (default: 5 seconds, minimum: 2 seconds)", + AutoSyncDelayText: "Enter delay in seconds", NotionGeneralSettingHeader: "General Notion Database Settings", NotionGeneralButton: "Notion General Sync", NotionGeneralButtonDesc: "Open this option, Sync to Notion General Database command will be displayed in the command palette (default: ON)", diff --git a/src/lang/locale/ja.ts b/src/lang/locale/ja.ts index df6e5ed..38c5bab 100644 --- a/src/lang/locale/ja.ts +++ b/src/lang/locale/ja.ts @@ -33,6 +33,11 @@ export const ja = { NotionUserText: "Notion IDを入力", NotionLinkDisplay: "Notionリンク表示", NotionLinkDisplayDesc: "デフォルトはONです。front matterにリンクを非表示にしたい場合は、オフにしてください", + AutoSync: "自動同期", + AutoSyncDesc: "frontmatter またはコンテンツが変更されたときに自動的に Notion に同期します(NotionID が必要)", + AutoSyncDelay: "自動同期遅延時間(秒)", + AutoSyncDelayDesc: "ドキュメントの変更後、自動同期をトリガーするまでの待機時間(デフォルト:5秒、最小:2秒)", + AutoSyncDelayText: "遅延秒数を入力", NotionGeneralSettingHeader: "一般的なNotionデータベース設定", NotionGeneralButton: "一般的なNotion同期", NotionGeneralButtonDesc: "このオプションを開くと、一般的なNotionデータベース同期コマンドがコマンドパレットに表示されます(デフォルト:ON)", diff --git a/src/lang/locale/zh.ts b/src/lang/locale/zh.ts index f982b45..b03296a 100644 --- a/src/lang/locale/zh.ts +++ b/src/lang/locale/zh.ts @@ -35,6 +35,11 @@ export const zh = { NotionUserText: "输入你的 Notion ID", NotionLinkDisplay: "Notion 链接显示", NotionLinkDisplayDesc: "默认开启,如果你不想在front matter中显示链接,请关闭", + AutoSync: "自动同步", + AutoSyncDesc: "当检测到文档的 frontmatter 或内容发生修改时,自动同步到 Notion(需要文档已有 NotionID)", + AutoSyncDelay: "自动同步延迟时间(秒)", + AutoSyncDelayDesc: "文档修改后等待多少秒才触发自动同步,避免频繁同步(默认:5秒,最小:2秒)", + AutoSyncDelayText: "输入延迟秒数", NotionGeneralSettingHeader: "普通 Notion 数据库设置", NotionGeneralButton: "普通数据库同步", NotionGeneralButtonDesc: "打开此选项,同步到普通数据库命令将显示在命令面板中(默认:开)", diff --git a/src/main.ts b/src/main.ts index 6d0b517..d856772 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,8 +1,9 @@ -import { App, Editor, MarkdownView, Notice, Plugin, PluginSettingTab, Setting } from "obsidian"; +import { App, Editor, MarkdownView, Notice, Plugin, PluginSettingTab, Setting, TFile, EventRef } from "obsidian"; import { addIcons } from 'src/ui/icon'; import { i18nConfig } from "src/lang/I18n"; import ribbonCommands from "src/commands/NotionCommands"; import { ObsidianSettingTab, PluginSettings, DEFAULT_SETTINGS, DatabaseDetails } from "src/ui/settingTabs"; +import { uploadCommandNext, uploadCommandGeneral, uploadCommandCustom } from "src/upload/uploadCommand"; // Remember to rename these classes and interfaces! @@ -11,6 +12,9 @@ export default class ObsidianSyncNotionPlugin extends Plugin { settings: PluginSettings; commands: ribbonCommands; app: App; + modifyEventRef: EventRef | null = null; + autoSyncTimeout: NodeJS.Timeout | null = null; + private syncingFiles: Set = new Set(); async onload() { await this.loadSettings(); @@ -36,9 +40,18 @@ export default class ObsidianSyncNotionPlugin extends Plugin { // This adds a settings tab so the user can configure various aspects of the plugin this.addSettingTab(new ObsidianSettingTab(this.app, this)); + // Setup auto sync listener + this.setupAutoSync(); + } onunload() { + if (this.modifyEventRef) { + this.app.vault.offref(this.modifyEventRef); + } + if (this.autoSyncTimeout) { + clearTimeout(this.autoSyncTimeout); + } } async loadSettings() { @@ -80,6 +93,94 @@ export default class ObsidianSyncNotionPlugin extends Plugin { await this.saveSettings(); } + setupAutoSync() { + // Remove existing listener if any + if (this.modifyEventRef) { + this.app.vault.offref(this.modifyEventRef); + } + + // Only setup if autoSync is enabled + if (!this.settings.autoSync) { + return; + } + + // Listen for file modifications + this.modifyEventRef = this.app.vault.on('modify', async (file: TFile) => { + // Only process markdown files + if (!(file instanceof TFile) || file.extension !== 'md') { + return; + } + + // Debounce: clear existing timeout + if (this.autoSyncTimeout) { + clearTimeout(this.autoSyncTimeout); + } + + // Set a new timeout to trigger sync after user-configured delay (in seconds) + const delayMs = (this.settings.autoSyncDelay || 2) * 1000; + this.autoSyncTimeout = setTimeout(async () => { + await this.autoSyncFile(file); + }, delayMs); + }); + } + + async autoSyncFile(file: TFile) { + // Check if file is already being synced + if (this.syncingFiles.has(file.path)) { + console.log(`[AutoSync] File ${file.path} is already being synced, skipping`); + return; + } + + try { + this.syncingFiles.add(file.path); + + // Get file's frontmatter + const frontMatter = this.app.metadataCache.getFileCache(file)?.frontmatter; + if (!frontMatter) { + console.log(`[AutoSync] No frontmatter found in ${file.path}`); + return; + } + + // Find which database this file belongs to by checking for NotionID-{abName} + let foundDbDetails: DatabaseDetails | null = null; + let notionId: string | null = null; + + for (const key in this.settings.databaseDetails) { + const dbDetails = this.settings.databaseDetails[key]; + const notionIDKey = `NotionID-${dbDetails.abName}`; + + if (frontMatter[notionIDKey]) { + foundDbDetails = dbDetails; + notionId = String(frontMatter[notionIDKey]); + break; + } + } + + // If no NotionID found, skip auto sync + if (!foundDbDetails || !notionId) { + console.log(`[AutoSync] No NotionID found in ${file.path}, skipping auto sync`); + return; + } + + console.log(`[AutoSync] ${new Date().toISOString()} Auto syncing ${file.basename} to ${foundDbDetails.fullName}`); + + // Trigger appropriate upload command based on database format + if (foundDbDetails.format === 'next') { + await uploadCommandNext(this, this.settings, foundDbDetails, this.app); + } else if (foundDbDetails.format === 'general') { + await uploadCommandGeneral(this, this.settings, foundDbDetails, this.app); + } else if (foundDbDetails.format === 'custom') { + await uploadCommandCustom(this, this.settings, foundDbDetails, this.app); + } + + } catch (error) { + console.error(`[AutoSync] Error syncing file ${file.path}:`, error); + new Notice(`Auto sync failed for ${file.basename}: ${error.message}`); + } finally { + this.syncingFiles.delete(file.path); + } + } + } diff --git a/src/ui/settingTabs.ts b/src/ui/settingTabs.ts index 543d6f4..023479f 100644 --- a/src/ui/settingTabs.ts +++ b/src/ui/settingTabs.ts @@ -13,6 +13,8 @@ export interface PluginSettings { bannerUrl: string; notionUser: string; NotionLinkDisplay: boolean; + autoSync: boolean; + autoSyncDelay: number; proxy: string; GeneralButton: boolean; tagButton: boolean; @@ -49,6 +51,8 @@ export const DEFAULT_SETTINGS: PluginSettings = { bannerUrl: "", notionUser: "", NotionLinkDisplay: true, + autoSync: false, + autoSyncDelay: 5, proxy: "", GeneralButton: true, tagButton: true, @@ -67,6 +71,7 @@ export const DEFAULT_SETTINGS: PluginSettings = { export class ObsidianSettingTab extends PluginSettingTab { plugin: ObsidianSyncNotionPlugin; databaseEl: HTMLDivElement; + autoSyncDelayContainer: HTMLElement | null = null; constructor(app: App, plugin: ObsidianSyncNotionPlugin) { super(app, plugin); @@ -87,6 +92,34 @@ export class ObsidianSettingTab extends PluginSettingTab { this.createSettingEl(containerEl, i18nConfig.NotionLinkDisplay, i18nConfig.NotionLinkDisplayDesc, 'toggle', i18nConfig.NotionLinkDisplay, this.plugin.settings.NotionLinkDisplay, 'NotionLinkDisplay') + this.createSettingEl(containerEl, i18nConfig.AutoSync, i18nConfig.AutoSyncDesc, 'toggle', i18nConfig.AutoSync, this.plugin.settings.autoSync, 'autoSync') + + // Auto Sync Delay setting - only visible when autoSync is enabled + this.autoSyncDelayContainer = containerEl.createDiv(); + const delaySetting = new Setting(this.autoSyncDelayContainer) + .setName(i18nConfig.AutoSyncDelay) + .setDesc(i18nConfig.AutoSyncDelayDesc) + .addText((text) => + text + .setPlaceholder(i18nConfig.AutoSyncDelayText) + .setValue(String(this.plugin.settings.autoSyncDelay)) + .onChange(async (value) => { + const delay = parseFloat(value); + if (!isNaN(delay) && delay >= 2) { + this.plugin.settings.autoSyncDelay = delay; + await this.plugin.saveSettings(); + } else if (!isNaN(delay) && delay < 2) { + // If user enters less than 2 seconds, set it to 2 + this.plugin.settings.autoSyncDelay = 2; + await this.plugin.saveSettings(); + text.setValue('2'); + } + }) + ); + + // Set initial visibility + this.updateAutoSyncDelayVisibility(); + // add new button new Setting(containerEl) @@ -151,6 +184,13 @@ export class ObsidianSettingTab extends PluginSettingTab { element.style.alignItems = "center"; } + // Update visibility of autoSyncDelay setting based on autoSync toggle + public updateAutoSyncDelayVisibility() { + if (this.autoSyncDelayContainer) { + this.autoSyncDelayContainer.style.display = this.plugin.settings.autoSync ? "block" : "none"; + } + } + // function to add one setting element in the setting tab. public createSettingEl(containerEl: HTMLElement, name: string, desc: string, type: string, placeholder: string, holderValue: any, settingsKey: string) { if (type === 'password') { @@ -178,6 +218,12 @@ export class ObsidianSettingTab extends PluginSettingTab { this.plugin.settings[settingsKey] = value; // Update the plugin settings directly await this.plugin.saveSettings(); await this.plugin.commands.updateCommand(); + + // If autoSync setting changed, update the listener and visibility + if (settingsKey === 'autoSync') { + this.plugin.setupAutoSync(); + this.updateAutoSyncDelayVisibility(); + } }) ); } else if (type === 'text') {