feat: add auto sync functionality with configurable delay in settings

This commit is contained in:
Jiaxin Peng
2025-10-31 21:42:48 +00:00
parent 33f0848b32
commit c369c291a7
5 changed files with 163 additions and 1 deletions

View File

@@ -35,6 +35,11 @@ export const en = {
NotionUserText: "Enter your notion ID", NotionUserText: "Enter your notion ID",
NotionLinkDisplay: "Notion Link Display", NotionLinkDisplay: "Notion Link Display",
NotionLinkDisplayDesc: "Default is ON, if you want to hide the link in the front matter, please turn it off", 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", NotionGeneralSettingHeader: "General Notion Database Settings",
NotionGeneralButton: "Notion General Sync", NotionGeneralButton: "Notion General Sync",
NotionGeneralButtonDesc: "Open this option, Sync to Notion General Database command will be displayed in the command palette (default: ON)", NotionGeneralButtonDesc: "Open this option, Sync to Notion General Database command will be displayed in the command palette (default: ON)",

View File

@@ -33,6 +33,11 @@ export const ja = {
NotionUserText: "Notion IDを入力", NotionUserText: "Notion IDを入力",
NotionLinkDisplay: "Notionリンク表示", NotionLinkDisplay: "Notionリンク表示",
NotionLinkDisplayDesc: "デフォルトはONです。front matterにリンクを非表示にしたい場合は、オフにしてください", NotionLinkDisplayDesc: "デフォルトはONです。front matterにリンクを非表示にしたい場合は、オフにしてください",
AutoSync: "自動同期",
AutoSyncDesc: "frontmatter またはコンテンツが変更されたときに自動的に Notion に同期しますNotionID が必要)",
AutoSyncDelay: "自動同期遅延時間(秒)",
AutoSyncDelayDesc: "ドキュメントの変更後、自動同期をトリガーするまでの待機時間デフォルト5秒、最小2秒",
AutoSyncDelayText: "遅延秒数を入力",
NotionGeneralSettingHeader: "一般的なNotionデータベース設定", NotionGeneralSettingHeader: "一般的なNotionデータベース設定",
NotionGeneralButton: "一般的なNotion同期", NotionGeneralButton: "一般的なNotion同期",
NotionGeneralButtonDesc: "このオプションを開くと、一般的なNotionデータベース同期コマンドがコマンドパレットに表示されますデフォルトON", NotionGeneralButtonDesc: "このオプションを開くと、一般的なNotionデータベース同期コマンドがコマンドパレットに表示されますデフォルトON",

View File

@@ -35,6 +35,11 @@ export const zh = {
NotionUserText: "输入你的 Notion ID", NotionUserText: "输入你的 Notion ID",
NotionLinkDisplay: "Notion 链接显示", NotionLinkDisplay: "Notion 链接显示",
NotionLinkDisplayDesc: "默认开启如果你不想在front matter中显示链接请关闭", NotionLinkDisplayDesc: "默认开启如果你不想在front matter中显示链接请关闭",
AutoSync: "自动同步",
AutoSyncDesc: "当检测到文档的 frontmatter 或内容发生修改时,自动同步到 Notion需要文档已有 NotionID",
AutoSyncDelay: "自动同步延迟时间(秒)",
AutoSyncDelayDesc: "文档修改后等待多少秒才触发自动同步避免频繁同步默认5秒最小2秒",
AutoSyncDelayText: "输入延迟秒数",
NotionGeneralSettingHeader: "普通 Notion 数据库设置", NotionGeneralSettingHeader: "普通 Notion 数据库设置",
NotionGeneralButton: "普通数据库同步", NotionGeneralButton: "普通数据库同步",
NotionGeneralButtonDesc: "打开此选项,同步到普通数据库命令将显示在命令面板中(默认:开)", NotionGeneralButtonDesc: "打开此选项,同步到普通数据库命令将显示在命令面板中(默认:开)",

View File

@@ -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 { addIcons } from 'src/ui/icon';
import { i18nConfig } from "src/lang/I18n"; import { i18nConfig } from "src/lang/I18n";
import ribbonCommands from "src/commands/NotionCommands"; import ribbonCommands from "src/commands/NotionCommands";
import { ObsidianSettingTab, PluginSettings, DEFAULT_SETTINGS, DatabaseDetails } from "src/ui/settingTabs"; 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! // Remember to rename these classes and interfaces!
@@ -11,6 +12,9 @@ export default class ObsidianSyncNotionPlugin extends Plugin {
settings: PluginSettings; settings: PluginSettings;
commands: ribbonCommands; commands: ribbonCommands;
app: App; app: App;
modifyEventRef: EventRef | null = null;
autoSyncTimeout: NodeJS.Timeout | null = null;
private syncingFiles: Set<string> = new Set();
async onload() { async onload() {
await this.loadSettings(); 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 adds a settings tab so the user can configure various aspects of the plugin
this.addSettingTab(new ObsidianSettingTab(this.app, this)); this.addSettingTab(new ObsidianSettingTab(this.app, this));
// Setup auto sync listener
this.setupAutoSync();
} }
onunload() { onunload() {
if (this.modifyEventRef) {
this.app.vault.offref(this.modifyEventRef);
}
if (this.autoSyncTimeout) {
clearTimeout(this.autoSyncTimeout);
}
} }
async loadSettings() { async loadSettings() {
@@ -80,6 +93,94 @@ export default class ObsidianSyncNotionPlugin extends Plugin {
await this.saveSettings(); 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);
}
}
} }

View File

@@ -13,6 +13,8 @@ export interface PluginSettings {
bannerUrl: string; bannerUrl: string;
notionUser: string; notionUser: string;
NotionLinkDisplay: boolean; NotionLinkDisplay: boolean;
autoSync: boolean;
autoSyncDelay: number;
proxy: string; proxy: string;
GeneralButton: boolean; GeneralButton: boolean;
tagButton: boolean; tagButton: boolean;
@@ -49,6 +51,8 @@ export const DEFAULT_SETTINGS: PluginSettings = {
bannerUrl: "", bannerUrl: "",
notionUser: "", notionUser: "",
NotionLinkDisplay: true, NotionLinkDisplay: true,
autoSync: false,
autoSyncDelay: 5,
proxy: "", proxy: "",
GeneralButton: true, GeneralButton: true,
tagButton: true, tagButton: true,
@@ -67,6 +71,7 @@ export const DEFAULT_SETTINGS: PluginSettings = {
export class ObsidianSettingTab extends PluginSettingTab { export class ObsidianSettingTab extends PluginSettingTab {
plugin: ObsidianSyncNotionPlugin; plugin: ObsidianSyncNotionPlugin;
databaseEl: HTMLDivElement; databaseEl: HTMLDivElement;
autoSyncDelayContainer: HTMLElement | null = null;
constructor(app: App, plugin: ObsidianSyncNotionPlugin) { constructor(app: App, plugin: ObsidianSyncNotionPlugin) {
super(app, plugin); 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.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 // add new button
new Setting(containerEl) new Setting(containerEl)
@@ -151,6 +184,13 @@ export class ObsidianSettingTab extends PluginSettingTab {
element.style.alignItems = "center"; 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. // 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) { public createSettingEl(containerEl: HTMLElement, name: string, desc: string, type: string, placeholder: string, holderValue: any, settingsKey: string) {
if (type === 'password') { if (type === 'password') {
@@ -178,6 +218,12 @@ export class ObsidianSettingTab extends PluginSettingTab {
this.plugin.settings[settingsKey] = value; // Update the plugin settings directly this.plugin.settings[settingsKey] = value; // Update the plugin settings directly
await this.plugin.saveSettings(); await this.plugin.saveSettings();
await this.plugin.commands.updateCommand(); 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') { } else if (type === 'text') {