diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index abf3267..35ada10 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -43,6 +43,10 @@ jobs: run: | node -e 'const manifest=require("./manifest.json"); const tag=process.env.GITHUB_REF_NAME; if (manifest.version !== tag) { throw new Error(`manifest.json version ${manifest.version} does not match tag ${tag}`); }' + - name: Verify release notes exist + run: | + test -f "RELEASE_NOTES_${GITHUB_REF_NAME}.md" + - name: Generate artifact attestations uses: actions/attest@v4 with: @@ -53,7 +57,7 @@ jobs: - name: Create GitHub release uses: softprops/action-gh-release@v2 with: - body_path: RELEASE_NOTES_1.0.4.md + body_path: RELEASE_NOTES_${{ github.ref_name }}.md files: | main.js manifest.json diff --git a/RELEASE_NOTES_1.0.5.md b/RELEASE_NOTES_1.0.5.md new file mode 100644 index 0000000..f206c71 --- /dev/null +++ b/RELEASE_NOTES_1.0.5.md @@ -0,0 +1,26 @@ +# Vault2Dify 1.0.5 + +## Added + +No user-facing features in this release. + +## Fixed + +- Restored Marketplace installs for Obsidian 1.12.7 public stable users. +- Declared `1.0.5` compatible with Obsidian `1.12.7` so the community plugin browser no longer falls back to the unavailable `1.0.0` release. +- Kept previous release compatibility records unchanged to avoid rewriting historical release metadata. + +## Obsidian Review + +- Updated the release metadata path used by the GitHub Release workflow so future tags load matching release notes. +- Added a release notes existence check before creating GitHub releases. + +## Validation + +- `npm test` +- `npm run lint:obsidian` +- `npm run typecheck` +- `npm run build` +- `npm run install:test-vault` +- Manifest/tag version check in the GitHub Release workflow. +- Release notes existence check in the GitHub Release workflow. diff --git a/main.js b/main.js index 9dc6d4a..411cee9 100644 --- a/main.js +++ b/main.js @@ -118,5 +118,5 @@ If the connection is also unstable, run Test connection and follow connection tr Keep the documents you need and delete extras. Return to plugin settings and click Reset sync records. Run Sync now again so the plugin rebuilds records.`,helpDebugTitle:"Debug logs",helpDebugBody:"Temporarily enable debug logs when the error stays generic, browser access works but the plugin still fails, NAS / Docker / reverse proxy routing is complex, or you need to report an issue. Do not share API keys, private addresses, note content, or other sensitive information in logs.",helpPrivacyTitle:"Privacy and safety",helpPrivacyBody:"The plugin only reads Markdown files in the current Obsidian vault that match enabled path mappings. It only sends required Markdown content to your configured Dify service URL. It does not send note content to the plugin author or any third-party service other than your configured Dify service. The Dify API key is stored in Obsidian plugin local data; do not commit a {configDir} directory containing plugin settings to a public repository.",navAria:"Obsidian settings navigation",navAbout:"About",navEditor:"Editor",navFiles:"Files and links",navAppearance:"Appearance",navHotkeys:"Hotkeys",navPlugins:"Third-party plugins",clearConnectionConfig:"Clear config",selectPlaceholder:"Please select",showApiKey:"Show API key",hideApiKey:"Hide API key",healthConnection:"Connection",healthActiveUrl:"Active URL",healthRecentSync:"Recent sync",connectionUnknown:"Not tested",connectionMissing:"Needs setup",connectionConnected:"Connected",connectionFailedState:"Failed",recentSyncNone:"No sync yet",quickStepKey:"Enter key",quickStepKeyDesc:"Dify knowledge API key",quickStepTest:"Test connection",quickStepTestDesc:"Find an endpoint and load bases",quickStepMapping:"Add mapping",quickStepMappingDesc:"Choose folder and bases",sectionBasicConnection:"Basic connection",settingsReviewConnectionTitle:"Connection",settingsReviewMappingTitle:"Directory mapping",settingsReviewSyncTitle:"Sync settings",pendingMappingsTitle:"New mappings",bulkSet:"Bulk set",enableAll:"Enable all",pauseAll:"Pause all",chooseFolderFirst:"Select a folder or note first",pendingEmptyDesc:"Select an Obsidian folder or note on the left, then choose Dify knowledge bases on the right and click Add.",pendingAdded:"Mapping added to the list below.",selectFolderPlaceholder:"Select a folder or note",datasetsDropdownEmpty:"Load Dify knowledge bases first",folderSearch:"Search folders or notes",addPendingMapping:"Add",saveMapping:"Save mapping",syncAfterChange:"After changes",fullScanCheck:"Full check",startupSyncShort:"Startup sync",concurrentUpload:"Concurrent uploads",debounceOption8:"8 seconds",debounceOption15:"15 seconds",debounceOption30:"30 seconds",fullScanOption30:"Every 30 minutes",fullScanOption60:"Every 60 minutes",fullScanOptionOff:"Off",concurrencyOption2:"2 files",concurrencyOption4:"4 files",optionOn:"On",optionOff:"Off",settingsReviewLanUrlName:"NAS LAN address",settingsReviewLanUrlPlaceholder:"Enter NAS LAN address",settingsReviewPublicUrlName:"NAS public address",settingsReviewPublicUrlPlaceholder:"Enter NAS public address",basicConnectionDesc:"First-time setup keeps only the essentials; advanced endpoint options are collapsed.",apiUrlName:"Dify service URL",apiUrlDesc:"Use the Dify service URL reachable from this Obsidian device. You may omit http:// or paste a URL ending in /v1 or /v1/datasets.",connectionNoteIdle:"Enter an API key and Dify service URL, then test the connection.",connectionNoteMissing:"Enter an API key and Dify service URL first.",connectionNoteSuccess:"Connected: {count} knowledge bases via {url}.",connectionNoteFailed:"Connection failed: {message}",advancedConnectionSummary:"Advanced connection: LAN/public fallback URLs, API path compatibility, timeout, retries",expand:"Expand",collapse:"Collapse",advancedSync:"Tune advanced options",syncCadenceName:"Sync cadence",syncCadenceDesc:"Sync {debounce}s after changes; run a full check every {interval} minutes.",syncCadenceEventOnly:"Sync {debounce}s after changes; periodic full checks are off.",syncCadenceIntervalOnly:"File change sync is off; run a full check every {interval} minutes.",syncCadenceOff:"Auto sync is off. Manual sync still works.",sectionDiagnostics:"Sync diagnostics",diagnosticsDesc:"Long status lives in diagnostics. The status bar stays short.",metricTotal:"Recent tasks",metricSynced:"Synced",metricSkipped:"Skipped",metricFailed:"Failed",mappingIndex:"#",mappingFolderColumn:"Obsidian path",mappingDatasetColumn:"Dify knowledge bases",mappingStatusColumn:"Status",mappingActionColumn:"Actions",lastError:"Recent error: {message}",deleteMappingTitle:"Delete mapping",deleteMappingDesc:"This only deletes the sync configuration. It will not delete Obsidian files or remote Dify documents.",deleteMappingConfirm:"Delete mapping",mappingSaved:"Mapping saved.",sectionLanguage:"Language",languageName:"Interface language",languageDesc:"Switch settings, notices, and status bar language.",languageChinese:"\u4E2D\u6587",languageEnglish:"English",sectionConnection:"Connection",apiKeyName:"Dify API Key",apiKeyDesc:"API key used to access Dify knowledge base APIs.",apiKeyPlaceholder:"Enter your Dify API key",endpointModeName:"Endpoint strategy",endpointModeDesc:"Fill LAN, public reverse proxy, or cloud URLs when needed. Auto mode tries available URLs in order.",endpointPrimary:"Primary URL",endpointLan:"LAN URL",endpointPublic:"Public URL",endpointAuto:"Auto",primaryUrlName:"Primary API URL",primaryUrlDesc:"Reachable Dify service root URL, for example http://dify-host.example.test:5000 or https://dify.example.com.",lanUrlName:"LAN API URL",lanUrlDesc:"Use this for Dify on NAS/Docker, local Docker, or a LAN server. Prefer a full URL such as http://dify-host.example.test:5000.",publicUrlName:"Public API URL",publicUrlDesc:"Use this for reverse proxy, domain, public port, or Dify Cloud access. It must point to Dify, not a NAS/server admin page.",nasUrlHint:"Deployment URL tip: supports NAS/Docker, local Docker, LAN servers, public reverse proxies, and Dify Cloud. Do not use container-only addresses, api:5001, or localhost on the machine running Dify. Use the Dify root URL reachable from this Obsidian device. If http:// is missing, the plugin will try http:// automatically.",urlPlaceholder:"Enter your Dify service URL",apiPathStyleName:"Dify document API path",apiPathStyleDesc:"Auto is recommended. The plugin supports new create-by-text and legacy create_by_text paths.",apiPathAuto:"Auto compatible",apiPathHyphen:"New hyphen path",apiPathUnderscore:"Legacy underscore path",timeoutName:"Request timeout",timeoutDesc:"Timeout for one Dify API request, in seconds.",retriesName:"Retry count",retriesDesc:"Retries for network failures or 5xx responses.",testConnection:"Test connection",refreshDatasets:"Refresh knowledge bases",connectionOk:"Connected and loaded {count} knowledge bases via {url}",connectionFailed:"Connection failed: {message}",connectionErrorMissingConfig:"Enter a Dify API key and service URL first.",connectionErrorAuthFailed:"The API key is invalid. Check that it is correct.",connectionErrorPermissionDenied:"The API key does not have knowledge base access. Check Dify permissions.",connectionErrorNotFound:"The Dify knowledge base API was not found. Check the Dify service URL, port, and reverse proxy forwarding.",connectionErrorPathMismatch:"The API path is incompatible. Confirm the plugin is on the latest build, retry Test connection, then sync.",connectionErrorRateLimited:"Dify returned too many requests. Lower upload concurrency or try again later.",connectionErrorServer:"Dify returned a server error. Check the Dify container, service logs, or reverse proxy.",connectionErrorTimeout:"The request timed out. Confirm this device can reach the Dify service URL, and check Dify service load, ports, firewall, Tailscale, and reverse proxy.",connectionErrorNetwork:"Network connection failed. Check the URL, port, firewall, Docker mapping, or reverse proxy.",connectionErrorUnexpectedResponse:"The current address did not return the Dify knowledge base API. Check that the URL and port point to Dify.",connectionErrorUnknown:"Connection failed. Check the Dify settings and try again.",datasetsRefreshed:"Refreshed {count} knowledge bases.",datasetsEmpty:"Connected, but this key returned no accessible knowledge bases. Check the key workspace, knowledge base permissions, or whether the Dify service URL points to the correct Dify instance.",sectionMapping:"Path mappings",mappingDesc:"One Obsidian folder or note can sync to multiple Dify knowledge bases, and many paths can share one knowledge base.",addMapping:"Add mapping",edit:"Edit",delete:"Delete",enabled:"Enabled",disabled:"Disabled",rootFolder:"Entire vault",noMappings:"No mappings yet. Add a mapping to enable automatic sync.",mappingPageInfoEmpty:"0-0 / 0",datasetMissing:"No knowledge base selected",datasetUnknown:"Unknown knowledge base",mappingModalTitle:"Edit path mapping",addMappingModalTitle:"Add mapping",folderName:"Obsidian folder or note",folderDesc:"Choose a folder to sync all Markdown notes under it, or choose one note to sync only that note.",selectFolder:"Select folder",datasetName:"Dify knowledge bases",datasetDesc:"Multiple selections are supported. If the list is empty, refresh knowledge bases or enter IDs manually.",manualDatasetIds:"Manual knowledge base IDs",manualDatasetIdsDesc:"Separate multiple IDs with commas, spaces, or new lines.",save:"Save",cancel:"Cancel",selectFolderTitle:"Select Obsidian folder or note",choose:"Choose",sectionAuto:"Auto sync",autoSyncName:"Enable auto sync",autoSyncDesc:"Master switch. Event sync and periodic full scans pause when disabled.",eventSyncName:"Sync after file changes",eventSyncDesc:"Listen to Obsidian create, modify, and rename events, then upload after debounce.",startupSyncName:"Sync after startup",startupSyncDesc:"Check changes after Obsidian finishes loading.",periodicSyncName:"Periodic full scan",periodicSyncDesc:"Fallback for event sync, useful with NAS/Docker, local Docker, LAN servers, Dify Cloud, or multi-device workflows.",syncIntervalName:"Sync interval",syncIntervalDesc:"Periodic full scan interval, in minutes.",debounceName:"Change debounce",debounceDesc:"Wait this many seconds after edits stop before syncing.",concurrencyName:"Concurrent uploads",concurrencyDesc:"Keep this at 1-2 for NAS/Docker, local Docker, or smaller self-hosted deployments to avoid overloading Dify indexing. Adjust for Dify Cloud rate limits.",pollIndexName:"Check indexing status",pollIndexDesc:"Briefly query Dify indexing status after upload. This is slower but more informative.",sectionActions:"Actions & diagnostics",manualSync:"Sync now",manualSyncDesc:"Scan mapped Markdown files and sync changes immediately.",debugName:"Debug logs",debugDesc:"Print detailed sync logs in the developer console.",lastSync:"Last sync: {time}",noLastSync:"No completed sync yet.",lastSyncInitial:"Last sync time: --/--/--",activeEndpoint:"Active endpoint: {url}",noActiveEndpoint:"Active endpoint: no successful connection yet.",recentSyncInitial:"Recent sync: 0 tasks, elapsed 0ms",recentSyncSummary:"Recent sync: {source}, {total} tasks, {synced} synced, {skipped} skipped, {failed} failed, elapsed {elapsed}",recentSyncAddress:"Recent sync endpoint: {url}",recentSyncError:"Recent sync error: {message}",recentSyncFailures:"Recent failed files: {items}",noRecentSync:"No recent sync details yet.",recordsCount:"{count} file-to-knowledge-base sync records stored.",resetRecords:"Reset sync records",resetRecordsDesc:"Remote Dify documents are not deleted. The next sync will compare remote documents again.",resetRecordsDone:"Sync records reset.",hashMigrated:"Legacy sync records migrated.",localDeleted:"Local deletion recorded: {path}",progress:"{current}/{total} processing {name}",folderPickerEmpty:"There are no mappable folders or Markdown notes in this vault."}},R=class extends p.Plugin{constructor(){super(...arguments);this.syncRecords=new Map;this.legacyHashFileName="historyContentHash";this.syncProgressNotice=null;this.queueTimer=null;this.startupSyncTimer=null;this.periodicIntervalId=null;this.pendingPaths=new Set;this.isSyncing=!1}async onload(){await this.loadPluginData(),this.client=new I(this),this.client.setActiveBaseUrl(this.settings.connectionHealth.lastSuccessfulBaseUrl),this.statusBarItem=this.addStatusBarItem(),this.updateStatusBar(this.t("ready")),this.addRibbonIcon("sync",this.t("ribbon"),async()=>{await this.performSync("manual")}).addClass("dify-sync-ribbon-class"),this.addCommand({id:"dify-sync-manual",name:this.t("commandManual"),callback:async()=>{await this.performSync("manual")}}),this.addCommand({id:"dify-sync-toggle-auto",name:this.t("commandToggleAuto"),callback:async()=>{this.settings.autoSyncEnabled=!this.settings.autoSyncEnabled,await this.savePluginData(),this.setupAutoSync(),new p.Notice(this.settings.autoSyncEnabled?this.t("autoOn"):this.t("autoOff"))}}),this.settingTab=new q(this.app,this),this.addSettingTab(this.settingTab),this.app.workspace.onLayoutReady(async()=>{await this.migrateLegacyHashStorage(),this.registerVaultEvents(),this.setupAutoSync(),this.settings.autoSyncEnabled&&this.settings.syncOnStartup&&(this.startupSyncTimer=window.setTimeout(()=>{this.startupSyncTimer=null,this.queueFullSync("startup")},5e3))})}onunload(){this.queueTimer!==null&&window.clearTimeout(this.queueTimer),this.startupSyncTimer!==null&&window.clearTimeout(this.startupSyncTimer),this.periodicIntervalId!==null&&window.clearInterval(this.periodicIntervalId)}t(e,t){var s;let n=(O[((s=this.settings)==null?void 0:s.language)||"zh-CN"]||O["zh-CN"])[e]||O["zh-CN"][e]||e;return t&&Object.entries(t).forEach(([o,l])=>{n=n.replace(new RegExp(`\\{${o}\\}`,"g"),String(l))}),n}debug(...e){}getConnectionFailureMessage(e="unknown"){return this.t(Ce(e))}showConnectionError(e){let t=this.getConnectionFailureReason(e);new p.Notice(this.t("connectionFailed",{message:this.getConnectionFailureMessage(t)}),8e3).messageEl.addClass("dify-sync-error-notice")}getConnectionFailureReason(e){if(!this.hasCompleteConnectionSettings())return"missing_config";let t=De(e);return t||(e instanceof m?T(e):this.settings.connectionHealth.status==="failed"&&this.settings.connectionHealth.reason?this.settings.connectionHealth.reason:"unknown")}hasCompleteConnectionSettings(){return!!this.settings.difyApiKey.trim()&&this.getConfiguredBaseUrls().length>0}async loadPluginData(){let e=await this.loadData(),t=f(e)?e:{},i=f(t.settings)?t.settings:t;this.settings=this.normalizeSettings(i,!!e),this.syncRecords=new Map(Object.entries(we(t.syncRecords))),await this.savePluginData()}async savePluginData(){let e={};this.syncRecords.forEach((t,i)=>{e[i]=t}),await this.saveData({schemaVersion:2,settings:this.settings,syncRecords:e})}normalizeSettings(e,t=!1){let i=f(e)?e:{},n=Object.assign({},ve,i);if(n.schemaVersion=2,n.initialSetupCompleted=typeof i.initialSetupCompleted=="boolean"?i.initialSetupCompleted:t,n.language=n.language==="en"?"en":"zh-CN",n.endpointMode=["primary","lan","public","auto"].includes(n.endpointMode)?n.endpointMode:"primary",n.apiPathStyle=["auto","hyphen","underscore"].includes(n.apiPathStyle)?n.apiPathStyle:"auto",n.syncInterval=v(n.syncInterval,5,240,30),n.debounceSeconds=v(n.debounceSeconds,1,120,8),n.requestTimeoutSeconds=v(n.requestTimeoutSeconds,5,180,30),n.maxRetries=v(n.maxRetries,0,5,2),n.maxConcurrent=v(n.maxConcurrent,1,5,2),n.knowledgeBases=Array.isArray(n.knowledgeBases)?n.knowledgeBases:[],n.obsidianFolders=Array.isArray(n.obsidianFolders)?n.obsidianFolders:[],n.mappings=Array.isArray(n.mappings)?n.mappings:[],n.connectionHealth=ke(i.connectionHealth),n.mappings.length===0&&n.difyKnowledgeId){let s=n.obsidianFolders.length>0?n.obsidianFolders:[""];n.mappings=s.map(o=>({id:N(),folder:o,datasetIds:[n.difyKnowledgeId],enabled:!0}))}return n.mappings=n.mappings.map(s=>({id:s.id||N(),folder:M(s.folder||""),datasetIds:D((s.datasetIds||[]).map(o=>o.trim()).filter(Boolean)),enabled:s.enabled!==!1})),n}registerVaultEvents(){this.registerEvent(this.app.vault.on("create",e=>{this.handleVaultChange(e,"event")})),this.registerEvent(this.app.vault.on("modify",e=>{this.handleVaultChange(e,"event")})),this.registerEvent(this.app.vault.on("delete",async e=>{e instanceof p.TFile&&this.isMarkdownFile(e)&&await this.markLocalDeleted(e.path)})),this.registerEvent(this.app.vault.on("rename",async(e,t)=>{e instanceof p.TFile&&this.isMarkdownFile(e)&&(await this.migrateRenamedRecords(t,e.path),this.enqueueFile(e.path,"event"))}))}setupAutoSync(){this.periodicIntervalId!==null&&(window.clearInterval(this.periodicIntervalId),this.periodicIntervalId=null),!(!this.settings.autoSyncEnabled||!this.settings.periodicFullScanEnabled)&&(this.periodicIntervalId=window.setInterval(()=>{this.queueFullSync("interval")},this.settings.syncInterval*60*1e3),this.registerInterval(this.periodicIntervalId))}handleVaultChange(e,t){!this.settings.autoSyncEnabled||!this.settings.eventSyncEnabled||e instanceof p.TFile&&this.isMarkdownFile(e)&&this.enqueueFile(e.path,t)}enqueueFile(e,t){this.pendingPaths.add(e),this.scheduleQueueFlush(t)}queueFullSync(e){this.settings.autoSyncEnabled&&this.scheduleQueueFlush(e,!0)}scheduleQueueFlush(e,t=!1){this.queueTimer!==null&&window.clearTimeout(this.queueTimer),this.queueTimer=window.setTimeout(()=>{this.queueTimer=null;let i=t?void 0:Array.from(this.pendingPaths);this.pendingPaths.clear(),this.performSync(e,i).catch(n=>{let s=this.getConnectionFailureMessage(this.getConnectionFailureReason(n));this.updateStatusBar(this.t("syncFailedShort"),!0),this.showSyncError(this.t("syncFailed",{message:s}))})},this.settings.debounceSeconds*1e3)}async performSync(e="manual",t){if(!this.validateSettings(!0))return;if(this.isSyncing){t&&t.forEach(s=>this.pendingPaths.add(s)),new p.Notice(this.t("syncQueued"));return}this.isSyncing=!0,this.showSyncNotice(this.t("syncNotice")),this.updateStatusBar(this.t("syncing"));let i=new Date,n={synced:0,skipped:0,failed:0,total:0,syncedFiles:[],failedFiles:[]};try{let s=this.getCandidateFiles(t),o=await this.buildSyncPlan(s,n);if(n.total=o.length+n.skipped,o.length===0){this.settings.lastSyncTime=new Date().toISOString(),this.settings.recentSyncResult=this.createRecentSyncResult(e,i,n),this.markConnectionSuccessFromActive(),await this.savePluginData(),this.updateStatusBar(this.t("ready")),this.showSyncComplete(n);return}let l=await this.loadDocumentIndexes(o);await this.processTasks(o,l,n),this.settings.lastSyncTime=new Date().toISOString();let c=n.failed>0?this.t("syncComplete",ee(n)):void 0;this.settings.recentSyncResult=this.createRecentSyncResult(e,i,n,c),n.failed>0&&n.synced===0?this.markConnectionFailure(new m(c||this.t("syncFailedShort"))):this.markConnectionSuccessFromActive(),await this.savePluginData(),this.updateStatusBar(n.failed>0?this.t("syncFailedShort"):this.t("ready"),n.failed>0),this.showSyncComplete(n)}catch(s){let o=this.getConnectionFailureMessage(this.getConnectionFailureReason(s));this.settings.recentSyncResult=this.createRecentSyncResult(e,i,n,o),this.markConnectionFailure(s),await this.savePluginData(),this.updateStatusBar(this.t("syncFailedShort"),!0),this.showSyncError(this.t("syncFailed",{message:o}))}finally{if(this.isSyncing=!1,this.pendingPaths.size>0){let s=Array.from(this.pendingPaths);this.pendingPaths.clear(),window.setTimeout(()=>{this.performSync("event",s).catch(o=>{let l=this.getConnectionFailureMessage(this.getConnectionFailureReason(o));this.updateStatusBar(this.t("syncFailedShort"),!0),this.showSyncError(this.t("syncFailed",{message:l}))})},1e3)}}}getCandidateFiles(e){if(!e)return this.app.vault.getMarkdownFiles();let t=[];return D(e).forEach(i=>{let n=this.app.vault.getAbstractFileByPath(i);n instanceof p.TFile&&this.isMarkdownFile(n)&&t.push(n)}),t}async buildSyncPlan(e,t){let i=[];for(let n of e){let s=this.getDatasetIdsForPath(n.path);if(s.length===0)continue;let o=await this.app.vault.cachedRead(n),l=this.hashContent(o),c=this.makeRemoteName(n.path);for(let d of s){H(this.syncRecords,d,n.path,l,g=>this.makeRemoteName(g));let u=this.getRecordKey(d,n.path),h=this.syncRecords.get(u);if(h&&h.hash===l&&h.documentId&&!h.deletedLocal&&h.remoteName===c){t.skipped++;continue}i.push({file:n,content:o,hash:l,datasetId:d,remoteName:c,recordKey:u})}}return i}async loadDocumentIndexes(e){let t=D(e.map(n=>n.datasetId)),i=new Map;for(let n of t){let s=await this.client.listDocuments(n),o={byId:new Map,byName:new Map};s.forEach(l=>{o.byId.set(l.id,l),l.name&&o.byName.set(l.name,l)}),i.set(n,o)}return i}async processTasks(e,t,i){let n=0,s=0,o=Math.min(this.settings.maxConcurrent,e.length),l=async()=>{for(;n ${c.datasetId}`,u)}}};await Promise.all(Array.from({length:o},l))}async syncTask(e,t){if(!t)throw new Error(`Unable to load remote document index for dataset ${e.datasetId}`);let i=this.syncRecords.get(e.recordKey),n=i!=null&&i.documentId?t.byId.get(i.documentId):void 0,s=t.byName.get(e.remoteName),o=n||s,l;if(o!=null&&o.id)try{l=await this.client.updateDocumentByText(e.datasetId,o.id,e.remoteName,e.content)}catch(u){let h=u instanceof m?u:new m(w(u));if(h.status!==404)throw h;l=await this.client.createDocumentByText(e.datasetId,e.remoteName,e.content)}else l=await this.client.createDocumentByText(e.datasetId,e.remoteName,e.content);let c=be(l),d=(c==null?void 0:c.id)||(o==null?void 0:o.id)||(i==null?void 0:i.documentId);if(!d)throw new Error("Dify did not return a document id.");this.settings.pollIndexStatus&&l.batch&&await this.pollIndexStatus(e.datasetId,l.batch),this.syncRecords.set(e.recordKey,{filePath:e.file.path,datasetId:e.datasetId,documentId:d,remoteName:e.remoteName,hash:e.hash,lastModified:e.file.stat.mtime,lastSyncedAt:new Date().toISOString(),lastBatch:l.batch}),t.byId.set(d,{id:d,name:e.remoteName}),t.byName.set(e.remoteName,{id:d,name:e.remoteName})}async pollIndexStatus(e,t){for(let i=0;i<3;i++)try{await Pe(1200),await this.client.getIndexingStatus(e,t);return}catch(n){this.debug("Indexing status check failed",w(n))}}async refreshKnowledgeBases(e=!0){if(!this.hasCompleteConnectionSettings())throw this.markConnectionMissingConfig(),await this.savePluginData(),new m(this.getConnectionFailureMessage("missing_config"),void 0,void 0,"missing_config");let t=await this.client.probeConnection();if(this.applyConnectionProbe(t),t.status!=="connected")throw await this.savePluginData(),new m(t.error||this.getConnectionFailureMessage(t.reason),t.statusCode,void 0,t.reason||"unknown");let i=t.datasets;return this.settings.knowledgeBases=i,this.settings.lastDatasetRefresh=t.checkedAt,await this.savePluginData(),e&&new p.Notice(i.length>0?this.t("datasetsRefreshed",{count:i.length}):this.t("datasetsEmpty")),i}async testConnection(){try{let e=await this.refreshKnowledgeBases(!1);this.settings.initialSetupCompleted=!0,await this.savePluginData(),new p.Notice(this.t("connectionOk",{count:e.length,url:this.settings.connectionHealth.activeBaseUrl||this.settings.connectionHealth.lastSuccessfulBaseUrl||b(this.settings.difyApiUrl)})),this.settingTab.refreshSettingsView()}catch(e){this.showConnectionError(e)}}applyConnectionProbe(e){var n;let t=Ae(e),i=e.reason||(t==null?void 0:t.reason);this.settings.connectionHealth={status:e.status,checkedAt:e.checkedAt,activeBaseUrl:e.activeBaseUrl,lastSuccessfulBaseUrl:e.lastSuccessfulBaseUrl||this.settings.connectionHealth.lastSuccessfulBaseUrl,datasetCount:e.datasetCount,latencyMs:e.latencyMs,error:e.status==="failed"?this.getConnectionFailureMessage(i||"unknown"):e.error,reason:i,statusCode:(n=e.statusCode)!=null?n:t==null?void 0:t.statusCode}}markConnectionSuccessFromActive(){let e=this.client.getActiveBaseUrl();e&&(this.settings.connectionHealth={...this.settings.connectionHealth,status:"connected",checkedAt:new Date().toISOString(),activeBaseUrl:e,lastSuccessfulBaseUrl:e,error:void 0,reason:void 0,statusCode:void 0})}markConnectionFailure(e){let t=e instanceof m?e:new m(w(e)),i=T(t);this.settings.connectionHealth={...this.settings.connectionHealth,status:"failed",checkedAt:new Date().toISOString(),activeBaseUrl:"",error:this.getConnectionFailureMessage(i),reason:i,statusCode:t.status}}markConnectionMissingConfig(){this.client.clearActiveBaseUrl(),this.settings.connectionHealth={...this.settings.connectionHealth,status:"missing_config",checkedAt:new Date().toISOString(),activeBaseUrl:"",error:this.getConnectionFailureMessage("missing_config"),reason:"missing_config",statusCode:void 0}}invalidateConnectionState(){this.client.clearActiveBaseUrl(),this.settings.connectionHealth={...this.settings.connectionHealth,status:this.settings.difyApiKey.trim()&&this.getConfiguredBaseUrls().length>0?"unknown":"missing_config",checkedAt:"",activeBaseUrl:"",error:void 0,reason:void 0,statusCode:void 0}}createRecentSyncResult(e,t,i,n){let s=new Date;return{source:e,status:n?"failed":"success",startedAt:t.toISOString(),finishedAt:s.toISOString(),elapsedMs:Math.max(0,s.getTime()-t.getTime()),activeBaseUrl:this.client.getActiveBaseUrl(),synced:i.synced,skipped:i.skipped,failed:i.failed,total:i.total,syncedFiles:i.syncedFiles.slice(0,20),failedFiles:i.failedFiles.slice(0,20),error:n}}getDatasetIdsForPath(e){return G(e,this.settings.mappings)}makeRemoteName(e){return e}getRecordKey(e,t){return k(e,t)}hashContent(e){let t=2166136261;for(let i=0;i>>0).toString(16)}async markLocalDeleted(e){let t=!1;this.syncRecords.forEach((i,n)=>{i.filePath===e&&(this.syncRecords.set(n,{...i,deletedLocal:!0,lastSyncedAt:new Date().toISOString()}),t=!0)}),t&&(await this.savePluginData(),new p.Notice(this.t("localDeleted",{path:e})))}async migrateRenamedRecords(e,t){B(this.syncRecords,e,t,n=>this.makeRemoteName(n))&&await this.savePluginData()}async migrateLegacyHashStorage(){if(!(this.syncRecords.size>0||this.settings.mappings.length===0))try{let t=this.app.vault.getAbstractFileByPath(this.legacyHashFileName);if(!(t instanceof p.TFile))return;let i=await this.app.vault.read(t),n=A(i);if(!f(n))return;Object.entries(n).forEach(([s,o])=>{let l=f(o)?o:{};this.getDatasetIdsForPath(s).forEach(d=>{this.syncRecords.set(this.getRecordKey(d,s),{filePath:s,datasetId:d,remoteName:this.makeRemoteName(s),hash:S(l,"hash")||"",lastModified:C(l,"lastModified")||0,lastSyncedAt:new Date().toISOString()})})}),this.syncRecords.size>0&&(await this.savePluginData(),new p.Notice(this.t("hashMigrated")))}catch(t){this.debug("Legacy hash migration failed",w(t))}}validateSettings(e){return this.validateConnectionSettings(e)&&this.validateMappingSettings(e)}validateConnectionSettings(e){return this.hasCompleteConnectionSettings()?!0:(e&&this.showConnectionError(new m(this.getConnectionFailureMessage("missing_config"),void 0,void 0,"missing_config")),!1)}validateMappingSettings(e){return this.settings.mappings.some(i=>i.enabled&&i.datasetIds.length>0)?!0:(e&&new p.Notice(this.t("validationMapping")),!1)}getConfiguredBaseUrls(){return D([b(this.settings.difyApiUrl)].filter(Boolean))}getActiveBaseUrl(){return this.client.getActiveBaseUrl()}isMarkdownFile(e){return e.extension.toLowerCase()==="md"}updateStatusBar(e,t=!1){this.statusBarItem.setText(`Dify: ${e}`),this.statusBarItem.toggleClass("dify-sync-error",t),this.statusBarItem.toggleClass("dify-sync-syncing",e===this.t("syncing"))}showSyncProgress(e,t,i){let n=i>0?Math.round(t/i*100):0,s=`${e} -${this.createProgressBar(n)} ${n}% (${t}/${i})`;this.showPersistentNotice(s)}showSyncNotice(e){this.showPersistentNotice(e)}showPersistentNotice(e){if(!this.syncProgressNotice){this.syncProgressNotice=new p.Notice(e,0),this.syncProgressNotice.messageEl.addClass("dify-sync-progress");return}this.syncProgressNotice.messageEl.textContent=e}showSyncComplete(e){this.hideProgressNotice();let t=e.total===0?this.t("syncNoChanges"):this.t("syncComplete",ee(e));new p.Notice(t,5e3).messageEl.addClass("dify-sync-complete")}showSyncError(e){this.hideProgressNotice(),new p.Notice(e,8e3).messageEl.addClass("dify-sync-error-notice")}hideProgressNotice(){this.syncProgressNotice&&(this.syncProgressNotice.hide(),this.syncProgressNotice=null)}createProgressBar(e){let i=Math.round(e/100*20);return`[${"\u2588".repeat(i)}${"\u2591".repeat(20-i)}]`}getDatasetLabel(e){let t=this.settings.knowledgeBases.find(i=>i.id===e);return t?`${t.name} (${t.id})`:`${this.t("datasetUnknown")} (${e})`}getDatasetName(e){let t=this.settings.knowledgeBases.find(i=>i.id===e);return(t==null?void 0:t.name)||e}},q=class extends p.PluginSettingTab{constructor(e,t){super(e,t);this.mainMappingPage=0;this.mainMappingPageSize=4;this.outerScrollHostClass="dify-sync-outer-scroll-host";this.innerScrollHostClass="dify-sync-inner-scroll-host";this.outerScrollHosts=new Set;this.scrollbarMarkToken=0;this.renderMode="legacy";this.plugin=t}getSettingDefinitions(){return[{name:this.plugin.t("settingsTitle"),desc:this.plugin.t("settingsSubtitle"),render:e=>(this.renderMode="declarative",this.renderSettingsApp(e.settingEl),()=>{this.scrollbarMarkToken++,this.clearScrollbarHosts()})}]}display(){this.renderLegacySettingsView()}refreshSettingsView(){if(this.renderMode==="declarative"){this.update();return}this.renderLegacySettingsView()}renderLegacySettingsView(){this.renderMode="legacy",this.scrollbarMarkToken++,this.clearScrollbarHosts();let{containerEl:e}=this;e.empty(),this.renderSettingsApp(e)}renderSettingsApp(e){e.addClass("dify-sync-settings");let t=e.createDiv("app"),n=t.createEl("main",{cls:"main"}).createDiv("settings");this.renderTopbar(n),this.renderSyncSummary(n);let s=n.createDiv("dify-sync-content");this.renderConnectionSection(s),this.renderMappingSection(s),this.renderSyncSettingsSection(s),t.createDiv({cls:"toast-region",attr:{"aria-live":"polite","aria-atomic":"true"}}),this.scheduleScrollbarHostMarking(e)}hide(){this.scrollbarMarkToken++,this.clearScrollbarHosts(),super.hide()}scheduleScrollbarHostMarking(e){let t=++this.scrollbarMarkToken;this.clearScrollbarHosts(),window.requestAnimationFrame(()=>{window.requestAnimationFrame(()=>{t===this.scrollbarMarkToken&&e.isConnected&&this.markScrollbarHosts(e)})})}markScrollbarHosts(e){this.clearScrollbarHosts();let t=this.findScrollableAncestors(e);if(t.length===0)return;this.innerScrollHost=e,e.addClass(this.innerScrollHostClass),t.forEach(s=>this.addOuterScrollHost(s));let i=t[0],n=s=>this.handleSettingsWheel(s,e,i);e.addEventListener("wheel",n,{passive:!1}),this.wheelCleanup=()=>e.removeEventListener("wheel",n)}findScrollableAncestors(e){let t=[],i=e.parentElement;for(;i;)this.isVerticalScrollHost(i)&&t.push(i),i=i.parentElement;return t}isVerticalScrollHost(e){let t=window.getComputedStyle(e).overflowY;return e.scrollHeight>e.clientHeight+1&&["auto","scroll","overlay"].includes(t)}addOuterScrollHost(e){e.addClass(this.outerScrollHostClass),this.outerScrollHosts.add(e)}handleSettingsWheel(e,t,i){if(e.defaultPrevented||e.deltaY===0||this.targetCanScrollWithin(e.target,t,e.deltaY))return;let n=i.scrollTop;i.scrollTop+=e.deltaY,i.scrollTop!==n&&(e.preventDefault(),e.stopPropagation())}targetCanScrollWithin(e,t,i){let n=e instanceof HTMLElement?e:null;for(;n&&n!==t;){if(!this.outerScrollHosts.has(n)&&this.elementCanScrollInDirection(n,i))return!0;n=n.parentElement}return!1}elementCanScrollInDirection(e,t){return this.isVerticalScrollHost(e)?t<0?e.scrollTop>0:e.scrollTop+e.clientHeighti.removeClass(this.outerScrollHostClass)),this.outerScrollHosts.clear(),(t=this.innerScrollHost)==null||t.removeClass(this.innerScrollHostClass),this.innerScrollHost=void 0}renderTopbar(e){let t=e.createEl("header",{cls:"topbar"}),i=t.createDiv("title-line");new p.Setting(i).setName(this.plugin.t("settingsTitle")).setHeading().nameEl.addClass("heading-text"),i.createEl("p",{text:this.plugin.t("settingsSubtitle"),cls:"settings-subtitle"});let s=t.createDiv("top-actions"),o=new p.ExtraButtonComponent(s).setIcon("languages").setTooltip(this.plugin.t("languageName")),l=o.extraSettingsEl;o.onClick(()=>this.openLanguageMenu(l)),l.addClass("language-button"),l.setAttr("aria-label",this.plugin.t("languageName")),l.setAttr("title",this.plugin.t("languageName")),l.setAttr("data-action","open-language-menu");let c=new p.ExtraButtonComponent(s).setIcon("help-circle").setTooltip(this.plugin.t("openHelp")).onClick(()=>{new $(this.app,this.plugin).open()}).extraSettingsEl;c.addClass("help-button"),c.setAttr("aria-label",this.plugin.t("openHelp")),c.setAttr("title",this.plugin.t("openHelp")),c.setAttr("data-action","open-help");let d=s.createEl("a",{cls:"github-link",href:"https://github.com/BeatsAD/Vault2Dify"});d.setAttr("target","_blank"),d.setAttr("rel","noopener noreferrer"),d.setAttr("aria-label",this.plugin.t("openGithub")),d.setAttr("title","GitHub"),(0,p.setIcon)(d,"github")}openLanguageMenu(e){let t=new p.Menu;[{value:"zh-CN",label:this.plugin.t("languageChinese")},{value:"en",label:this.plugin.t("languageEnglish")}].forEach(s=>{t.addItem(o=>{o.setTitle(s.label).setChecked(this.plugin.settings.language===s.value).onClick(()=>{this.plugin.settings.language!==s.value&&(async()=>(this.plugin.settings.language=s.value,await this.plugin.savePluginData(),this.plugin.updateStatusBar(this.plugin.t("ready")),this.refreshSettingsView()))()})})});let n=e.getBoundingClientRect();t.showAtPosition({x:n.left,y:n.bottom+4,width:n.width})}renderSyncSummary(e){let t=Z({connectionHealth:this.plugin.settings.connectionHealth,recentSyncResult:this.plugin.settings.recentSyncResult}),i={total:this.plugin.t("metricTotal"),synced:this.plugin.t("metricSynced"),skipped:this.plugin.t("metricSkipped"),failed:this.plugin.t("metricFailed")},n=e.createDiv("sync-summary");n.setAttr("aria-label",this.plugin.t("sectionDiagnostics")),t.metrics.forEach(s=>{let o=n.createDiv("metric");o.createDiv({text:i[s.key],cls:"metric-label"}),o.createDiv({text:String(s.value),cls:s.tone==="default"?"metric-value":`metric-value ${s.tone}`})})}renderConnectionSection(e){let{card:t}=this.createReviewSection(e,"connection-title",this.plugin.t("settingsReviewConnectionTitle")),i,n=this.createNativeSetting(t,"setting-row");n.controlEl.addClass("has-secret-toggle"),n.setName(this.createSettingName(this.plugin.t("apiKeyName"),{required:!0})).setTooltip(this.plugin.t("apiKeyDesc")).addText(h=>{i=h.setPlaceholder(this.plugin.t("apiKeyPlaceholder")).setValue(this.plugin.settings.difyApiKey).onChange(g=>{this.plugin.settings.difyApiKey=g.trim(),this.plugin.invalidateConnectionState(),this.plugin.savePluginData()}).inputEl,i.id="dify-api-key",i.type="password",i.setAttr("aria-label",this.plugin.t("apiKeyName"))}).addExtraButton(h=>{let g=h.setIcon("eye").setTooltip(this.plugin.t("showApiKey")).onClick(()=>{let y=i.type==="password";i.type=y?"text":"password",h.setIcon(y?"eye-off":"eye");let E=this.plugin.t(y?"hideApiKey":"showApiKey");h.setTooltip(E),g.setAttr("aria-label",E),g.setAttr("title",E),g.setAttr("aria-pressed",String(y))}).extraSettingsEl;g.addClass("secret-toggle"),g.setAttr("aria-label",this.plugin.t("showApiKey")),g.setAttr("title",this.plugin.t("showApiKey")),g.setAttr("aria-controls","dify-api-key"),g.setAttr("aria-pressed","false")}),this.createNativeSetting(t,"setting-row").setName(this.createSettingName(this.plugin.t("apiUrlName"),{required:!0})).setTooltip(this.plugin.t("apiUrlDesc")).addText(h=>{h.setPlaceholder(this.plugin.t("urlPlaceholder")).setValue(this.plugin.settings.difyApiUrl).onChange(y=>{this.plugin.settings.difyApiUrl=y.trim(),this.plugin.invalidateConnectionState(),this.plugin.savePluginData()}).inputEl.setAttr("aria-label",this.plugin.t("apiUrlName"))});let s=t.createDiv("dataset-refresh-row"),o=s.createDiv("status-line");o.setAttr("aria-label",this.plugin.t("healthConnection")),o.createSpan({cls:`status-dot ${this.getConnectionStatusToneClass()}`}),o.createEl("strong",{text:this.getConnectionStatusLabel()});let l=s.createDiv("connection-action-buttons"),c=new p.ButtonComponent(l).setButtonText(this.plugin.t("clearConnectionConfig")).setClass("native-action-button").onClick(()=>{(async()=>(this.plugin.settings.difyApiKey="",this.plugin.settings.difyApiUrl="",this.plugin.settings.lanApiUrl="",this.plugin.settings.publicApiUrl="",this.plugin.invalidateConnectionState(),await this.plugin.savePluginData(),this.refreshSettingsView()))()});c.buttonEl.setAttr("data-action","clear-connection-config"),c.buttonEl.setAttr("aria-label",this.plugin.t("clearConnectionConfig"));let d;d=new p.ButtonComponent(l).setButtonText(this.plugin.t("testConnection")).setClass("native-action-button").onClick(()=>{d.setDisabled(!0),d.setButtonText(this.plugin.t("syncing")),(async()=>{try{await this.plugin.testConnection()}finally{d.setDisabled(!1),d.setButtonText(this.plugin.t("testConnection")),this.refreshSettingsView()}})()}),d.buttonEl.setAttr("data-action","test-connection"),d.buttonEl.setAttr("aria-label",this.plugin.t("testConnection"));let u=s.createDiv({cls:"callout",attr:{"data-role":"connection-result"}});u.hidden=!0,u.createEl("strong",{text:this.getConnectionStatusLabel()}),u.createSpan({text:this.getConnectionNote()})}renderMappingSection(e){let{card:t}=this.createReviewSection(e,"mapping-title",this.plugin.t("settingsReviewMappingTitle"),c=>{let d=new p.ButtonComponent(c).setButtonText(this.plugin.t("addMapping")).setCta().onClick(()=>{new L(this.app,this.plugin,void 0,async u=>{this.plugin.settings.mappings.push(u),await this.plugin.savePluginData(),new p.Notice(this.plugin.t("mappingSaved")),this.refreshSettingsView()}).open()});d.buttonEl.setAttr("data-action","add-mapping"),d.buttonEl.setAttr("aria-label",this.plugin.t("addMapping"))}),i=t.createDiv({cls:"mapping-table main-mapping-table",attr:{role:"table","aria-label":this.plugin.t("sectionMapping")}}),n=i.createDiv({cls:"mapping-header",attr:{role:"row"}});n.createDiv({text:this.plugin.t("mappingIndex")}),n.createDiv({text:this.plugin.t("mappingFolderColumn")}),n.createDiv({text:this.plugin.t("mappingDatasetColumn")}),n.createDiv({text:this.plugin.t("mappingStatusColumn")}),n.createDiv({text:this.plugin.t("mappingActionColumn")});let s=this.plugin.settings.mappings,o=Math.max(0,Math.ceil(s.length/this.mainMappingPageSize)-1);this.mainMappingPage=Math.min(this.mainMappingPage,o),s.length===0&&i.createDiv({text:this.plugin.t("noMappings"),cls:"main-mapping-empty"});let l=this.mainMappingPage*this.mainMappingPageSize;s.slice(l,l+this.mainMappingPageSize).forEach((c,d)=>{this.renderMappingRow(i,c,l+d)}),this.renderMainMappingPagination(t,this.plugin.settings.mappings.length)}renderMainMappingPagination(e,t){let i=e.createDiv({cls:"main-mapping-pagination mapping-pagination",attr:{"data-role":"main-mapping-pagination"}}),n=Math.max(1,Math.ceil(t/this.mainMappingPageSize)),s=new p.ButtonComponent(i).setButtonText("\u2039"),o=t===0?0:this.mainMappingPage*this.mainMappingPageSize+1,l=t===0?0:Math.min(t,(this.mainMappingPage+1)*this.mainMappingPageSize);i.createSpan({text:t===0?this.plugin.t("mappingPageInfoEmpty"):`${o}-${l} / ${t}`,attr:{"data-role":"main-mapping-page-info"}});let c=new p.ButtonComponent(i).setButtonText("\u203A");s.setDisabled(t===0||this.mainMappingPage===0),c.setDisabled(t===0||this.mainMappingPage>=n-1),s.onClick(()=>{this.mainMappingPage=Math.max(0,this.mainMappingPage-1),this.refreshSettingsView()}),c.onClick(()=>{this.mainMappingPage=Math.min(n-1,this.mainMappingPage+1),this.refreshSettingsView()})}renderMappingRow(e,t,i){let n=e.createDiv({cls:"mapping-row",attr:{role:"row"}});se(n,this.plugin,t,i,"toggle-mapping-status",async()=>{t.enabled=!t.enabled,await this.plugin.savePluginData(),this.refreshSettingsView()},s=>{let o=new p.ExtraButtonComponent(s).setIcon("pencil").setTooltip(this.plugin.t("edit")).onClick(()=>{new L(this.app,this.plugin,t,async c=>{this.plugin.settings.mappings=this.plugin.settings.mappings.map(d=>d.id===c.id?c:d),await this.plugin.savePluginData(),this.refreshSettingsView()}).open()}).extraSettingsEl;o.addClass("icon"),o.setAttr("data-action","edit-mapping"),o.setAttr("aria-label",this.plugin.t("edit")),o.setAttr("title",this.plugin.t("edit"));let l=new p.ExtraButtonComponent(s).setIcon("trash-2").setTooltip(this.plugin.t("delete")).onClick(()=>{new _(this.app,this.plugin.t("deleteMappingTitle"),this.plugin.t("deleteMappingDesc"),this.plugin.t("deleteMappingConfirm"),this.plugin.t("cancel"),async()=>{this.plugin.settings.mappings=J(this.plugin.settings.mappings,t.id),await this.plugin.savePluginData(),this.refreshSettingsView()},{folder:t.folder||this.plugin.t("rootFolder"),datasets:t.datasetIds.map(c=>this.plugin.getDatasetName(c)).join("\u3001")||this.plugin.t("datasetMissing")},c=>this.plugin.t(c)).open()}).extraSettingsEl;l.addClass("icon"),l.addClass("danger"),l.setAttr("data-action","delete-mapping"),l.setAttr("aria-label",this.plugin.t("delete")),l.setAttr("title",this.plugin.t("delete"))})}renderSyncSettingsSection(e){let{card:t}=this.createReviewSection(e,"auto-title",this.plugin.t("settingsReviewSyncTitle"));this.createNativeSetting(t,"setting-row","switch-row").setName(this.createSettingName(this.plugin.t("sectionAuto"))).addToggle(y=>{y.setValue(this.plugin.settings.autoSyncEnabled).onChange(E=>{(async()=>(this.plugin.settings.autoSyncEnabled=E,await this.plugin.savePluginData(),this.plugin.setupAutoSync(),this.refreshSettingsView()))()}),y.toggleEl.setAttr("data-action","toggle-auto-sync")});let i=t.createDiv({attr:{id:"advanced-sync"}});this.createAdvancedSelect(i,this.plugin.t("syncAfterChange"),this.plugin.settings.debounceSeconds,[[8,this.plugin.t("debounceOption8")],[15,this.plugin.t("debounceOption15")],[30,this.plugin.t("debounceOption30")]],async y=>{this.plugin.settings.debounceSeconds=y,await this.plugin.savePluginData()},"debounce"),this.createAdvancedSelect(i,this.plugin.t("fullScanCheck"),this.plugin.settings.periodicFullScanEnabled?this.plugin.settings.syncInterval:0,[[30,this.plugin.t("fullScanOption30")],[60,this.plugin.t("fullScanOption60")],[0,this.plugin.t("fullScanOptionOff")]],async y=>{this.plugin.settings.periodicFullScanEnabled=y>0,y>0&&(this.plugin.settings.syncInterval=y),await this.plugin.savePluginData(),this.plugin.setupAutoSync()},"interval"),this.createAdvancedSelect(i,this.plugin.t("startupSyncShort"),this.plugin.settings.syncOnStartup,[[!0,this.plugin.t("optionOn")],[!1,this.plugin.t("optionOff")]],async y=>{this.plugin.settings.syncOnStartup=y,await this.plugin.savePluginData()},"startup-sync"),this.createAdvancedSelect(i,this.plugin.t("concurrentUpload"),this.plugin.settings.maxConcurrent,[[2,this.plugin.t("concurrencyOption2")],[4,this.plugin.t("concurrencyOption4")]],async y=>{this.plugin.settings.maxConcurrent=v(y,1,5,2),await this.plugin.savePluginData()},"concurrency");let n=t.createEl("details",{cls:"diagnostic-fold"});n.open=!0;let s=n.createEl("summary");s.createSpan({text:this.plugin.t("sectionDiagnostics")}),s.createSpan({text:`Dify: ${this.plugin.t("ready")}`});let o=n.createDiv("diagnostic-body"),l=this.plugin.settings.lastSyncTime?this.plugin.t("lastSync",{time:new Date(this.plugin.settings.lastSyncTime).toLocaleString()}):this.plugin.t("lastSyncInitial");o.createDiv({text:l,cls:"dify-sync-diagnostic-line"}),this.renderRecentSyncResult(o);let c=o.createDiv("sync-actions");new p.ButtonComponent(c).setButtonText(this.plugin.settings.debugLogging?`${this.plugin.t("debugName")} \xB7 ${this.plugin.t("enabled")}`:this.plugin.t("debugName")).onClick(()=>{(async()=>(this.plugin.settings.debugLogging=!this.plugin.settings.debugLogging,await this.plugin.savePluginData(),this.refreshSettingsView()))()}).buttonEl.setAttr("data-action","toggle-debug-logging"),new p.ButtonComponent(c).setButtonText(this.plugin.t("resetRecords")).onClick(()=>{(async()=>(this.plugin.syncRecords.clear(),await this.plugin.savePluginData(),new p.Notice(this.plugin.t("resetRecordsDone")),this.refreshSettingsView()))()}).buttonEl.setAttr("data-action","reset-sync-records");let h=t.createDiv("sync-actions manual-sync-actions"),g;g=new p.ButtonComponent(h).setButtonText(this.plugin.t("manualSync")).setCta().onClick(()=>{g.setDisabled(!0),g.setButtonText(this.plugin.t("syncing")),(async()=>{try{await this.plugin.performSync("manual")}finally{g.setDisabled(!1),g.setButtonText(this.plugin.t("manualSync")),this.refreshSettingsView()}})()}),g.buttonEl.setAttr("data-action","sync-now"),g.buttonEl.setAttr("aria-label",this.plugin.t("manualSync"))}renderRecentSyncResult(e){let t=this.plugin.settings.recentSyncResult;if(!t){e.createDiv({text:this.plugin.t("recentSyncInitial"),cls:"dify-sync-diagnostic-line"});return}e.createDiv({text:this.plugin.t("recentSyncSummary",{source:t.source,total:t.total,synced:t.synced,skipped:t.skipped,failed:t.failed,elapsed:Te(t.elapsedMs)}),cls:"dify-sync-diagnostic-line"}),t.error&&e.createDiv({text:this.plugin.t("recentSyncError",{message:t.error}),cls:"dify-sync-diagnostic-line dify-sync-diagnostic-error"})}createReviewSection(e,t,i,n){let s=e.createEl("section",{cls:"section",attr:{"aria-labelledby":t}}),o=s.createDiv("section-head"),l=o.createDiv("heading-line"),c=new p.Setting(l).setName(i).setHeading();return c.nameEl.addClass("heading-text"),c.nameEl.id=t,n&&n(o),{card:s.createDiv("section-card")}}createSettingName(e,t={}){let i=this.containerEl.doc,n=i.createDocumentFragment(),s=i.createElement("span");if(s.className="setting-label",s.textContent=e,n.appendChild(s),t.required){let o=i.createElement("span");o.className="required-marker",o.textContent="*",o.setAttribute("aria-label","Required"),n.appendChild(o)}return n}createSettingRow(e,t,i,n={}){return{control:this.createNativeSetting(e,"setting-row").setName(this.createSettingName(t,n)).setDesc(i).setTooltip(i).controlEl}}createTextInput(e,t,i,n){let s=e.createEl("input",{attr:{type:"text",placeholder:i,"aria-label":t}});return s.value=n,s}createAdvancedText(e,t,i,n,s,o=this.plugin.t("urlPlaceholder"),l={}){this.createNativeSetting(e,"advanced-field").setName(this.createSettingName(t,l)).addText(c=>{let d=c.setPlaceholder(o).setValue(i).onChange(u=>{n(u.trim())}).inputEl;d.id=s})}createAdvancedNumber(e,t,i,n,s,o,l){let c=e.createDiv("advanced-field");c.createEl("label",{text:t,attr:{for:l}});let d=c.createEl("input",{attr:{id:l,type:"number",min:String(n),max:String(s)}});d.value=String(i),d.addEventListener("change",()=>{o(Number(d.value)).catch(u=>{new p.Notice(w(u))})})}createAdvancedSelect(e,t,i,n,s,o){let l=new Map(n.map(([d])=>[String(d),d])),c=Object.fromEntries(n.map(([d,u])=>[String(d),u]));this.createNativeSetting(e,"advanced-field").setName(this.createSettingName(t)).addDropdown(d=>{d.addOptions(c).setValue(String(i)).onChange(u=>{let h=l.get(u);h!==void 0&&s(h)}),d.selectEl.id=`${o}-select`,d.selectEl.setAttr("aria-label",t)})}createNativeSetting(e,...t){let i=new p.Setting(e).setClass("native-setting-row");return t.forEach(n=>i.settingEl.addClass(n)),i}createHelpIcon(e,t){let i=e.createSpan({text:"?",cls:"help-icon"});i.setAttr("tabindex","0"),i.setAttr("aria-label",t),i.setAttr("data-tooltip",t)}getConnectionStatusLabel(){let e=this.plugin.settings.connectionHealth.status;return!this.plugin.settings.difyApiKey.trim()||this.plugin.getConfiguredBaseUrls().length===0?this.plugin.t("connectionMissing"):e==="connected"?this.plugin.t("connectionConnected"):e==="failed"?this.plugin.t("connectionFailedState"):e==="missing_config"?this.plugin.t("connectionMissing"):this.plugin.t("connectionUnknown")}getConnectionStatusToneClass(){let e=this.plugin.settings.connectionHealth.status;return!this.plugin.settings.difyApiKey.trim()||this.plugin.getConfiguredBaseUrls().length===0?"is-muted":e==="connected"?"is-success":e==="failed"?"is-error":"is-muted"}getActiveUrlLabel(){let e=this.plugin.settings.connectionHealth;return te(e.activeBaseUrl||e.lastSuccessfulBaseUrl||b(this.plugin.settings.difyApiUrl)||"-")}getConnectionNote(){let e=this.plugin.settings.connectionHealth;return!this.plugin.settings.difyApiKey.trim()||this.plugin.getConfiguredBaseUrls().length===0?this.plugin.t("connectionNoteMissing"):e.status==="connected"?this.plugin.t("connectionNoteSuccess",{count:e.datasetCount,url:te(e.activeBaseUrl||e.lastSuccessfulBaseUrl)}):e.status==="failed"&&e.error?this.plugin.t("connectionNoteFailed",{message:e.error}):this.plugin.t("connectionNoteIdle")}},$=class extends p.Modal{constructor(e,t){super(e);this.plugin=t}onOpen(){let{contentEl:e}=this;e.empty(),this.modalEl.addClass("dify-sync-modal-shell"),this.modalEl.addClass("help-modal-shell"),e.addClass("dify-sync-modal"),e.addClass("help-modal"),e.createDiv("modal-head").createDiv("modal-title-line").createEl("h2",{text:this.plugin.t("helpModalTitle"),attr:{id:"help-modal-title"}});let n=e.createDiv({cls:"help-modal-body",attr:{role:"document","aria-labelledby":"help-modal-title"}});this.getHelpSections().forEach(s=>this.renderHelpSection(n,s))}getHelpSections(){return[{titleKey:"helpQuickStartTitle",paragraphKeys:["helpQuickStartBody"],orderedListKeys:["helpQuickStartList"]},{titleKey:"helpApiUrlTitle",paragraphKeys:["helpApiUrlBody"]},{titleKey:"helpApiKeyTitle",paragraphKeys:["helpApiKeyBody"]},{titleKey:"helpMappingTitle",paragraphKeys:["helpMappingBody"],listKeys:["helpMappingList"]},{titleKey:"helpSyncModesTitle",listKeys:["helpSyncModesList"]},{titleKey:"helpSyncRecommendedTitle",listKeys:["helpSyncRecommendedList"]},{titleKey:"helpSyncRulesTitle",paragraphKeys:["helpSyncRulesBody"],orderedListKeys:["helpSyncRulesList"]},{titleKey:"helpTroubleshootingTitle",orderedListKeys:["helpTroubleshootingList"]},{titleKey:"helpCommonErrorsTitle",errorListKeys:["helpCommonErrorsList"]},{titleKey:"helpDatasetsTitle",listKeys:["helpDatasetsList"]},{titleKey:"helpFilesNotSyncingTitle",listKeys:["helpFilesNotSyncingList"]},{titleKey:"helpSlowSyncTitle",listKeys:["helpSlowSyncList"]},{titleKey:"helpDuplicateDocsTitle",paragraphKeys:["helpDuplicateDocsBody"],orderedListKeys:["helpDuplicateDocsList"]},{titleKey:"helpDebugTitle",paragraphKeys:["helpDebugBody"]},{titleKey:"helpPrivacyTitle",paragraphKeys:["helpPrivacyBody"]}]}renderHelpSection(e,t){let i=e.createEl("section",{cls:"help-section"});i.createEl("h3",{text:this.plugin.t(t.titleKey)}),(t.paragraphKeys||[]).forEach(n=>{this.splitHelpLines(this.plugin.t(n,{configDir:this.app.vault.configDir})).forEach(s=>{i.createEl("p",{text:s})})}),(t.orderedListKeys||[]).forEach(n=>{this.renderHelpList(i,this.splitHelpLines(this.plugin.t(n,{configDir:this.app.vault.configDir})),!0)}),(t.listKeys||[]).forEach(n=>{this.renderHelpList(i,this.splitHelpLines(this.plugin.t(n,{configDir:this.app.vault.configDir})),!1)}),(t.errorListKeys||[]).forEach(n=>{this.renderHelpErrorList(i,this.splitHelpLines(this.plugin.t(n,{configDir:this.app.vault.configDir})))})}renderHelpList(e,t,i){let n=e.createEl(i?"ol":"ul",{cls:"help-list"});t.forEach(s=>n.createEl("li",{text:s}))}renderHelpErrorList(e,t){let i=e.createDiv("help-error-list");t.forEach(n=>{let s=n.includes("\uFF1A")?"\uFF1A":":",[o,...l]=n.split(s),c=i.createDiv("help-error-item");c.createEl("strong",{text:o.trim()});let d=l.join(s).trim();d&&c.createSpan({text:d})})}splitHelpLines(e){return e.split(` +${this.createProgressBar(n)} ${n}% (${t}/${i})`;this.showPersistentNotice(s)}showSyncNotice(e){this.showPersistentNotice(e)}showPersistentNotice(e){if(!this.syncProgressNotice){this.syncProgressNotice=new p.Notice(e,0),this.syncProgressNotice.messageEl.addClass("dify-sync-progress");return}this.syncProgressNotice.messageEl.textContent=e}showSyncComplete(e){this.hideProgressNotice();let t=e.total===0?this.t("syncNoChanges"):this.t("syncComplete",ee(e));new p.Notice(t,5e3).messageEl.addClass("dify-sync-complete")}showSyncError(e){this.hideProgressNotice(),new p.Notice(e,8e3).messageEl.addClass("dify-sync-error-notice")}hideProgressNotice(){this.syncProgressNotice&&(this.syncProgressNotice.hide(),this.syncProgressNotice=null)}createProgressBar(e){let i=Math.round(e/100*20);return`[${"\u2588".repeat(i)}${"\u2591".repeat(20-i)}]`}getDatasetLabel(e){let t=this.settings.knowledgeBases.find(i=>i.id===e);return t?`${t.name} (${t.id})`:`${this.t("datasetUnknown")} (${e})`}getDatasetName(e){let t=this.settings.knowledgeBases.find(i=>i.id===e);return(t==null?void 0:t.name)||e}},q=class extends p.PluginSettingTab{constructor(e,t){super(e,t);this.mainMappingPage=0;this.mainMappingPageSize=4;this.outerScrollHostClass="dify-sync-outer-scroll-host";this.innerScrollHostClass="dify-sync-inner-scroll-host";this.outerScrollHosts=new Set;this.scrollbarMarkToken=0;this.renderMode="legacy";this.plugin=t}getSettingDefinitions(){return[{name:this.plugin.t("settingsTitle"),desc:this.plugin.t("settingsSubtitle"),render:e=>(this.renderMode="declarative",this.renderSettingsApp(e.settingEl),()=>{this.scrollbarMarkToken++,this.clearScrollbarHosts()})}]}display(){this.renderLegacySettingsView()}refreshSettingsView(){if(this.renderMode==="declarative"){let e=this.update;if(typeof e=="function"){e.call(this);return}}this.renderLegacySettingsView()}renderLegacySettingsView(){this.renderMode="legacy",this.scrollbarMarkToken++,this.clearScrollbarHosts();let{containerEl:e}=this;e.empty(),this.renderSettingsApp(e)}renderSettingsApp(e){e.addClass("dify-sync-settings");let t=e.createDiv("app"),n=t.createEl("main",{cls:"main"}).createDiv("settings");this.renderTopbar(n),this.renderSyncSummary(n);let s=n.createDiv("dify-sync-content");this.renderConnectionSection(s),this.renderMappingSection(s),this.renderSyncSettingsSection(s),t.createDiv({cls:"toast-region",attr:{"aria-live":"polite","aria-atomic":"true"}}),this.scheduleScrollbarHostMarking(e)}hide(){this.scrollbarMarkToken++,this.clearScrollbarHosts(),super.hide()}scheduleScrollbarHostMarking(e){let t=++this.scrollbarMarkToken;this.clearScrollbarHosts(),window.requestAnimationFrame(()=>{window.requestAnimationFrame(()=>{t===this.scrollbarMarkToken&&e.isConnected&&this.markScrollbarHosts(e)})})}markScrollbarHosts(e){this.clearScrollbarHosts();let t=this.findScrollableAncestors(e);if(t.length===0)return;this.innerScrollHost=e,e.addClass(this.innerScrollHostClass),t.forEach(s=>this.addOuterScrollHost(s));let i=t[0],n=s=>this.handleSettingsWheel(s,e,i);e.addEventListener("wheel",n,{passive:!1}),this.wheelCleanup=()=>e.removeEventListener("wheel",n)}findScrollableAncestors(e){let t=[],i=e.parentElement;for(;i;)this.isVerticalScrollHost(i)&&t.push(i),i=i.parentElement;return t}isVerticalScrollHost(e){let t=window.getComputedStyle(e).overflowY;return e.scrollHeight>e.clientHeight+1&&["auto","scroll","overlay"].includes(t)}addOuterScrollHost(e){e.addClass(this.outerScrollHostClass),this.outerScrollHosts.add(e)}handleSettingsWheel(e,t,i){if(e.defaultPrevented||e.deltaY===0||this.targetCanScrollWithin(e.target,t,e.deltaY))return;let n=i.scrollTop;i.scrollTop+=e.deltaY,i.scrollTop!==n&&(e.preventDefault(),e.stopPropagation())}targetCanScrollWithin(e,t,i){let n=e instanceof HTMLElement?e:null;for(;n&&n!==t;){if(!this.outerScrollHosts.has(n)&&this.elementCanScrollInDirection(n,i))return!0;n=n.parentElement}return!1}elementCanScrollInDirection(e,t){return this.isVerticalScrollHost(e)?t<0?e.scrollTop>0:e.scrollTop+e.clientHeighti.removeClass(this.outerScrollHostClass)),this.outerScrollHosts.clear(),(t=this.innerScrollHost)==null||t.removeClass(this.innerScrollHostClass),this.innerScrollHost=void 0}renderTopbar(e){let t=e.createEl("header",{cls:"topbar"}),i=t.createDiv("title-line");new p.Setting(i).setName(this.plugin.t("settingsTitle")).setHeading().nameEl.addClass("heading-text"),i.createEl("p",{text:this.plugin.t("settingsSubtitle"),cls:"settings-subtitle"});let s=t.createDiv("top-actions"),o=new p.ExtraButtonComponent(s).setIcon("languages").setTooltip(this.plugin.t("languageName")),l=o.extraSettingsEl;o.onClick(()=>this.openLanguageMenu(l)),l.addClass("language-button"),l.setAttr("aria-label",this.plugin.t("languageName")),l.setAttr("title",this.plugin.t("languageName")),l.setAttr("data-action","open-language-menu");let c=new p.ExtraButtonComponent(s).setIcon("help-circle").setTooltip(this.plugin.t("openHelp")).onClick(()=>{new $(this.app,this.plugin).open()}).extraSettingsEl;c.addClass("help-button"),c.setAttr("aria-label",this.plugin.t("openHelp")),c.setAttr("title",this.plugin.t("openHelp")),c.setAttr("data-action","open-help");let d=s.createEl("a",{cls:"github-link",href:"https://github.com/BeatsAD/Vault2Dify"});d.setAttr("target","_blank"),d.setAttr("rel","noopener noreferrer"),d.setAttr("aria-label",this.plugin.t("openGithub")),d.setAttr("title","GitHub"),(0,p.setIcon)(d,"github")}openLanguageMenu(e){let t=new p.Menu;[{value:"zh-CN",label:this.plugin.t("languageChinese")},{value:"en",label:this.plugin.t("languageEnglish")}].forEach(s=>{t.addItem(o=>{o.setTitle(s.label).setChecked(this.plugin.settings.language===s.value).onClick(()=>{this.plugin.settings.language!==s.value&&(async()=>(this.plugin.settings.language=s.value,await this.plugin.savePluginData(),this.plugin.updateStatusBar(this.plugin.t("ready")),this.refreshSettingsView()))()})})});let n=e.getBoundingClientRect();t.showAtPosition({x:n.left,y:n.bottom+4,width:n.width})}renderSyncSummary(e){let t=Z({connectionHealth:this.plugin.settings.connectionHealth,recentSyncResult:this.plugin.settings.recentSyncResult}),i={total:this.plugin.t("metricTotal"),synced:this.plugin.t("metricSynced"),skipped:this.plugin.t("metricSkipped"),failed:this.plugin.t("metricFailed")},n=e.createDiv("sync-summary");n.setAttr("aria-label",this.plugin.t("sectionDiagnostics")),t.metrics.forEach(s=>{let o=n.createDiv("metric");o.createDiv({text:i[s.key],cls:"metric-label"}),o.createDiv({text:String(s.value),cls:s.tone==="default"?"metric-value":`metric-value ${s.tone}`})})}renderConnectionSection(e){let{card:t}=this.createReviewSection(e,"connection-title",this.plugin.t("settingsReviewConnectionTitle")),i,n=this.createNativeSetting(t,"setting-row");n.controlEl.addClass("has-secret-toggle"),n.setName(this.createSettingName(this.plugin.t("apiKeyName"),{required:!0})).setTooltip(this.plugin.t("apiKeyDesc")).addText(h=>{i=h.setPlaceholder(this.plugin.t("apiKeyPlaceholder")).setValue(this.plugin.settings.difyApiKey).onChange(g=>{this.plugin.settings.difyApiKey=g.trim(),this.plugin.invalidateConnectionState(),this.plugin.savePluginData()}).inputEl,i.id="dify-api-key",i.type="password",i.setAttr("aria-label",this.plugin.t("apiKeyName"))}).addExtraButton(h=>{let g=h.setIcon("eye").setTooltip(this.plugin.t("showApiKey")).onClick(()=>{let y=i.type==="password";i.type=y?"text":"password",h.setIcon(y?"eye-off":"eye");let E=this.plugin.t(y?"hideApiKey":"showApiKey");h.setTooltip(E),g.setAttr("aria-label",E),g.setAttr("title",E),g.setAttr("aria-pressed",String(y))}).extraSettingsEl;g.addClass("secret-toggle"),g.setAttr("aria-label",this.plugin.t("showApiKey")),g.setAttr("title",this.plugin.t("showApiKey")),g.setAttr("aria-controls","dify-api-key"),g.setAttr("aria-pressed","false")}),this.createNativeSetting(t,"setting-row").setName(this.createSettingName(this.plugin.t("apiUrlName"),{required:!0})).setTooltip(this.plugin.t("apiUrlDesc")).addText(h=>{h.setPlaceholder(this.plugin.t("urlPlaceholder")).setValue(this.plugin.settings.difyApiUrl).onChange(y=>{this.plugin.settings.difyApiUrl=y.trim(),this.plugin.invalidateConnectionState(),this.plugin.savePluginData()}).inputEl.setAttr("aria-label",this.plugin.t("apiUrlName"))});let s=t.createDiv("dataset-refresh-row"),o=s.createDiv("status-line");o.setAttr("aria-label",this.plugin.t("healthConnection")),o.createSpan({cls:`status-dot ${this.getConnectionStatusToneClass()}`}),o.createEl("strong",{text:this.getConnectionStatusLabel()});let l=s.createDiv("connection-action-buttons"),c=new p.ButtonComponent(l).setButtonText(this.plugin.t("clearConnectionConfig")).setClass("native-action-button").onClick(()=>{(async()=>(this.plugin.settings.difyApiKey="",this.plugin.settings.difyApiUrl="",this.plugin.settings.lanApiUrl="",this.plugin.settings.publicApiUrl="",this.plugin.invalidateConnectionState(),await this.plugin.savePluginData(),this.refreshSettingsView()))()});c.buttonEl.setAttr("data-action","clear-connection-config"),c.buttonEl.setAttr("aria-label",this.plugin.t("clearConnectionConfig"));let d;d=new p.ButtonComponent(l).setButtonText(this.plugin.t("testConnection")).setClass("native-action-button").onClick(()=>{d.setDisabled(!0),d.setButtonText(this.plugin.t("syncing")),(async()=>{try{await this.plugin.testConnection()}finally{d.setDisabled(!1),d.setButtonText(this.plugin.t("testConnection")),this.refreshSettingsView()}})()}),d.buttonEl.setAttr("data-action","test-connection"),d.buttonEl.setAttr("aria-label",this.plugin.t("testConnection"));let u=s.createDiv({cls:"callout",attr:{"data-role":"connection-result"}});u.hidden=!0,u.createEl("strong",{text:this.getConnectionStatusLabel()}),u.createSpan({text:this.getConnectionNote()})}renderMappingSection(e){let{card:t}=this.createReviewSection(e,"mapping-title",this.plugin.t("settingsReviewMappingTitle"),c=>{let d=new p.ButtonComponent(c).setButtonText(this.plugin.t("addMapping")).setCta().onClick(()=>{new L(this.app,this.plugin,void 0,async u=>{this.plugin.settings.mappings.push(u),await this.plugin.savePluginData(),new p.Notice(this.plugin.t("mappingSaved")),this.refreshSettingsView()}).open()});d.buttonEl.setAttr("data-action","add-mapping"),d.buttonEl.setAttr("aria-label",this.plugin.t("addMapping"))}),i=t.createDiv({cls:"mapping-table main-mapping-table",attr:{role:"table","aria-label":this.plugin.t("sectionMapping")}}),n=i.createDiv({cls:"mapping-header",attr:{role:"row"}});n.createDiv({text:this.plugin.t("mappingIndex")}),n.createDiv({text:this.plugin.t("mappingFolderColumn")}),n.createDiv({text:this.plugin.t("mappingDatasetColumn")}),n.createDiv({text:this.plugin.t("mappingStatusColumn")}),n.createDiv({text:this.plugin.t("mappingActionColumn")});let s=this.plugin.settings.mappings,o=Math.max(0,Math.ceil(s.length/this.mainMappingPageSize)-1);this.mainMappingPage=Math.min(this.mainMappingPage,o),s.length===0&&i.createDiv({text:this.plugin.t("noMappings"),cls:"main-mapping-empty"});let l=this.mainMappingPage*this.mainMappingPageSize;s.slice(l,l+this.mainMappingPageSize).forEach((c,d)=>{this.renderMappingRow(i,c,l+d)}),this.renderMainMappingPagination(t,this.plugin.settings.mappings.length)}renderMainMappingPagination(e,t){let i=e.createDiv({cls:"main-mapping-pagination mapping-pagination",attr:{"data-role":"main-mapping-pagination"}}),n=Math.max(1,Math.ceil(t/this.mainMappingPageSize)),s=new p.ButtonComponent(i).setButtonText("\u2039"),o=t===0?0:this.mainMappingPage*this.mainMappingPageSize+1,l=t===0?0:Math.min(t,(this.mainMappingPage+1)*this.mainMappingPageSize);i.createSpan({text:t===0?this.plugin.t("mappingPageInfoEmpty"):`${o}-${l} / ${t}`,attr:{"data-role":"main-mapping-page-info"}});let c=new p.ButtonComponent(i).setButtonText("\u203A");s.setDisabled(t===0||this.mainMappingPage===0),c.setDisabled(t===0||this.mainMappingPage>=n-1),s.onClick(()=>{this.mainMappingPage=Math.max(0,this.mainMappingPage-1),this.refreshSettingsView()}),c.onClick(()=>{this.mainMappingPage=Math.min(n-1,this.mainMappingPage+1),this.refreshSettingsView()})}renderMappingRow(e,t,i){let n=e.createDiv({cls:"mapping-row",attr:{role:"row"}});se(n,this.plugin,t,i,"toggle-mapping-status",async()=>{t.enabled=!t.enabled,await this.plugin.savePluginData(),this.refreshSettingsView()},s=>{let o=new p.ExtraButtonComponent(s).setIcon("pencil").setTooltip(this.plugin.t("edit")).onClick(()=>{new L(this.app,this.plugin,t,async c=>{this.plugin.settings.mappings=this.plugin.settings.mappings.map(d=>d.id===c.id?c:d),await this.plugin.savePluginData(),this.refreshSettingsView()}).open()}).extraSettingsEl;o.addClass("icon"),o.setAttr("data-action","edit-mapping"),o.setAttr("aria-label",this.plugin.t("edit")),o.setAttr("title",this.plugin.t("edit"));let l=new p.ExtraButtonComponent(s).setIcon("trash-2").setTooltip(this.plugin.t("delete")).onClick(()=>{new _(this.app,this.plugin.t("deleteMappingTitle"),this.plugin.t("deleteMappingDesc"),this.plugin.t("deleteMappingConfirm"),this.plugin.t("cancel"),async()=>{this.plugin.settings.mappings=J(this.plugin.settings.mappings,t.id),await this.plugin.savePluginData(),this.refreshSettingsView()},{folder:t.folder||this.plugin.t("rootFolder"),datasets:t.datasetIds.map(c=>this.plugin.getDatasetName(c)).join("\u3001")||this.plugin.t("datasetMissing")},c=>this.plugin.t(c)).open()}).extraSettingsEl;l.addClass("icon"),l.addClass("danger"),l.setAttr("data-action","delete-mapping"),l.setAttr("aria-label",this.plugin.t("delete")),l.setAttr("title",this.plugin.t("delete"))})}renderSyncSettingsSection(e){let{card:t}=this.createReviewSection(e,"auto-title",this.plugin.t("settingsReviewSyncTitle"));this.createNativeSetting(t,"setting-row","switch-row").setName(this.createSettingName(this.plugin.t("sectionAuto"))).addToggle(y=>{y.setValue(this.plugin.settings.autoSyncEnabled).onChange(E=>{(async()=>(this.plugin.settings.autoSyncEnabled=E,await this.plugin.savePluginData(),this.plugin.setupAutoSync(),this.refreshSettingsView()))()}),y.toggleEl.setAttr("data-action","toggle-auto-sync")});let i=t.createDiv({attr:{id:"advanced-sync"}});this.createAdvancedSelect(i,this.plugin.t("syncAfterChange"),this.plugin.settings.debounceSeconds,[[8,this.plugin.t("debounceOption8")],[15,this.plugin.t("debounceOption15")],[30,this.plugin.t("debounceOption30")]],async y=>{this.plugin.settings.debounceSeconds=y,await this.plugin.savePluginData()},"debounce"),this.createAdvancedSelect(i,this.plugin.t("fullScanCheck"),this.plugin.settings.periodicFullScanEnabled?this.plugin.settings.syncInterval:0,[[30,this.plugin.t("fullScanOption30")],[60,this.plugin.t("fullScanOption60")],[0,this.plugin.t("fullScanOptionOff")]],async y=>{this.plugin.settings.periodicFullScanEnabled=y>0,y>0&&(this.plugin.settings.syncInterval=y),await this.plugin.savePluginData(),this.plugin.setupAutoSync()},"interval"),this.createAdvancedSelect(i,this.plugin.t("startupSyncShort"),this.plugin.settings.syncOnStartup,[[!0,this.plugin.t("optionOn")],[!1,this.plugin.t("optionOff")]],async y=>{this.plugin.settings.syncOnStartup=y,await this.plugin.savePluginData()},"startup-sync"),this.createAdvancedSelect(i,this.plugin.t("concurrentUpload"),this.plugin.settings.maxConcurrent,[[2,this.plugin.t("concurrencyOption2")],[4,this.plugin.t("concurrencyOption4")]],async y=>{this.plugin.settings.maxConcurrent=v(y,1,5,2),await this.plugin.savePluginData()},"concurrency");let n=t.createEl("details",{cls:"diagnostic-fold"});n.open=!0;let s=n.createEl("summary");s.createSpan({text:this.plugin.t("sectionDiagnostics")}),s.createSpan({text:`Dify: ${this.plugin.t("ready")}`});let o=n.createDiv("diagnostic-body"),l=this.plugin.settings.lastSyncTime?this.plugin.t("lastSync",{time:new Date(this.plugin.settings.lastSyncTime).toLocaleString()}):this.plugin.t("lastSyncInitial");o.createDiv({text:l,cls:"dify-sync-diagnostic-line"}),this.renderRecentSyncResult(o);let c=o.createDiv("sync-actions");new p.ButtonComponent(c).setButtonText(this.plugin.settings.debugLogging?`${this.plugin.t("debugName")} \xB7 ${this.plugin.t("enabled")}`:this.plugin.t("debugName")).onClick(()=>{(async()=>(this.plugin.settings.debugLogging=!this.plugin.settings.debugLogging,await this.plugin.savePluginData(),this.refreshSettingsView()))()}).buttonEl.setAttr("data-action","toggle-debug-logging"),new p.ButtonComponent(c).setButtonText(this.plugin.t("resetRecords")).onClick(()=>{(async()=>(this.plugin.syncRecords.clear(),await this.plugin.savePluginData(),new p.Notice(this.plugin.t("resetRecordsDone")),this.refreshSettingsView()))()}).buttonEl.setAttr("data-action","reset-sync-records");let h=t.createDiv("sync-actions manual-sync-actions"),g;g=new p.ButtonComponent(h).setButtonText(this.plugin.t("manualSync")).setCta().onClick(()=>{g.setDisabled(!0),g.setButtonText(this.plugin.t("syncing")),(async()=>{try{await this.plugin.performSync("manual")}finally{g.setDisabled(!1),g.setButtonText(this.plugin.t("manualSync")),this.refreshSettingsView()}})()}),g.buttonEl.setAttr("data-action","sync-now"),g.buttonEl.setAttr("aria-label",this.plugin.t("manualSync"))}renderRecentSyncResult(e){let t=this.plugin.settings.recentSyncResult;if(!t){e.createDiv({text:this.plugin.t("recentSyncInitial"),cls:"dify-sync-diagnostic-line"});return}e.createDiv({text:this.plugin.t("recentSyncSummary",{source:t.source,total:t.total,synced:t.synced,skipped:t.skipped,failed:t.failed,elapsed:Te(t.elapsedMs)}),cls:"dify-sync-diagnostic-line"}),t.error&&e.createDiv({text:this.plugin.t("recentSyncError",{message:t.error}),cls:"dify-sync-diagnostic-line dify-sync-diagnostic-error"})}createReviewSection(e,t,i,n){let s=e.createEl("section",{cls:"section",attr:{"aria-labelledby":t}}),o=s.createDiv("section-head"),l=o.createDiv("heading-line"),c=new p.Setting(l).setName(i).setHeading();return c.nameEl.addClass("heading-text"),c.nameEl.id=t,n&&n(o),{card:s.createDiv("section-card")}}createSettingName(e,t={}){let i=this.containerEl.doc,n=i.createDocumentFragment(),s=i.createElement("span");if(s.className="setting-label",s.textContent=e,n.appendChild(s),t.required){let o=i.createElement("span");o.className="required-marker",o.textContent="*",o.setAttribute("aria-label","Required"),n.appendChild(o)}return n}createSettingRow(e,t,i,n={}){return{control:this.createNativeSetting(e,"setting-row").setName(this.createSettingName(t,n)).setDesc(i).setTooltip(i).controlEl}}createTextInput(e,t,i,n){let s=e.createEl("input",{attr:{type:"text",placeholder:i,"aria-label":t}});return s.value=n,s}createAdvancedText(e,t,i,n,s,o=this.plugin.t("urlPlaceholder"),l={}){this.createNativeSetting(e,"advanced-field").setName(this.createSettingName(t,l)).addText(c=>{let d=c.setPlaceholder(o).setValue(i).onChange(u=>{n(u.trim())}).inputEl;d.id=s})}createAdvancedNumber(e,t,i,n,s,o,l){let c=e.createDiv("advanced-field");c.createEl("label",{text:t,attr:{for:l}});let d=c.createEl("input",{attr:{id:l,type:"number",min:String(n),max:String(s)}});d.value=String(i),d.addEventListener("change",()=>{o(Number(d.value)).catch(u=>{new p.Notice(w(u))})})}createAdvancedSelect(e,t,i,n,s,o){let l=new Map(n.map(([d])=>[String(d),d])),c=Object.fromEntries(n.map(([d,u])=>[String(d),u]));this.createNativeSetting(e,"advanced-field").setName(this.createSettingName(t)).addDropdown(d=>{d.addOptions(c).setValue(String(i)).onChange(u=>{let h=l.get(u);h!==void 0&&s(h)}),d.selectEl.id=`${o}-select`,d.selectEl.setAttr("aria-label",t)})}createNativeSetting(e,...t){let i=new p.Setting(e).setClass("native-setting-row");return t.forEach(n=>i.settingEl.addClass(n)),i}createHelpIcon(e,t){let i=e.createSpan({text:"?",cls:"help-icon"});i.setAttr("tabindex","0"),i.setAttr("aria-label",t),i.setAttr("data-tooltip",t)}getConnectionStatusLabel(){let e=this.plugin.settings.connectionHealth.status;return!this.plugin.settings.difyApiKey.trim()||this.plugin.getConfiguredBaseUrls().length===0?this.plugin.t("connectionMissing"):e==="connected"?this.plugin.t("connectionConnected"):e==="failed"?this.plugin.t("connectionFailedState"):e==="missing_config"?this.plugin.t("connectionMissing"):this.plugin.t("connectionUnknown")}getConnectionStatusToneClass(){let e=this.plugin.settings.connectionHealth.status;return!this.plugin.settings.difyApiKey.trim()||this.plugin.getConfiguredBaseUrls().length===0?"is-muted":e==="connected"?"is-success":e==="failed"?"is-error":"is-muted"}getActiveUrlLabel(){let e=this.plugin.settings.connectionHealth;return te(e.activeBaseUrl||e.lastSuccessfulBaseUrl||b(this.plugin.settings.difyApiUrl)||"-")}getConnectionNote(){let e=this.plugin.settings.connectionHealth;return!this.plugin.settings.difyApiKey.trim()||this.plugin.getConfiguredBaseUrls().length===0?this.plugin.t("connectionNoteMissing"):e.status==="connected"?this.plugin.t("connectionNoteSuccess",{count:e.datasetCount,url:te(e.activeBaseUrl||e.lastSuccessfulBaseUrl)}):e.status==="failed"&&e.error?this.plugin.t("connectionNoteFailed",{message:e.error}):this.plugin.t("connectionNoteIdle")}},$=class extends p.Modal{constructor(e,t){super(e);this.plugin=t}onOpen(){let{contentEl:e}=this;e.empty(),this.modalEl.addClass("dify-sync-modal-shell"),this.modalEl.addClass("help-modal-shell"),e.addClass("dify-sync-modal"),e.addClass("help-modal"),e.createDiv("modal-head").createDiv("modal-title-line").createEl("h2",{text:this.plugin.t("helpModalTitle"),attr:{id:"help-modal-title"}});let n=e.createDiv({cls:"help-modal-body",attr:{role:"document","aria-labelledby":"help-modal-title"}});this.getHelpSections().forEach(s=>this.renderHelpSection(n,s))}getHelpSections(){return[{titleKey:"helpQuickStartTitle",paragraphKeys:["helpQuickStartBody"],orderedListKeys:["helpQuickStartList"]},{titleKey:"helpApiUrlTitle",paragraphKeys:["helpApiUrlBody"]},{titleKey:"helpApiKeyTitle",paragraphKeys:["helpApiKeyBody"]},{titleKey:"helpMappingTitle",paragraphKeys:["helpMappingBody"],listKeys:["helpMappingList"]},{titleKey:"helpSyncModesTitle",listKeys:["helpSyncModesList"]},{titleKey:"helpSyncRecommendedTitle",listKeys:["helpSyncRecommendedList"]},{titleKey:"helpSyncRulesTitle",paragraphKeys:["helpSyncRulesBody"],orderedListKeys:["helpSyncRulesList"]},{titleKey:"helpTroubleshootingTitle",orderedListKeys:["helpTroubleshootingList"]},{titleKey:"helpCommonErrorsTitle",errorListKeys:["helpCommonErrorsList"]},{titleKey:"helpDatasetsTitle",listKeys:["helpDatasetsList"]},{titleKey:"helpFilesNotSyncingTitle",listKeys:["helpFilesNotSyncingList"]},{titleKey:"helpSlowSyncTitle",listKeys:["helpSlowSyncList"]},{titleKey:"helpDuplicateDocsTitle",paragraphKeys:["helpDuplicateDocsBody"],orderedListKeys:["helpDuplicateDocsList"]},{titleKey:"helpDebugTitle",paragraphKeys:["helpDebugBody"]},{titleKey:"helpPrivacyTitle",paragraphKeys:["helpPrivacyBody"]}]}renderHelpSection(e,t){let i=e.createEl("section",{cls:"help-section"});i.createEl("h3",{text:this.plugin.t(t.titleKey)}),(t.paragraphKeys||[]).forEach(n=>{this.splitHelpLines(this.plugin.t(n,{configDir:this.app.vault.configDir})).forEach(s=>{i.createEl("p",{text:s})})}),(t.orderedListKeys||[]).forEach(n=>{this.renderHelpList(i,this.splitHelpLines(this.plugin.t(n,{configDir:this.app.vault.configDir})),!0)}),(t.listKeys||[]).forEach(n=>{this.renderHelpList(i,this.splitHelpLines(this.plugin.t(n,{configDir:this.app.vault.configDir})),!1)}),(t.errorListKeys||[]).forEach(n=>{this.renderHelpErrorList(i,this.splitHelpLines(this.plugin.t(n,{configDir:this.app.vault.configDir})))})}renderHelpList(e,t,i){let n=e.createEl(i?"ol":"ul",{cls:"help-list"});t.forEach(s=>n.createEl("li",{text:s}))}renderHelpErrorList(e,t){let i=e.createDiv("help-error-list");t.forEach(n=>{let s=n.includes("\uFF1A")?"\uFF1A":":",[o,...l]=n.split(s),c=i.createDiv("help-error-item");c.createEl("strong",{text:o.trim()});let d=l.join(s).trim();d&&c.createSpan({text:d})})}splitHelpLines(e){return e.split(` `).map(t=>t.trim()).filter(Boolean)}},_=class extends p.Modal{constructor(e,t,i,n,s,o,l,c=d=>d){super(e);this.title=t,this.message=i,this.confirmText=n,this.cancelText=s,this.onConfirm=o,this.preview=l,this.labelProvider=c}onOpen(){var h,g;let{contentEl:e}=this;e.empty(),this.modalEl.addClass("dify-sync-modal-shell"),this.modalEl.addClass("confirm-modal-shell"),e.addClass("dify-sync-modal"),e.addClass("confirm-modal"),e.createDiv("modal-head").createDiv("modal-title-line").createEl("h2",{text:this.title,attr:{id:"delete-modal-title"}});let s=e.createDiv("confirm-body").createDiv({cls:"confirm-mapping",attr:{"data-role":"delete-mapping-preview"}}),o=s.createDiv("confirm-mapping-item");o.createEl("strong",{text:this.pluginLabel("mappingFolderColumn"),cls:"confirm-mapping-label"}),o.createSpan({text:((h=this.preview)==null?void 0:h.folder)||"-",cls:"confirm-mapping-value"}).setAttr("data-role","delete-folder");let l=s.createDiv("confirm-mapping-item");l.createEl("strong",{text:this.pluginLabel("datasetName"),cls:"confirm-mapping-label"}),l.createSpan({text:((g=this.preview)==null?void 0:g.datasets)||"-",cls:"confirm-mapping-value"}).setAttr("data-role","delete-datasets");let c=e.createDiv("modal-actions");new p.ButtonComponent(c).setButtonText(this.cancelText).onClick(()=>this.close()).buttonEl.setAttr("data-action","close-delete-confirm"),Me(new p.ButtonComponent(c).setButtonText(this.confirmText)).setCta().onClick(()=>{(async()=>(await this.onConfirm(),this.close()))()}).buttonEl.setAttr("data-action","confirm-delete")}pluginLabel(e){return this.labelProvider(e)}},L=class extends p.Modal{constructor(e,t,i,n){super(e);this.selectedFolder="";this.folderSelected=!1;this.selectedDatasets=new Set;this.pendingMappings=[];this.plugin=t,this.isEditing=!!i,this.mapping=i?{id:i.id,folder:i.folder,datasetIds:[...i.datasetIds],enabled:i.enabled}:{id:N(),folder:"",datasetIds:[],enabled:!0},this.onSave=n}onOpen(){let{contentEl:e}=this;e.empty(),this.modalEl.addClass("dify-sync-modal-shell"),this.modalEl.addClass("mapping-modal-shell"),e.addClass("dify-sync-modal"),e.addClass("mapping-modal"),this.contentEl.addEventListener("click",()=>this.closeSiblingDropdowns()),e.createDiv("modal-head").createDiv("modal-title-line").createEl("h2",{text:this.plugin.t(this.isEditing?"mappingModalTitle":"addMappingModalTitle"),attr:{id:"mapping-modal-title"}});let n=e.createDiv("modal-body"),s=n.createDiv("mapping-builder");this.renderFolderPanel(s);let l=s.createDiv({cls:"mapping-arrow",attr:{"aria-hidden":"true"}}).createDiv("mapping-arrow-icon");(0,p.setIcon)(l,"arrow-right"),this.renderDatasetPanel(s),this.renderPendingMappings(n);let c=e.createDiv("modal-actions");new p.ButtonComponent(c).setButtonText(this.plugin.t("cancel")).onClick(()=>this.close()).buttonEl.setAttr("data-action","close-modal");let u=new p.ButtonComponent(c).setButtonText(this.plugin.t("refreshDatasets")).onClick(()=>{(async()=>{try{await this.plugin.refreshKnowledgeBases(),this.renderDatasetChoices(),new p.Notice(this.plugin.t("datasetsRefreshed",{count:this.plugin.settings.knowledgeBases.length}))}catch(g){this.plugin.showConnectionError(g)}})()});u.buttonEl.setAttr("data-action","refresh-modal-datasets"),u.buttonEl.setAttr("aria-label",this.plugin.t("refreshDatasets")),new p.ButtonComponent(c).setButtonText(this.plugin.t("saveMapping")).setCta().onClick(()=>{this.savePendingMappings()}).buttonEl.setAttr("data-action","save-mapping"),this.isEditing?(this.selectFolder(this.mapping.folder,!1),this.mapping.datasetIds.forEach(g=>this.selectedDatasets.add(g)),this.updateDatasetSummary(),this.addPendingMapping(!1,this.mapping.enabled,this.mapping.id)):(this.updateDatasetSummary(),this.updatePendingVisibility())}renderFolderPanel(e){let t=e.createDiv("mapping-panel");t.createDiv("label-line").createEl("label",{text:this.plugin.t("folderName"),attr:{id:"modal-folder-label"}}),this.folderInputEl=t.createEl("input",{attr:{id:"modal-folder",type:"hidden",value:""}});let n=t.createDiv("dropdown-box");n.addEventListener("click",c=>c.stopPropagation());let s=n.createEl("button",{cls:"dropdown-trigger",attr:{type:"button","aria-expanded":"false","aria-controls":"folder-dropdown-menu","data-action":"toggle-folder-dropdown"}});this.folderSummaryEl=s.createSpan({text:this.plugin.t("selectFolderPlaceholder"),attr:{"data-role":"folder-summary"}}),(0,p.setIcon)(s.createSpan({cls:"dropdown-caret",attr:{"aria-hidden":"true"}}),"chevron-down");let o=n.createDiv({cls:"dropdown-menu",attr:{id:"folder-dropdown-menu"}});o.hidden=!0;let l=o.createEl("input",{cls:"folder-search",attr:{type:"search",placeholder:this.plugin.t("folderSearch"),"aria-label":this.plugin.t("folderSearch")}});this.folderTreeEl=o.createDiv({cls:"folder-tree dify-sync-obsidian-folder-tree",attr:{role:"tree","aria-labelledby":"modal-folder-label"}}),this.renderFolderTree(),s.addEventListener("click",()=>this.toggleMenu(s,o)),l.addEventListener("input",()=>{let c=l.value.trim().toLowerCase();this.applyFolderSearch(c)})}renderDatasetPanel(e){let t=e.createDiv("mapping-panel");t.createDiv("label-line").createEl("label",{text:this.plugin.t("datasetName"),attr:{id:"modal-dataset-label"}}),this.datasetInputEl=t.createEl("input",{attr:{id:"modal-dataset",type:"hidden",value:"","data-dataset-id":""}});let n=t.createDiv("dataset-select-row"),s=n.createDiv("dropdown-box");s.addEventListener("click",l=>l.stopPropagation()),this.datasetTriggerEl=s.createEl("button",{cls:"dropdown-trigger",attr:{type:"button","aria-expanded":"false","aria-controls":"dataset-dropdown-menu","data-action":"toggle-dataset-dropdown"}}),this.datasetSummaryEl=this.datasetTriggerEl.createSpan({attr:{"data-role":"dataset-summary"}}),(0,p.setIcon)(this.datasetTriggerEl.createSpan({cls:"dropdown-caret",attr:{"aria-hidden":"true"}}),"chevron-down"),this.datasetOptionsEl=s.createDiv({cls:"dropdown-menu",attr:{id:"dataset-dropdown-menu"}}),this.datasetOptionsEl.hidden=!0,this.renderDatasetChoices(),this.datasetTriggerEl.addEventListener("click",()=>{if(!this.folderSelected){new p.Notice(this.plugin.t("chooseFolderFirst"));return}this.toggleMenu(this.datasetTriggerEl,this.datasetOptionsEl)}),new p.ButtonComponent(n).setButtonText(this.plugin.t("addPendingMapping")).setCta().onClick(()=>this.addPendingMapping(!0)).buttonEl.setAttr("data-action","add-pending-mapping")}renderFolderTree(){this.folderTreeEl.empty();let e=this.app.vault.getAllLoadedFiles().filter(n=>n instanceof p.TFolder&&!!n.path&&n.path!=="/").map(n=>n.path).sort(),t=this.app.vault.getMarkdownFiles().filter(n=>n.extension.toLowerCase()==="md").map(n=>n.path).sort();if(e.length===0&&t.length===0){this.folderTreeEl.createDiv({text:this.plugin.t("folderPickerEmpty"),cls:"pending-empty"});return}let i=new Map;e.forEach(n=>{let s=n.lastIndexOf("/"),o=s===-1?"":n.slice(0,s),l=i.get(o)||[];l.push({path:n,type:"folder"}),i.set(o,l)}),t.forEach(n=>{let s=n.lastIndexOf("/"),o=s===-1?"":n.slice(0,s),l=i.get(o)||[];l.push({path:n,type:"file"}),i.set(o,l)}),i.forEach(n=>{n.sort((s,o)=>s.type!==o.type?s.type==="folder"?-1:1:s.path.localeCompare(o.path))}),(i.get("")||[]).forEach(n=>this.renderPathNode(n,i,this.folderTreeEl,0))}renderPathNode(e,t,i,n){if(e.type==="file"){this.renderFileNode(e.path,i);return}this.renderFolderNode(e.path,t,i,n)}renderFolderNode(e,t,i,n){let s=t.get(e)||[],o=s.length>0,l=`folder-tree-${e.replace(/[^a-zA-Z0-9_-]+/g,"-").toLowerCase()}`,c=e.split("/").pop()||e,d=i.createDiv({cls:["nav-folder",n===0?"mod-root":""].filter(Boolean).join(" ")});d.dataset.folder=e;let u=d.createDiv({cls:["nav-folder-title",o?"":"is-folder-leaf",e===this.selectedFolder&&this.folderSelected?"is-selected":""].filter(Boolean).join(" "),attr:{role:"treeitem",tabindex:"0","data-action":o?"toggle-tree-folder":"select-folder","aria-expanded":o?"true":null,"data-tree-target":o?l:null}});u.dataset.folder=e;let h=u.createSpan({cls:o?"nav-folder-collapse-indicator dify-sync-folder-toggle":"nav-folder-collapse-indicator dify-sync-folder-toggle is-leaf",attr:{"aria-hidden":"true"}});if(o&&(0,p.setIcon)(h,"chevron-right"),u.createDiv({text:c,cls:"nav-folder-title-content"}),u.createSpan({text:this.getFolderCountLabel(e),cls:"dify-sync-folder-count"}),u.addEventListener("click",()=>{if(o){this.toggleTreeFolder(u),this.selectFolder(e,!1);return}this.selectFolder(e,!0)}),u.addEventListener("keydown",y=>this.handleFolderTreeKeydown(y,u,o)),!o)return;let g=d.createDiv({cls:"nav-folder-children",attr:{id:l,role:"group"}});s.forEach(y=>this.renderPathNode(y,t,g,n+1))}renderFileNode(e,t){let i=e.split("/").pop()||e,n=t.createDiv({cls:"nav-file"});n.dataset.folder=e;let s=n.createDiv({cls:["nav-file-title",e===this.selectedFolder&&this.folderSelected?"is-selected":""].filter(Boolean).join(" "),attr:{role:"treeitem",tabindex:"0","data-action":"select-note"}});s.dataset.folder=e,s.createDiv({text:i,cls:"nav-file-title-content"}),s.addEventListener("click",()=>this.selectFolder(e,!0)),s.addEventListener("keydown",o=>this.handleFolderTreeKeydown(o,s,!1))}applyFolderSearch(e){let t=Array.from(this.folderTreeEl.querySelectorAll(".nav-folder, .nav-file")),i=t.map(n=>n.dataset.folder||"");t.forEach(n=>{let s=n.dataset.folder||"",o=s.toLowerCase(),l=!e||o.includes(e)||i.some(c=>c.startsWith(`${s}/`)&&c.toLowerCase().includes(e));n.hidden=!l}),this.folderTreeEl.querySelectorAll(".nav-folder-title[data-tree-target]").forEach(n=>{let s=this.getFolderChildContainer(n);if(!s)return;if(e){let l=Array.from(s.querySelectorAll(".nav-folder, .nav-file")).some(c=>!c.hidden);s.hidden=!l,n.toggleClass("is-collapsed",!l),n.setAttr("aria-expanded",String(l));return}let o=n.getAttr("aria-expanded")!=="false";s.hidden=!o,n.toggleClass("is-collapsed",!o)})}handleFolderTreeKeydown(e,t,i){var n,s;if(e.key==="Enter"||e.key===" "){e.preventDefault();let o=t.dataset.folder||"";i&&this.toggleTreeFolder(t),this.selectFolder(o,!i);return}if(e.key==="ArrowRight"&&i){e.preventDefault(),this.setFolderExpanded(t,!0);return}if(e.key==="ArrowLeft"&&i){e.preventDefault(),this.setFolderExpanded(t,!1);return}if(e.key==="Escape"){e.preventDefault();let o=this.folderSummaryEl.closest(".dropdown-box");(n=o==null?void 0:o.querySelector(".dropdown-menu"))==null||n.setAttr("hidden","true"),(s=o==null?void 0:o.querySelector(".dropdown-trigger"))==null||s.setAttr("aria-expanded","false")}}getFolderCountLabel(e){return`${this.app.vault.getMarkdownFiles().filter(i=>i.path.startsWith(`${e}/`)).length} \u7BC7`}renderDatasetChoices(){if(this.datasetOptionsEl.empty(),this.datasetOptionsEl.removeClass("is-empty"),this.plugin.settings.knowledgeBases.length===0){this.datasetOptionsEl.addClass("is-empty"),this.datasetOptionsEl.createDiv({text:this.plugin.t("datasetsDropdownEmpty"),cls:"dataset-empty-state"});return}this.plugin.settings.knowledgeBases.forEach(e=>{let t=this.datasetOptionsEl.createEl("label",{cls:"dataset-option"}),i=t.createEl("input",{attr:{type:"checkbox","data-action":"select-dataset","data-dataset":e.name||e.id,"data-dataset-id":e.id}});i.checked=this.selectedDatasets.has(e.id);let n=t.createSpan({cls:"dataset-checkmark",attr:{"aria-hidden":"true"}});(0,p.setIcon)(n,"check"),t.toggleClass("is-selected",i.checked),i.addEventListener("change",()=>{i.checked?this.selectedDatasets.add(e.id):this.selectedDatasets.delete(e.id),t.toggleClass("is-selected",i.checked),this.updateDatasetSummary()});let s=t.createSpan();s.createSpan({text:e.name||e.id}),s.createEl("small",{text:e.id})})}renderPendingMappings(e){let t=e.createDiv("pending-mappings"),i=t.createDiv("pending-mapping-head");i.createDiv("label-line").createEl("label",{text:this.plugin.t("pendingMappingsTitle")});let s=i.createDiv("pending-bulk-actions");s.setAttr("aria-label",this.plugin.t("bulkSet"));let o=new p.ButtonComponent(s).setButtonText(this.plugin.t("enableAll")).setClass("bulk-action").onClick(()=>this.setAllPendingStatus(!0));this.enableAllButtonEl=o.buttonEl,this.enableAllButtonEl.setAttr("data-action","enable-all-pending");let l=new p.ButtonComponent(s).setButtonText(this.plugin.t("pauseAll")).setClass("bulk-action").onClick(()=>this.setAllPendingStatus(!1));this.pauseAllButtonEl=l.buttonEl,this.pauseAllButtonEl.setAttr("data-action","pause-all-pending"),this.pendingEmptyEl=t.createDiv({text:this.plugin.t("pendingEmptyDesc"),cls:"pending-empty",attr:{"data-role":"pending-empty"}}),this.pendingTableEl=t.createDiv({cls:"mapping-table pending-mapping-table",attr:{"data-role":"pending-mapping-table",hidden:"true"}}),this.renderPendingHeader();let c=t.createDiv({cls:"mapping-pagination",attr:{"data-role":"pending-pagination"}});new p.ButtonComponent(c).setButtonText("\u2039").setDisabled(!0),this.pendingPageInfoEl=c.createSpan({text:"0-0 / 0",attr:{"data-role":"pending-page-info"}}),new p.ButtonComponent(c).setButtonText("\u203A").setDisabled(!0)}renderPendingHeader(){this.pendingTableEl.empty();let e=this.pendingTableEl.createDiv({cls:"mapping-header",attr:{role:"row"}});e.createDiv({text:this.plugin.t("mappingIndex")}),e.createDiv({text:this.plugin.t("mappingFolderColumn")}),e.createDiv({text:this.plugin.t("mappingDatasetColumn")}),e.createDiv({text:this.plugin.t("mappingStatusColumn")}),e.createDiv({text:this.plugin.t("mappingActionColumn")})}addPendingMapping(e=!0,t=!0,i=N()){if(!this.folderSelected){new p.Notice(this.plugin.t("chooseFolderFirst"));return}if(this.selectedDatasets.size===0){new p.Notice(this.plugin.t("datasetMissing"));return}let n={id:i,folder:M(this.selectedFolder),datasetIds:Array.from(this.selectedDatasets),enabled:t};this.pendingMappings=this.isEditing?[n]:this.pendingMappings.filter(s=>s.folder!==n.folder).concat(n),e&&new p.Notice(this.plugin.t("pendingAdded")),this.renderPendingRows(),this.resetDraft()}renderPendingRows(){this.renderPendingHeader(),this.pendingMappings.forEach((e,t)=>{let i=this.pendingTableEl.createDiv({cls:"mapping-row",attr:{role:"row"}});se(i,this.plugin,e,t,"toggle-pending-status",()=>{e.enabled=!e.enabled,this.renderPendingRows()},n=>{let s=new p.ExtraButtonComponent(n).setIcon("refresh-cw").setTooltip(this.plugin.t("refreshDatasets")).onClick(()=>{(async()=>{try{await this.plugin.refreshKnowledgeBases(),this.renderDatasetChoices(),new p.Notice(this.plugin.t("datasetsRefreshed",{count:this.plugin.settings.knowledgeBases.length}))}catch(l){this.plugin.showConnectionError(l)}})()}).extraSettingsEl;s.addClass("icon"),s.setAttr("data-action","refresh-pending-mapping"),s.setAttr("aria-label",this.plugin.t("refreshDatasets")),s.setAttr("title",this.plugin.t("refreshDatasets"));let o=new p.ExtraButtonComponent(n).setIcon("trash-2").setTooltip(this.plugin.t("delete")).onClick(()=>{this.pendingMappings=this.pendingMappings.filter(l=>l.id!==e.id),this.renderPendingRows()}).extraSettingsEl;o.addClass("icon"),o.addClass("danger"),o.setAttr("data-action","remove-pending-mapping"),o.setAttr("aria-label",this.plugin.t("delete"))})}),this.updatePendingVisibility()}updatePendingVisibility(){let e=this.pendingMappings.length;this.pendingTableEl&&(this.pendingTableEl.hidden=e===0,this.pendingTableEl.toggleClass("is-hidden",e===0),this.pendingTableEl.style.display=e===0?"none":""),this.pendingEmptyEl&&(this.pendingEmptyEl.hidden=e>0,this.pendingEmptyEl.toggleClass("is-hidden",e>0),this.pendingEmptyEl.style.display=e>0?"none":""),this.pendingPageInfoEl&&this.pendingPageInfoEl.setText(e?`1-${e} / ${e}`:"0-0 / 0"),this.enableAllButtonEl&&(this.enableAllButtonEl.disabled=e===0),this.pauseAllButtonEl&&(this.pauseAllButtonEl.disabled=e===0)}setAllPendingStatus(e){this.pendingMappings=this.pendingMappings.map(t=>({...t,enabled:e})),this.renderPendingRows()}async savePendingMappings(){if(this.pendingMappings.length===0){new p.Notice(this.plugin.t("validationMapping"));return}for(let e of this.pendingMappings)await this.onSave(e);this.close()}selectFolder(e,t){var i,n;if(this.selectedFolder=e,this.folderSelected=!0,this.folderInputEl&&(this.folderInputEl.value=e),this.folderSummaryEl.setText(e||this.plugin.t("rootFolder")),this.folderTreeEl.querySelectorAll(".nav-folder-title, .nav-file-title").forEach(s=>{s.toggleClass("is-selected",s.dataset.folder===e)}),this.datasetTriggerEl.disabled=!1,this.updateDatasetSummary(),t){let s=this.folderSummaryEl.closest(".dropdown-box");(i=s==null?void 0:s.querySelector(".dropdown-menu"))==null||i.setAttr("hidden","true"),(n=s==null?void 0:s.querySelector(".dropdown-trigger"))==null||n.setAttr("aria-expanded","false")}}updateDatasetSummary(){if(!this.datasetSummaryEl)return;if(!this.folderSelected){this.datasetSummaryEl.setText(this.plugin.t("chooseFolderFirst")),this.datasetTriggerEl&&(this.datasetTriggerEl.disabled=!0),this.datasetInputEl&&(this.datasetInputEl.value="",this.datasetInputEl.dataset.datasetId="");return}if(this.selectedDatasets.size===0){this.datasetSummaryEl.setText(this.plugin.t("datasetName")),this.datasetInputEl&&(this.datasetInputEl.value="",this.datasetInputEl.dataset.datasetId="");return}let e=Array.from(this.selectedDatasets),t=e.map(i=>this.plugin.getDatasetName(i));this.datasetSummaryEl.setText(t.join("\u3001")),this.datasetInputEl&&(this.datasetInputEl.value=t.join(","),this.datasetInputEl.dataset.datasetId=e.join(","))}resetDraft(){this.isEditing||(this.folderSelected=!1,this.selectedFolder="",this.selectedDatasets.clear(),this.folderInputEl&&(this.folderInputEl.value=""),this.folderSummaryEl.setText(this.plugin.t("selectFolderPlaceholder")),this.renderDatasetChoices(),this.updateDatasetSummary())}toggleMenu(e,t){if(e.getAttr("aria-expanded")==="true"){e.setAttr("aria-expanded","false"),t.hidden=!0;return}this.closeSiblingDropdowns(t),e.setAttr("aria-expanded","true"),t.hidden=!1}closeSiblingDropdowns(e){let t=(e==null?void 0:e.closest(".dify-sync-modal"))||this.contentEl;t.querySelectorAll(".dropdown-menu").forEach(i=>{var s;if(i===e)return;i.hidden=!0;let n=i.id?t.querySelector(`.dropdown-trigger[aria-controls="${i.id}"]`):(s=i.parentElement)==null?void 0:s.querySelector(".dropdown-trigger");n==null||n.setAttr("aria-expanded","false")})}toggleTreeFolder(e){let t=e.getAttr("aria-expanded")==="true";this.setFolderExpanded(e,!t)}setFolderExpanded(e,t){let i=this.getFolderChildContainer(e);i&&(e.setAttr("aria-expanded",String(t)),e.toggleClass("is-collapsed",!t),i.hidden=!t)}getFolderChildContainer(e){let t=e.dataset.treeTarget,i=t?this.contentEl.querySelector(`#${t}`):null;return i!=null&&i.instanceOf(HTMLElement)?i:null}onClose(){this.contentEl.empty()}};function ne(){return{status:"unknown",checkedAt:"",activeBaseUrl:"",lastSuccessfulBaseUrl:"",datasetCount:0,latencyMs:0}}function De(a){if(f(a))return ie(a.reason)?a.reason:void 0}function be(a){let r=a.document;if(X(r))return r;let e=a.data;return X(e)?e:void 0}function X(a){return f(a)&&typeof a.id=="string"}function we(a){return f(a)?Object.fromEntries(Object.entries(a).filter(r=>Ee(r[1]))):{}}function Ee(a){return f(a)&&typeof a.filePath=="string"&&typeof a.datasetId=="string"&&typeof a.remoteName=="string"&&typeof a.hash=="string"&&typeof a.lastModified=="number"&&typeof a.lastSyncedAt=="string"}function ke(a){let r=ne(),e=f(a)?a:{},t=["unknown","missing_config","connected","failed"].includes(String(e.status))?e.status:r.status,i=ie(e.reason)?e.reason:void 0;return{status:t,checkedAt:S(e,"checkedAt")||r.checkedAt,activeBaseUrl:S(e,"activeBaseUrl")||r.activeBaseUrl,lastSuccessfulBaseUrl:S(e,"lastSuccessfulBaseUrl")||r.lastSuccessfulBaseUrl,datasetCount:v(C(e,"datasetCount")||r.datasetCount,0,1e5,r.datasetCount),latencyMs:v(C(e,"latencyMs")||r.latencyMs,0,6e5,r.latencyMs),error:S(e,"error"),reason:i,statusCode:C(e,"statusCode")}}function Ae(a){for(let r=a.candidates.length-1;r>=0;r--){let e=a.candidates[r];if(!e.ok)return e}}function Ce(a){let r={missing_config:"connectionErrorMissingConfig",auth_failed:"connectionErrorAuthFailed",permission_denied:"connectionErrorPermissionDenied",not_found:"connectionErrorNotFound",timeout:"connectionErrorTimeout",network:"connectionErrorNetwork",server:"connectionErrorServer",path_mismatch:"connectionErrorPathMismatch",rate_limited:"connectionErrorRateLimited",unexpected_response:"connectionErrorUnexpectedResponse",unknown:"connectionErrorUnknown"};return r[a]||r.unknown}function ie(a){return["missing_config","auth_failed","permission_denied","not_found","timeout","network","server","path_mismatch","rate_limited","unexpected_response","unknown"].includes(String(a))}function Te(a){return a<1e3?`${a}ms`:`${(a/1e3).toFixed(1)}s`}function ee(a){return{synced:a.synced,skipped:a.skipped,failed:a.failed}}function te(a){return(a||"").replace(/^https?:\/\//i,"")||"-"}function Pe(a){return new Promise(r=>window.setTimeout(r,a))}function se(a,r,e,t,i,n,s){a.dataset.folder=e.folder,a.dataset.dataset=e.datasetIds.map(h=>r.getDatasetName(h)).join(","),a.dataset.datasetId=e.datasetIds.join(","),a.createDiv({text:String(t+1),cls:"mapping-index"}),a.createDiv().createDiv({text:e.folder||r.t("rootFolder"),cls:"folder-name"});let l=a.createDiv("tags");(e.datasetIds.length?e.datasetIds:[""]).forEach(h=>{l.createSpan({text:h?r.getDatasetName(h):r.t("datasetMissing"),cls:"tag dataset-name"})});let d=a.createDiv().createDiv("mapping-status-control"),u=new p.ToggleComponent(d).setValue(e.enabled).onChange(()=>{Promise.resolve(n()).catch(h=>{new p.Notice(w(h))})});u.toggleEl.setAttr("data-action",i),u.toggleEl.setAttr("aria-label",e.enabled?r.t("enabled"):r.t("disabled")),d.createSpan({text:e.enabled?r.t("enabled"):r.t("disabled"),cls:"mapping-status-label"}),s(a.createDiv("row-actions"))}function N(){return`map-${Date.now()}-${Math.random().toString(36).slice(2,8)}`}function w(a){return a instanceof Error?a.message:f(a)?S(a,"message")||"Unknown error":typeof a=="string"?a:typeof a=="number"||typeof a=="boolean"||typeof a=="bigint"||typeof a=="symbol"?String(a):"Unknown error"}function Me(a){let r=a,e=r.setDestructive;if(typeof e=="function")return e.call(a);let i=r["setWarning"];return typeof i=="function"?i.call(a):a} diff --git a/main.ts b/main.ts index aa213fc..d855bc2 100644 --- a/main.ts +++ b/main.ts @@ -1516,8 +1516,11 @@ class DifySyncSettingTab extends PluginSettingTab { refreshSettingsView(): void { if (this.renderMode === 'declarative') { - this.update(); - return; + const updateSettingTab = (this as unknown as Record)['update']; + if (typeof updateSettingTab === 'function') { + updateSettingTab.call(this); + return; + } } this.renderLegacySettingsView(); diff --git a/manifest.json b/manifest.json index 679be43..fd98f6a 100644 --- a/manifest.json +++ b/manifest.json @@ -1,8 +1,8 @@ { "id": "vault-to-dify", "name": "Vault2Dify", - "version": "1.0.4", - "minAppVersion": "1.13.0", + "version": "1.0.5", + "minAppVersion": "1.12.7", "description": "Sync selected Markdown notes to a Dify Knowledge Base.", "author": "WenBin", "authorUrl": "https://github.com/BeatsAD", diff --git a/package-lock.json b/package-lock.json index 438fed9..e569dcf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vault-to-dify", - "version": "1.0.4", + "version": "1.0.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vault-to-dify", - "version": "1.0.4", + "version": "1.0.5", "license": "MIT", "devDependencies": { "@eslint/js": "^9.39.4", diff --git a/package.json b/package.json index 862f3d9..4b0634d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vault-to-dify", - "version": "1.0.4", + "version": "1.0.5", "description": "Sync your Obsidian Vault to Dify Knowledge Base", "main": "main.js", "scripts": { diff --git a/tests/settings-review-ui.test.mjs b/tests/settings-review-ui.test.mjs index 2870fe1..ffbbc9d 100644 --- a/tests/settings-review-ui.test.mjs +++ b/tests/settings-review-ui.test.mjs @@ -139,7 +139,11 @@ test("settings tab renders restored review layout with native Obsidian setting c "concurrencyOption2: '2 files'", ]); assert.doesNotMatch(settingsTabBlock, /\.setClass\('[^']+\s+[^']+'\)/, "Native Setting classes must be added one class at a time"); - assert.equal(countOccurrences(settingsTabBlock, "this.update();"), 1, "Settings tab should only call update inside refreshSettingsView"); + assert.equal(countOccurrences(settingsTabBlock, "this.update();"), 0, "Settings tab should avoid direct update calls so Obsidian 1.12.7 remains supported"); + sourceContainsAll(settingsTabBlock, [ + "const updateSettingTab = (this as unknown as Record)['update'];", + "updateSettingTab.call(this);", + ]); assert.ok( countOccurrences(settingsTabBlock, "this.refreshSettingsView();") >= 10, "Settings tab actions should refresh through the Obsidian-version-aware helper", diff --git a/versions.json b/versions.json index 2018327..3e42a2e 100644 --- a/versions.json +++ b/versions.json @@ -2,5 +2,6 @@ "1.0.0": "1.1.1", "1.0.2": "1.13.0", "1.0.3": "1.13.0", - "1.0.4": "1.13.0" + "1.0.4": "1.13.0", + "1.0.5": "1.12.7" }