diff --git a/main.js b/main.js index 96f433f..6310899 100644 --- a/main.js +++ b/main.js @@ -3,7 +3,7 @@ THIS IS A GENERATED/BUNDLED FILE BY ESBUILD if you want to view the source, please visit the github repository of this plugin */ -var N=Object.defineProperty;var ae=Object.getOwnPropertyDescriptor;var re=Object.getOwnPropertyNames;var oe=Object.prototype.hasOwnProperty;var le=(a,r)=>{for(var e in r)N(a,e,{get:r[e],enumerable:!0})},ce=(a,r,e,t)=>{if(r&&typeof r=="object"||typeof r=="function")for(let i of re(r))!oe.call(a,i)&&i!==e&&N(a,i,{get:()=>r[i],enumerable:!(t=ae(r,i))||t.enumerable});return a};var de=a=>ce(N({},"__esModule",{value:!0}),a);var Me={};le(Me,{default:()=>x});module.exports=de(Me);var p=require("obsidian");function E(a,r){return`${a}::${r}`}function F(a,r,e,t){let i=[];return a.forEach((n,s)=>{n.filePath===r&&i.push([s,{...n,filePath:e,remoteName:n.remoteName||t(r),deletedLocal:!1}])}),i.forEach(([n,s])=>{a.delete(n),a.set(E(s.datasetId,e),s)}),i.length>0}function B(a,r,e,t,i,n=new Date,s=3e4){let o=E(r,e);if(a.has(o))return!1;let c,l,d=-1;return a.forEach((u,h)=>{if(u.datasetId!==r||u.filePath===e||!u.documentId||!u.deletedLocal||u.hash!==t)return;let g=Date.parse(u.lastSyncedAt);if(!Number.isFinite(g))return;let y=n.getTime()-g;y<0||y>s||g<=d||(c=h,l=u,d=g)}),!c||!l?!1:(a.delete(c),a.set(o,{...l,filePath:e,remoteName:l.remoteName||i(l.filePath),deletedLocal:!1}),!0)}var J=require("obsidian");function _(a,r=""){let e=T(a,r),t=z(a);if(!t)throw new Error(j(e));return t.filter(i=>!!(i!=null&&i.id)).map(i=>({id:i.id,name:i.name||i.id,description:i.description}))}function V(a,r=""){if(!z(a))throw new Error(j(T(a,r)))}function T(a,r=""){let e=/^\s*0?`; top-level fields: ${a.topLevelKeys.join(", ")}`:"";return`Current address did not return a Dify knowledge base API response (${r}${e}).`}function Q(a){return typeof a=="object"&&a!==null&&!Array.isArray(a)}function b(a){let r=(a||"").trim().replace(/\/+$/,"").replace(/\/v1\/datasets$/i,"").replace(/\/v1$/i,"");return r?/^https?:\/\//i.test(r)?r:`http://${r}`:""}function P(a){return(a||"").trim().replace(/^\/+|\/+$/g,"")}function pe(a,r){let e=P(r);return e===""?!0:e.toLowerCase().endsWith(".md")?a===e:a===e||a.startsWith(`${e}/`)}function A(a){if(!a)return{};try{return JSON.parse(a)}catch(r){return{}}}function f(a){return typeof a=="object"&&a!==null&&!Array.isArray(a)}function S(a,r){let e=a[r];return typeof e=="string"?e:void 0}function k(a,r){let e=a[r];return typeof e=="number"?e:void 0}function D(a){return Array.from(new Set(a.map(r=>r.trim()).filter(Boolean)))}function W(a,r){let e=[];return r.filter(t=>t.enabled&&Array.isArray(t.datasetIds)&&t.datasetIds.length>0).forEach(t=>{pe(a,t.folder)&&e.push(...t.datasetIds)}),D(e)}function G(a,r){return a.filter(e=>e.id!==r)}function v(a,r,e,t){let i=Number(a);return Number.isFinite(i)?Math.min(e,Math.max(r,i)):t}var m=class extends Error{constructor(e,t,i,n){super(e);this.name="DifyApiError",this.status=t,this.responseText=i,this.reason=n,Object.setPrototypeOf(this,m.prototype)}get isTransient(){return!this.status||this.status===408||this.status===429||this.status>=500}get isPathMismatch(){return this.status===404||this.status===405}},M=class{constructor(r){this.activeBaseUrl="";this.runtimePathStyle=null;this.host=r}getActiveBaseUrl(){return this.activeBaseUrl}setActiveBaseUrl(r){let e=this.getCandidateBaseUrls(!1);r&&e.includes(r)&&(this.activeBaseUrl=r)}clearActiveBaseUrl(){this.activeBaseUrl=""}async probeConnection(){let r=this.getCandidateBaseUrls(!1),e=new Date().toISOString(),t=[],i=null,n=null;for(let s of r){let o=Date.now();try{let c=await this.listDatasetsFromBaseUrl(s),l=Date.now()-o;return this.activeBaseUrl=s,t.push({baseUrl:s,ok:!0,latencyMs:l,datasetCount:c.length}),{status:"connected",checkedAt:e,activeBaseUrl:s,lastSuccessfulBaseUrl:s,datasetCount:c.length,latencyMs:l,candidates:t,datasets:c}}catch(c){let l=this.toApiError(c),d=C(l);if(i=l,n={baseUrl:s,ok:!1,latencyMs:Date.now()-o,statusCode:l.status,error:l.message,reason:d},t.push(n),!this.shouldTryNextBase(l))break}}return{status:"failed",checkedAt:e,activeBaseUrl:"",datasetCount:0,latencyMs:t.reduce((s,o)=>s+o.latencyMs,0),candidates:t,error:(n==null?void 0:n.error)||(i==null?void 0:i.message)||"No Dify API URL is available.",reason:(n==null?void 0:n.reason)||(i?C(i):"unknown"),statusCode:n==null?void 0:n.statusCode,datasets:[]}}async listDatasets(){let r=[];for(let t=1;t<=50;t++){let i=await this.requestUnknown(`/datasets?page=${t}&limit=${100}`,{method:"GET"}),n=this.extractDatasets(i);if(r.push(...n),!H(i)&&n.length<100)break}return r}extractDatasets(r){return _(r)}async listDatasetsFromBaseUrl(r){let e=[];for(let i=1;i<=50;i++){let n=await this.requestOnce(r,`/datasets?page=${i}&limit=${100}`,{method:"GET"}),s=this.extractDatasets(n);if(e.push(...s),!H(n)&&s.length<100)break}return e}async listDocuments(r){let e=[];for(let i=1;i<=100;i++){let n=await this.requestUnknown(`/datasets/${encodeURIComponent(r)}/documents?page=${i}&limit=${100}`,{method:"GET"}),s=ue(n);if(s.forEach(o=>{e.push(o)}),!H(n)&&s.length<100)break}return e}async createDocumentByText(r,e,t){return this.mutateTextDocument(r,void 0,{name:e,text:t,indexing_technique:"high_quality",process_rule:{mode:"automatic"}})}async updateDocumentByText(r,e,t,i){return this.mutateTextDocument(r,e,{name:t,text:i,process_rule:{mode:"automatic"}})}async getIndexingStatus(r,e){return this.requestUnknown(`/datasets/${encodeURIComponent(r)}/documents/${encodeURIComponent(e)}/indexing-status`,{method:"GET"})}async mutateTextDocument(r,e,t){let i=this.getPathStyleCandidates(),n=null;for(let s of i){let o=e?`/datasets/${encodeURIComponent(r)}/documents/${encodeURIComponent(e)}/${s==="hyphen"?"update-by-text":"update_by_text"}`:`/datasets/${encodeURIComponent(r)}/document/${s==="hyphen"?"create-by-text":"create_by_text"}`;try{let c=await this.request(o,{method:"POST",body:t},he);return this.runtimePathStyle=s,c}catch(c){let l=this.toApiError(c);if(n=l,this.host.settings.apiPathStyle==="auto"&&l.isPathMismatch){this.host.debug(`Dify API path style ${s} failed, trying fallback`,l.message);continue}throw l}}throw n||new m("Unable to resolve Dify document API path.")}getPathStyleCandidates(){return this.host.settings.apiPathStyle==="hyphen"?["hyphen"]:this.host.settings.apiPathStyle==="underscore"?["underscore"]:this.runtimePathStyle?[this.runtimePathStyle,this.runtimePathStyle==="hyphen"?"underscore":"hyphen"]:["hyphen","underscore"]}async requestUnknown(r,e){return this.request(r,e,t=>!0)}async request(r,e,t){let i=this.getCandidateBaseUrls(),n=null;for(let s of i){let o=Math.max(1,this.host.settings.maxRetries+1);for(let c=1;c<=o;c++)try{let l=await this.requestOnce(s,r,e);if(this.activeBaseUrl=s,!t(l))throw new m("Dify returned an unexpected response shape.",void 0,JSON.stringify(l));return l}catch(l){let d=this.toApiError(l);if(n=d,!d.isTransient||c>=o)break;await fe(400*c)}if(n&&!this.shouldTryNextBase(n))throw n}throw n||new m("No Dify API URL is available.")}async requestOnce(r,e,t){let i=`${r}/v1${e}`;this.host.debug(`${t.method} ${i}`);let n=Math.max(5,this.host.settings.requestTimeoutSeconds)*1e3,s=null,o=new Promise((c,l)=>{s=window.setTimeout(()=>{l(new m(`Request timed out after ${this.host.settings.requestTimeoutSeconds}s`))},n)});try{let c={url:i,method:t.method,headers:{Authorization:`Bearer ${this.host.settings.difyApiKey}`,"Content-Type":"application/json"},body:t.body?JSON.stringify(t.body):void 0,throw:!1},l=(0,J.requestUrl)(c),d=await Promise.race([l,o]);if(s!==null&&window.clearTimeout(s),d.status<200||d.status>=300)throw new m(this.extractErrorMessage(d.text,d.status),d.status,d.text);let u=A(d.text);return this.logResponseDiagnostics(i,d.status,u,d.text),me(e)&&V(u,d.text),u}catch(c){throw s!==null&&window.clearTimeout(s),this.toApiError(c)}}getCandidateBaseUrls(r=!0){let e=this.host.settings,i=[b(e.difyApiUrl)],n=D(i.filter(Boolean));return r&&this.activeBaseUrl&&n.includes(this.activeBaseUrl)?[this.activeBaseUrl,...n.filter(s=>s!==this.activeBaseUrl)]:n}shouldTryNextBase(r){return!r.status||r.status===404||r.status>=500}extractErrorMessage(r,e){let t=A(r),i=f(t)?S(t,"message")||S(t,"error")||S(t,"detail")||r||"Unknown error":r||"Unknown error";return`HTTP ${e}: ${String(i).slice(0,300)}`}logResponseDiagnostics(r,e,t,i=""){let n=T(t,i);this.host.debug("Dify API response",{url:r,status:e,topLevelType:n.topLevelType,topLevelKeys:n.topLevelKeys,dataIsArray:n.dataIsArray,dataLength:n.dataLength,looksLikeHtml:n.looksLikeHtml})}toApiError(r){if(r instanceof m)return r;let e=ge(r),t=typeof e.status=="number"?e.status:void 0,i=typeof e.message=="string"?e.message:String(r),n=typeof e.text=="string"?e.text:typeof e.responseText=="string"?e.responseText:void 0,s=ye(e.reason)?e.reason:void 0;return new m(i,t,n,s)}};function H(a){return f(a)&&a.has_more===!0}function ue(a){return(Array.isArray(a)?a:f(a)&&Array.isArray(a.data)?a.data:[]).filter(U)}function U(a){return f(a)&&typeof a.id=="string"}function he(a){if(!f(a))return!1;let r=a.document,e=a.data;return(r===void 0||U(r))&&(e===void 0||U(e))}function ge(a){return f(a)?a:{}}function C(a){if(a.reason)return a.reason;let r=a.message.toLowerCase();return a.status===401?"auth_failed":a.status===403?"permission_denied":a.status===404?"not_found":a.status===405?"path_mismatch":a.status===429?"rate_limited":a.status&&a.status>=500?"server":r.includes("did not return a dify knowledge base api response")||r.includes("received an html page")?"unexpected_response":r.includes("timed out")||r.includes("timeout")?"timeout":r.includes("failed to fetch")||r.includes("network")||r.includes("econnrefused")||r.includes("enotfound")||r.includes("econnreset")?"network":"unknown"}function ye(a){return["missing_config","auth_failed","permission_denied","not_found","timeout","network","server","path_mismatch","rate_limited","unexpected_response","unknown"].includes(String(a))}function me(a){return a==="/datasets"||a.startsWith("/datasets?")}function fe(a){return new Promise(r=>window.setTimeout(r,a))}function Y(a){var t;let r=a.recentSyncResult,e=I(r==null?void 0:r.failed);return{metrics:[{key:"total",value:I(r==null?void 0:r.total),tone:"accent"},{key:"synced",value:I(r==null?void 0:r.synced),tone:"success"},{key:"skipped",value:I(r==null?void 0:r.skipped),tone:"warning"},{key:"failed",value:e,tone:"error"}],connectionTone:Se((t=a.connectionHealth)==null?void 0:t.status),hasRecentSync:!!r}}function Se(a){return a==="connected"?"success":a==="failed"?"error":a==="missing_config"?"warning":"muted"}function I(a){return typeof a=="number"&&Number.isFinite(a)&&a>0?Math.floor(a):0}var ve={schemaVersion:2,initialSetupCompleted:!1,language:"en",difyApiKey:"",difyApiUrl:"",lanApiUrl:"",publicApiUrl:"",endpointMode:"primary",apiPathStyle:"auto",difyKnowledgeId:"",obsidianFolders:[],mappings:[],knowledgeBases:[],lastDatasetRefresh:"",autoSyncEnabled:!1,eventSyncEnabled:!0,syncOnStartup:!0,periodicFullScanEnabled:!0,syncInterval:30,debounceSeconds:8,requestTimeoutSeconds:30,maxRetries:2,maxConcurrent:2,pollIndexStatus:!1,debugLogging:!1,lastSyncTime:"",connectionHealth:te(),recentSyncResult:void 0},K={"zh-CN":{pluginName:"Vault2Dify",ribbon:"\u540C\u6B65\u5230 Dify",commandManual:"Vault2Dify: \u540C\u6B65\u5230 Dify \u77E5\u8BC6\u5E93",commandToggleAuto:"\u5F00\u542F/\u5173\u95ED\u81EA\u52A8\u540C\u6B65",ready:"\u5C31\u7EEA",syncing:"\u6B63\u5728\u540C\u6B65...",syncQueued:"\u5DF2\u6709\u540C\u6B65\u6B63\u5728\u8FD0\u884C\uFF0C\u672C\u6B21\u53D8\u66F4\u5DF2\u52A0\u5165\u961F\u5217\u3002",syncNotice:"\u6B63\u5728\u540C\u6B65\u5230 Dify \u77E5\u8BC6\u5E93...",syncComplete:"\u540C\u6B65\u5B8C\u6210\uFF1A{synced} \u4E2A\u6210\u529F\uFF0C{skipped} \u4E2A\u8DF3\u8FC7\uFF0C{failed} \u4E2A\u5931\u8D25",syncNoChanges:"\u6CA1\u6709\u9700\u8981\u540C\u6B65\u7684\u53D8\u66F4\u3002",syncFailed:"\u540C\u6B65\u5931\u8D25\uFF1A{message}",syncFailedShort:"\u5931\u8D25",validationApiKey:"\u8BF7\u5148\u914D\u7F6E Dify API Key\u3002",validationApiUrl:"\u8BF7\u5148\u914D\u7F6E Dify \u670D\u52A1\u5730\u5740\u3002",validationMapping:"\u8BF7\u81F3\u5C11\u914D\u7F6E\u4E00\u6761\u8DEF\u5F84\u4E0E\u77E5\u8BC6\u5E93\u6620\u5C04\u3002",autoOn:"\u81EA\u52A8\u540C\u6B65\u5DF2\u5F00\u542F",autoOff:"\u81EA\u52A8\u540C\u6B65\u5DF2\u5173\u95ED",settingsTitle:"Vault2Dify",settingsSubtitle:"\u5C06 Obsidian \u7B14\u8BB0\u5E93\u540C\u6B65\u5230 Dify \u77E5\u8BC6\u5E93",settingsHelp:"\u5C06 Obsidian \u7B14\u8BB0\u5E93\u540C\u6B65\u5230 Dify \u77E5\u8BC6\u5E93\u3002",openGithub:"\u6253\u5F00 GitHub \u5F00\u6E90\u9879\u76EE",openHelp:"\u6253\u5F00\u5E2E\u52A9\u6587\u6863",helpModalTitle:"Vault2Dify \u5E2E\u52A9",helpQuickStartTitle:"\u5FEB\u901F\u5F00\u59CB",helpQuickStartBody:"Vault2Dify \u4F1A\u5C06 Obsidian \u7B14\u8BB0\u5E93\u540C\u6B65\u5230\u4F60\u914D\u7F6E\u7684 Dify \u77E5\u8BC6\u5E93\u3002",helpQuickStartList:`\u586B\u5199 Dify API Key\u3002 +var F=Object.defineProperty;var ae=Object.getOwnPropertyDescriptor;var re=Object.getOwnPropertyNames;var oe=Object.prototype.hasOwnProperty;var le=(a,r)=>{for(var e in r)F(a,e,{get:r[e],enumerable:!0})},ce=(a,r,e,t)=>{if(r&&typeof r=="object"||typeof r=="function")for(let i of re(r))!oe.call(a,i)&&i!==e&&F(a,i,{get:()=>r[i],enumerable:!(t=ae(r,i))||t.enumerable});return a};var de=a=>ce(F({},"__esModule",{value:!0}),a);var Me={};le(Me,{default:()=>R});module.exports=de(Me);var p=require("obsidian");function A(a,r){return`${a}::${r}`}function B(a,r,e,t){let i=[];return a.forEach((n,s)=>{n.filePath===r&&i.push([s,{...n,filePath:e,remoteName:n.remoteName||t(r),deletedLocal:!1}])}),i.forEach(([n,s])=>{a.delete(n),a.set(A(s.datasetId,e),s)}),i.length>0}function H(a,r,e,t,i,n=new Date,s=3e4){let o=A(r,e);if(a.has(o))return!1;let l,c,d=-1;return a.forEach((u,h)=>{if(u.datasetId!==r||u.filePath===e||!u.documentId||!u.deletedLocal||u.hash!==t)return;let g=Date.parse(u.lastSyncedAt);if(!Number.isFinite(g))return;let y=n.getTime()-g;y<0||y>s||g<=d||(l=h,c=u,d=g)}),!l||!c?!1:(a.delete(l),a.set(o,{...c,filePath:e,remoteName:c.remoteName||i(c.filePath),deletedLocal:!1}),!0)}var Y=require("obsidian");function V(a,r=""){let e=P(a,r),t=j(a);if(!t)throw new Error(Q(e));return t.filter(i=>!!(i!=null&&i.id)).map(i=>({id:i.id,name:i.name||i.id,description:i.description}))}function z(a,r=""){if(!j(a))throw new Error(Q(P(a,r)))}function P(a,r=""){let e=/^\s*0?`; top-level fields: ${a.topLevelKeys.join(", ")}`:"";return`Current address did not return a Dify knowledge base API response (${r}${e}).`}function W(a){return typeof a=="object"&&a!==null&&!Array.isArray(a)}function b(a){let r=(a||"").trim().replace(/\/+$/,"").replace(/\/v1\/datasets$/i,"").replace(/\/v1$/i,"");return r?/^https?:\/\//i.test(r)?r:`http://${r}`:""}function M(a){return(a||"").trim().replace(/^\/+|\/+$/g,"")}function pe(a,r){let e=M(r);return e===""?!0:e.toLowerCase().endsWith(".md")?a===e:a===e||a.startsWith(`${e}/`)}function k(a){if(!a)return{};try{return JSON.parse(a)}catch(r){return{}}}function f(a){return typeof a=="object"&&a!==null&&!Array.isArray(a)}function S(a,r){let e=a[r];return typeof e=="string"?e:void 0}function C(a,r){let e=a[r];return typeof e=="number"?e:void 0}function D(a){return Array.from(new Set(a.map(r=>r.trim()).filter(Boolean)))}function G(a,r){let e=[];return r.filter(t=>t.enabled&&Array.isArray(t.datasetIds)&&t.datasetIds.length>0).forEach(t=>{pe(a,t.folder)&&e.push(...t.datasetIds)}),D(e)}function J(a,r){return a.filter(e=>e.id!==r)}function v(a,r,e,t){let i=Number(a);return Number.isFinite(i)?Math.min(e,Math.max(r,i)):t}var m=class extends Error{constructor(e,t,i,n){super(e);this.name="DifyApiError",this.status=t,this.responseText=i,this.reason=n,Object.setPrototypeOf(this,m.prototype)}get isTransient(){return!this.status||this.status===408||this.status===429||this.status>=500}get isPathMismatch(){return this.status===404||this.status===405}},I=class{constructor(r){this.activeBaseUrl="";this.runtimePathStyle=null;this.host=r}getActiveBaseUrl(){return this.activeBaseUrl}setActiveBaseUrl(r){let e=this.getCandidateBaseUrls(!1);r&&e.includes(r)&&(this.activeBaseUrl=r)}clearActiveBaseUrl(){this.activeBaseUrl=""}async probeConnection(){let r=this.getCandidateBaseUrls(!1),e=new Date().toISOString(),t=[],i=null,n=null;for(let s of r){let o=Date.now();try{let l=await this.listDatasetsFromBaseUrl(s),c=Date.now()-o;return this.activeBaseUrl=s,t.push({baseUrl:s,ok:!0,latencyMs:c,datasetCount:l.length}),{status:"connected",checkedAt:e,activeBaseUrl:s,lastSuccessfulBaseUrl:s,datasetCount:l.length,latencyMs:c,candidates:t,datasets:l}}catch(l){let c=this.toApiError(l),d=T(c);if(i=c,n={baseUrl:s,ok:!1,latencyMs:Date.now()-o,statusCode:c.status,error:c.message,reason:d},t.push(n),!this.shouldTryNextBase(c))break}}return{status:"failed",checkedAt:e,activeBaseUrl:"",datasetCount:0,latencyMs:t.reduce((s,o)=>s+o.latencyMs,0),candidates:t,error:(n==null?void 0:n.error)||(i==null?void 0:i.message)||"No Dify API URL is available.",reason:(n==null?void 0:n.reason)||(i?T(i):"unknown"),statusCode:n==null?void 0:n.statusCode,datasets:[]}}async listDatasets(){let r=[];for(let t=1;t<=50;t++){let i=await this.requestUnknown(`/datasets?page=${t}&limit=${100}`,{method:"GET"}),n=this.extractDatasets(i);if(r.push(...n),!U(i)&&n.length<100)break}return r}extractDatasets(r){return V(r)}async listDatasetsFromBaseUrl(r){let e=[];for(let i=1;i<=50;i++){let n=await this.requestOnce(r,`/datasets?page=${i}&limit=${100}`,{method:"GET"}),s=this.extractDatasets(n);if(e.push(...s),!U(n)&&s.length<100)break}return e}async listDocuments(r){let e=[];for(let i=1;i<=100;i++){let n=await this.requestUnknown(`/datasets/${encodeURIComponent(r)}/documents?page=${i}&limit=${100}`,{method:"GET"}),s=ue(n);if(s.forEach(o=>{e.push(o)}),!U(n)&&s.length<100)break}return e}async createDocumentByText(r,e,t){return this.mutateTextDocument(r,void 0,{name:e,text:t,indexing_technique:"high_quality",process_rule:{mode:"automatic"}})}async updateDocumentByText(r,e,t,i){return this.mutateTextDocument(r,e,{name:t,text:i,process_rule:{mode:"automatic"}})}async getIndexingStatus(r,e){return this.requestUnknown(`/datasets/${encodeURIComponent(r)}/documents/${encodeURIComponent(e)}/indexing-status`,{method:"GET"})}async mutateTextDocument(r,e,t){let i=this.getPathStyleCandidates(),n=null;for(let s of i){let o=e?`/datasets/${encodeURIComponent(r)}/documents/${encodeURIComponent(e)}/${s==="hyphen"?"update-by-text":"update_by_text"}`:`/datasets/${encodeURIComponent(r)}/document/${s==="hyphen"?"create-by-text":"create_by_text"}`;try{let l=await this.request(o,{method:"POST",body:t},he);return this.runtimePathStyle=s,l}catch(l){let c=this.toApiError(l);if(n=c,this.host.settings.apiPathStyle==="auto"&&c.isPathMismatch){this.host.debug(`Dify API path style ${s} failed, trying fallback`,c.message);continue}throw c}}throw n||new m("Unable to resolve Dify document API path.")}getPathStyleCandidates(){return this.host.settings.apiPathStyle==="hyphen"?["hyphen"]:this.host.settings.apiPathStyle==="underscore"?["underscore"]:this.runtimePathStyle?[this.runtimePathStyle,this.runtimePathStyle==="hyphen"?"underscore":"hyphen"]:["hyphen","underscore"]}async requestUnknown(r,e){return this.request(r,e,t=>!0)}async request(r,e,t){let i=this.getCandidateBaseUrls(),n=null;for(let s of i){let o=Math.max(1,this.host.settings.maxRetries+1);for(let l=1;l<=o;l++)try{let c=await this.requestOnce(s,r,e);if(this.activeBaseUrl=s,!t(c))throw new m("Dify returned an unexpected response shape.",void 0,JSON.stringify(c));return c}catch(c){let d=this.toApiError(c);if(n=d,!d.isTransient||l>=o)break;await fe(400*l)}if(n&&!this.shouldTryNextBase(n))throw n}throw n||new m("No Dify API URL is available.")}async requestOnce(r,e,t){let i=`${r}/v1${e}`;this.host.debug(`${t.method} ${i}`);let n=Math.max(5,this.host.settings.requestTimeoutSeconds)*1e3,s=null,o=new Promise((l,c)=>{s=window.setTimeout(()=>{c(new m(`Request timed out after ${this.host.settings.requestTimeoutSeconds}s`))},n)});try{let l={url:i,method:t.method,headers:{Authorization:`Bearer ${this.host.settings.difyApiKey}`,"Content-Type":"application/json"},body:t.body?JSON.stringify(t.body):void 0,throw:!1},c=(0,Y.requestUrl)(l),d=await Promise.race([c,o]);if(s!==null&&window.clearTimeout(s),d.status<200||d.status>=300)throw new m(this.extractErrorMessage(d.text,d.status),d.status,d.text);let u=k(d.text);return this.logResponseDiagnostics(i,d.status,u,d.text),me(e)&&z(u,d.text),u}catch(l){throw s!==null&&window.clearTimeout(s),this.toApiError(l)}}getCandidateBaseUrls(r=!0){let e=this.host.settings,i=[b(e.difyApiUrl)],n=D(i.filter(Boolean));return r&&this.activeBaseUrl&&n.includes(this.activeBaseUrl)?[this.activeBaseUrl,...n.filter(s=>s!==this.activeBaseUrl)]:n}shouldTryNextBase(r){return!r.status||r.status===404||r.status>=500}extractErrorMessage(r,e){let t=k(r),i=f(t)?S(t,"message")||S(t,"error")||S(t,"detail")||r||"Unknown error":r||"Unknown error";return`HTTP ${e}: ${String(i).slice(0,300)}`}logResponseDiagnostics(r,e,t,i=""){let n=P(t,i);this.host.debug("Dify API response",{url:r,status:e,topLevelType:n.topLevelType,topLevelKeys:n.topLevelKeys,dataIsArray:n.dataIsArray,dataLength:n.dataLength,looksLikeHtml:n.looksLikeHtml})}toApiError(r){if(r instanceof m)return r;let e=ge(r),t=typeof e.status=="number"?e.status:void 0,i=typeof e.message=="string"?e.message:String(r),n=typeof e.text=="string"?e.text:typeof e.responseText=="string"?e.responseText:void 0,s=ye(e.reason)?e.reason:void 0;return new m(i,t,n,s)}};function U(a){return f(a)&&a.has_more===!0}function ue(a){return(Array.isArray(a)?a:f(a)&&Array.isArray(a.data)?a.data:[]).filter(K)}function K(a){return f(a)&&typeof a.id=="string"}function he(a){if(!f(a))return!1;let r=a.document,e=a.data;return(r===void 0||K(r))&&(e===void 0||K(e))}function ge(a){return f(a)?a:{}}function T(a){if(a.reason)return a.reason;let r=a.message.toLowerCase();return a.status===401?"auth_failed":a.status===403?"permission_denied":a.status===404?"not_found":a.status===405?"path_mismatch":a.status===429?"rate_limited":a.status&&a.status>=500?"server":r.includes("did not return a dify knowledge base api response")||r.includes("received an html page")?"unexpected_response":r.includes("timed out")||r.includes("timeout")?"timeout":r.includes("failed to fetch")||r.includes("network")||r.includes("econnrefused")||r.includes("enotfound")||r.includes("econnreset")?"network":"unknown"}function ye(a){return["missing_config","auth_failed","permission_denied","not_found","timeout","network","server","path_mismatch","rate_limited","unexpected_response","unknown"].includes(String(a))}function me(a){return a==="/datasets"||a.startsWith("/datasets?")}function fe(a){return new Promise(r=>window.setTimeout(r,a))}function Z(a){var t;let r=a.recentSyncResult,e=x(r==null?void 0:r.failed);return{metrics:[{key:"total",value:x(r==null?void 0:r.total),tone:"accent"},{key:"synced",value:x(r==null?void 0:r.synced),tone:"success"},{key:"skipped",value:x(r==null?void 0:r.skipped),tone:"warning"},{key:"failed",value:e,tone:"error"}],connectionTone:Se((t=a.connectionHealth)==null?void 0:t.status),hasRecentSync:!!r}}function Se(a){return a==="connected"?"success":a==="failed"?"error":a==="missing_config"?"warning":"muted"}function x(a){return typeof a=="number"&&Number.isFinite(a)&&a>0?Math.floor(a):0}var ve={schemaVersion:2,initialSetupCompleted:!1,language:"en",difyApiKey:"",difyApiUrl:"",lanApiUrl:"",publicApiUrl:"",endpointMode:"primary",apiPathStyle:"auto",difyKnowledgeId:"",obsidianFolders:[],mappings:[],knowledgeBases:[],lastDatasetRefresh:"",autoSyncEnabled:!1,eventSyncEnabled:!0,syncOnStartup:!0,periodicFullScanEnabled:!0,syncInterval:30,debounceSeconds:8,requestTimeoutSeconds:30,maxRetries:2,maxConcurrent:2,pollIndexStatus:!1,debugLogging:!1,lastSyncTime:"",connectionHealth:ne(),recentSyncResult:void 0},O={"zh-CN":{pluginName:"Vault2Dify",ribbon:"\u540C\u6B65\u5230 Dify",commandManual:"Vault2Dify: \u540C\u6B65\u5230 Dify \u77E5\u8BC6\u5E93",commandToggleAuto:"\u5F00\u542F/\u5173\u95ED\u81EA\u52A8\u540C\u6B65",ready:"\u5C31\u7EEA",syncing:"\u6B63\u5728\u540C\u6B65...",syncQueued:"\u5DF2\u6709\u540C\u6B65\u6B63\u5728\u8FD0\u884C\uFF0C\u672C\u6B21\u53D8\u66F4\u5DF2\u52A0\u5165\u961F\u5217\u3002",syncNotice:"\u6B63\u5728\u540C\u6B65\u5230 Dify \u77E5\u8BC6\u5E93...",syncComplete:"\u540C\u6B65\u5B8C\u6210\uFF1A{synced} \u4E2A\u6210\u529F\uFF0C{skipped} \u4E2A\u8DF3\u8FC7\uFF0C{failed} \u4E2A\u5931\u8D25",syncNoChanges:"\u6CA1\u6709\u9700\u8981\u540C\u6B65\u7684\u53D8\u66F4\u3002",syncFailed:"\u540C\u6B65\u5931\u8D25\uFF1A{message}",syncFailedShort:"\u5931\u8D25",validationApiKey:"\u8BF7\u5148\u914D\u7F6E Dify API Key\u3002",validationApiUrl:"\u8BF7\u5148\u914D\u7F6E Dify \u670D\u52A1\u5730\u5740\u3002",validationMapping:"\u8BF7\u81F3\u5C11\u914D\u7F6E\u4E00\u6761\u8DEF\u5F84\u4E0E\u77E5\u8BC6\u5E93\u6620\u5C04\u3002",autoOn:"\u81EA\u52A8\u540C\u6B65\u5DF2\u5F00\u542F",autoOff:"\u81EA\u52A8\u540C\u6B65\u5DF2\u5173\u95ED",settingsTitle:"Vault2Dify",settingsSubtitle:"\u5C06 Obsidian \u7B14\u8BB0\u5E93\u540C\u6B65\u5230 Dify \u77E5\u8BC6\u5E93",settingsHelp:"\u5C06 Obsidian \u7B14\u8BB0\u5E93\u540C\u6B65\u5230 Dify \u77E5\u8BC6\u5E93\u3002",openGithub:"\u6253\u5F00 GitHub \u5F00\u6E90\u9879\u76EE",openHelp:"\u6253\u5F00\u5E2E\u52A9\u6587\u6863",helpModalTitle:"Vault2Dify \u5E2E\u52A9",helpQuickStartTitle:"\u5FEB\u901F\u5F00\u59CB",helpQuickStartBody:"Vault2Dify \u4F1A\u5C06 Obsidian \u7B14\u8BB0\u5E93\u540C\u6B65\u5230\u4F60\u914D\u7F6E\u7684 Dify \u77E5\u8BC6\u5E93\u3002",helpQuickStartList:`\u586B\u5199 Dify API Key\u3002 \u586B\u5199 Dify \u670D\u52A1\u5730\u5740\u3002 \u70B9\u51FB\u6D4B\u8BD5\u8FDE\u63A5\uFF0C\u63D2\u4EF6\u4F1A\u81EA\u52A8\u83B7\u53D6\u77E5\u8BC6\u5E93\u3002 \u65B0\u589E\u8DEF\u5F84\u6620\u5C04\u3002 @@ -117,6 +117,6 @@ Split large knowledge bases by folder and sync in batches. If the connection is also unstable, run Test connection and follow connection troubleshooting first.`,helpDuplicateDocsTitle:"Duplicate documents appear",helpDuplicateDocsBody:"Possible causes include missing legacy sync records, manually changed Dify document names, reset or lost local sync records, or remote documents that were manually deleted and then synced again.",helpDuplicateDocsList:`Confirm duplicate documents in the Dify console. 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."}},x=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 M(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 O(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=(K[((s=this.settings)==null?void 0:s.language)||"zh-CN"]||K["zh-CN"])[e]||K["zh-CN"][e]||e;return t&&Object.entries(t).forEach(([o,c])=>{n=n.replace(new RegExp(`\\{${o}\\}`,"g"),String(c))}),n}debug(...e){this.settings.debugLogging&&console.log("[Dify Sync]",...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?C(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=Ae(i.connectionHealth),n.mappings.length===0&&n.difyKnowledgeId){let s=n.obsidianFolders.length>0?n.obsidianFolders:[""];n.mappings=s.map(o=>({id:L(),folder:o,datasetIds:[n.difyKnowledgeId],enabled:!0}))}return n.mappings=n.mappings.map(s=>({id:s.id||L(),folder:P(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 c=await this.loadDocumentIndexes(o);await this.processTasks(o,c,n),this.settings.lastSyncTime=new Date().toISOString();let l=n.failed>0?this.t("syncComplete",X(n)):void 0;this.settings.recentSyncResult=this.createRecentSyncResult(e,i,n,l),n.failed>0&&n.synced===0?this.markConnectionFailure(new m(l||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 c=this.getConnectionFailureMessage(this.getConnectionFailureReason(o));this.updateStatusBar(this.t("syncFailedShort"),!0),this.showSyncError(this.t("syncFailed",{message:c}))})},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),c=this.hashContent(o),l=this.makeRemoteName(n.path);for(let d of s){B(this.syncRecords,d,n.path,c,g=>this.makeRemoteName(g));let u=this.getRecordKey(d,n.path),h=this.syncRecords.get(u);if(h&&h.hash===c&&h.documentId&&!h.deletedLocal&&h.remoteName===l){t.skipped++;continue}i.push({file:n,content:o,hash:c,datasetId:d,remoteName:l,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(c=>{o.byId.set(c.id,c),c.name&&o.byName.set(c.name,c)}),i.set(n,o)}return i}async processTasks(e,t,i){let n=0,s=0,o=Math.min(this.settings.maxConcurrent,e.length),c=async()=>{for(;n ${l.datasetId}`,u)}}};await Promise.all(Array.from({length:o},c))}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,c;if(o!=null&&o.id)try{c=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;c=await this.client.createDocumentByText(e.datasetId,e.remoteName,e.content)}else c=await this.client.createDocumentByText(e.datasetId,e.remoteName,e.content);let l=be(c),d=(l==null?void 0:l.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&&c.batch&&await this.pollIndexStatus(e.datasetId,c.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:c.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.display()}catch(e){this.showConnectionError(e)}}applyConnectionProbe(e){var n;let t=ke(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=C(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 W(e,this.settings.mappings)}makeRemoteName(e){return e}getRecordKey(e,t){return E(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){F(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 c=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(c,"hash")||"",lastModified:k(c,"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",X(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}},O=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.plugin=t}display(){let{containerEl:e}=this;e.empty(),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")),c=o.extraSettingsEl;o.onClick(()=>this.openLanguageMenu(c)),c.addClass("language-button"),c.setAttr("aria-label",this.plugin.t("languageName")),c.setAttr("title",this.plugin.t("languageName")),c.setAttr("data-action","open-language-menu");let l=new p.ExtraButtonComponent(s).setIcon("help-circle").setTooltip(this.plugin.t("openHelp")).onClick(()=>{new q(this.app,this.plugin).open()}).extraSettingsEl;l.addClass("help-button"),l.setAttr("aria-label",this.plugin.t("openHelp")),l.setAttr("title",this.plugin.t("openHelp")),l.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.display()))()})})});let n=e.getBoundingClientRect();t.showAtPosition({x:n.left,y:n.bottom+4,width:n.width})}renderSyncSummary(e){let t=Y({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;this.createNativeSetting(t,"setting-row").setName(this.createSettingName(this.plugin.t("apiKeyName"),{required:!0})).setTooltip(this.plugin.t("apiKeyDesc")).addText(u=>{i=u.setPlaceholder(this.plugin.t("apiKeyPlaceholder")).setValue(this.plugin.settings.difyApiKey).onChange(h=>{this.plugin.settings.difyApiKey=h.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(u=>{let h=u.setIcon("eye").setTooltip(this.plugin.t("showApiKey")).onClick(()=>{let g=i.type==="password";i.type=g?"text":"password",u.setIcon(g?"eye-off":"eye");let y=this.plugin.t(g?"hideApiKey":"showApiKey");u.setTooltip(y),h.setAttr("aria-label",y),h.setAttr("title",y),h.setAttr("aria-pressed",String(g))}).extraSettingsEl;h.addClass("secret-toggle"),h.setAttr("aria-label",this.plugin.t("showApiKey")),h.setAttr("title",this.plugin.t("showApiKey")),h.setAttr("aria-controls","dify-api-key"),h.setAttr("aria-pressed","false")}),this.createNativeSetting(t,"setting-row").setName(this.createSettingName(this.plugin.t("apiUrlName"),{required:!0})).setTooltip(this.plugin.t("apiUrlDesc")).addText(u=>{u.setPlaceholder(this.plugin.t("urlPlaceholder")).setValue(this.plugin.settings.difyApiUrl).onChange(g=>{this.plugin.settings.difyApiUrl=g.trim(),this.plugin.invalidateConnectionState(),this.plugin.savePluginData()}).inputEl.setAttr("aria-label",this.plugin.t("apiUrlName"))});let n=t.createDiv("dataset-refresh-row"),s=n.createDiv("status-line");s.setAttr("aria-label",this.plugin.t("healthConnection")),s.createSpan({cls:`status-dot ${this.getConnectionStatusToneClass()}`}),s.createEl("strong",{text:this.getConnectionStatusLabel()});let o=n.createDiv("connection-action-buttons"),c=new p.ButtonComponent(o).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.display()))()});c.buttonEl.setAttr("data-action","clear-connection-config"),c.buttonEl.setAttr("aria-label",this.plugin.t("clearConnectionConfig"));let l;l=new p.ButtonComponent(o).setButtonText(this.plugin.t("testConnection")).setClass("native-action-button").onClick(()=>{l.setDisabled(!0),l.setButtonText(this.plugin.t("syncing")),(async()=>{try{await this.plugin.testConnection()}finally{l.setDisabled(!1),l.setButtonText(this.plugin.t("testConnection")),this.display()}})()}),l.buttonEl.setAttr("data-action","test-connection"),l.buttonEl.setAttr("aria-label",this.plugin.t("testConnection"));let d=n.createDiv({cls:"callout",attr:{"data-role":"connection-result"}});d.hidden=!0,d.createEl("strong",{text:this.getConnectionStatusLabel()}),d.createSpan({text:this.getConnectionNote()})}renderMappingSection(e){let{card:t}=this.createReviewSection(e,"mapping-title",this.plugin.t("settingsReviewMappingTitle"),l=>{let d=new p.ButtonComponent(l).setButtonText(this.plugin.t("addMapping")).setCta().onClick(()=>{new R(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.display()}).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 c=this.mainMappingPage*this.mainMappingPageSize;s.slice(c,c+this.mainMappingPageSize).forEach((l,d)=>{this.renderMappingRow(i,l,c+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,c=t===0?0:Math.min(t,(this.mainMappingPage+1)*this.mainMappingPageSize);i.createSpan({text:t===0?this.plugin.t("mappingPageInfoEmpty"):`${o}-${c} / ${t}`,attr:{"data-role":"main-mapping-page-info"}});let l=new p.ButtonComponent(i).setButtonText("\u203A");s.setDisabled(t===0||this.mainMappingPage===0),l.setDisabled(t===0||this.mainMappingPage>=n-1),s.onClick(()=>{this.mainMappingPage=Math.max(0,this.mainMappingPage-1),this.display()}),l.onClick(()=>{this.mainMappingPage=Math.min(n-1,this.mainMappingPage+1),this.display()})}renderMappingRow(e,t,i){let n=e.createDiv({cls:"mapping-row",attr:{role:"row"}});ie(n,this.plugin,t,i,"toggle-mapping-status",async()=>{t.enabled=!t.enabled,await this.plugin.savePluginData(),this.display()},s=>{let o=new p.ExtraButtonComponent(s).setIcon("pencil").setTooltip(this.plugin.t("edit")).onClick(()=>{new R(this.app,this.plugin,t,async l=>{this.plugin.settings.mappings=this.plugin.settings.mappings.map(d=>d.id===l.id?l:d),await this.plugin.savePluginData(),this.display()}).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 c=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=G(this.plugin.settings.mappings,t.id),await this.plugin.savePluginData(),this.display()},{folder:t.folder||this.plugin.t("rootFolder"),datasets:t.datasetIds.map(l=>this.plugin.getDatasetName(l)).join("\u3001")||this.plugin.t("datasetMissing")},l=>this.plugin.t(l)).open()}).extraSettingsEl;c.addClass("icon"),c.addClass("danger"),c.setAttr("data-action","delete-mapping"),c.setAttr("aria-label",this.plugin.t("delete")),c.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(se=>{(async()=>(this.plugin.settings.autoSyncEnabled=se,await this.plugin.savePluginData(),this.plugin.setupAutoSync(),this.display()))()}),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"),c=this.plugin.settings.lastSyncTime?this.plugin.t("lastSync",{time:new Date(this.plugin.settings.lastSyncTime).toLocaleString()}):this.plugin.t("lastSyncInitial");o.createDiv({text:c,cls:"dify-sync-diagnostic-line"}),this.renderRecentSyncResult(o);let l=o.createDiv("sync-actions");new p.ButtonComponent(l).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.display()))()}).buttonEl.setAttr("data-action","toggle-debug-logging"),new p.ButtonComponent(l).setButtonText(this.plugin.t("resetRecords")).onClick(()=>{(async()=>(this.plugin.syncRecords.clear(),await this.plugin.savePluginData(),new p.Notice(this.plugin.t("resetRecordsDone")),this.display()))()}).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.display()}})()}),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"),c=o.createDiv("heading-line"),l=new p.Setting(c).setName(i).setHeading();return l.nameEl.addClass("heading-text"),l.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"),c={}){this.createNativeSetting(e,"advanced-field").setName(this.createSettingName(t,c)).addText(l=>{let d=l.setPlaceholder(o).setValue(i).onChange(u=>{n(u.trim())}).inputEl;d.id=s})}createAdvancedNumber(e,t,i,n,s,o,c){let l=e.createDiv("advanced-field");l.createEl("label",{text:t,attr:{for:c}});let d=l.createEl("input",{attr:{id:c,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 c=new Map(n.map(([d])=>[String(d),d])),l=Object.fromEntries(n.map(([d,u])=>[String(d),u]));this.createNativeSetting(e,"advanced-field").setName(this.createSettingName(t)).addDropdown(d=>{d.addOptions(l).setValue(String(i)).onChange(u=>{let h=c.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 ee(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:ee(e.activeBaseUrl||e.lastSuccessfulBaseUrl)}):e.status==="failed"&&e.error?this.plugin.t("connectionNoteFailed",{message:e.error}):this.plugin.t("connectionNoteIdle")}},q=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,...c]=n.split(s),l=i.createDiv("help-error-item");l.createEl("strong",{text:o.trim()});let d=c.join(s).trim();d&&l.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,c,l=d=>d){super(e);this.title=t,this.message=i,this.confirmText=n,this.cancelText=s,this.onConfirm=o,this.preview=c,this.labelProvider=l}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 c=s.createDiv("confirm-mapping-item");c.createEl("strong",{text:this.pluginLabel("datasetName"),cls:"confirm-mapping-label"}),c.createSpan({text:((g=this.preview)==null?void 0:g.datasets)||"-",cls:"confirm-mapping-value"}).setAttr("data-role","delete-datasets");let l=e.createDiv("modal-actions");new p.ButtonComponent(l).setButtonText(this.cancelText).onClick(()=>this.close()).buttonEl.setAttr("data-action","close-delete-confirm"),new p.ButtonComponent(l).setButtonText(this.confirmText).setWarning().onClick(()=>{(async()=>(await this.onConfirm(),this.close()))()}).buttonEl.setAttr("data-action","confirm-delete")}pluginLabel(e){return this.labelProvider(e)}},R=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:L(),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 c=s.createDiv({cls:"mapping-arrow",attr:{"aria-hidden":"true"}}).createDiv("mapping-arrow-icon");(0,p.setIcon)(c,"arrow-right"),this.renderDatasetPanel(s),this.renderPendingMappings(n);let l=e.createDiv("modal-actions");new p.ButtonComponent(l).setButtonText(this.plugin.t("cancel")).onClick(()=>this.close()).buttonEl.setAttr("data-action","close-modal");let u=new p.ButtonComponent(l).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(l).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",l=>l.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 c=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)),c.addEventListener("input",()=>{let l=c.value.trim().toLowerCase();this.applyFolderSearch(l)})}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",c=>c.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),c=i.get(o)||[];c.push({path:n,type:"folder"}),i.set(o,c)}),t.forEach(n=>{let s=n.lastIndexOf("/"),o=s===-1?"":n.slice(0,s),c=i.get(o)||[];c.push({path:n,type:"file"}),i.set(o,c)}),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,c=`folder-tree-${e.replace(/[^a-zA-Z0-9_-]+/g,"-").toLowerCase()}`,l=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?c: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:l,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:c,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(),c=!e||o.includes(e)||i.some(l=>l.startsWith(`${s}/`)&&l.toLowerCase().includes(e));n.hidden=!c}),this.folderTreeEl.querySelectorAll(".nav-folder-title[data-tree-target]").forEach(n=>{let s=this.getFolderChildContainer(n);if(!s)return;if(e){let c=Array.from(s.querySelectorAll(".nav-folder, .nav-file")).some(l=>!l.hidden);s.hidden=!c,n.toggleClass("is-collapsed",!c),n.setAttr("aria-expanded",String(c));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.plugin.settings.knowledgeBases.length===0){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 c=new p.ButtonComponent(s).setButtonText(this.plugin.t("pauseAll")).setClass("bulk-action").onClick(()=>this.setAllPendingStatus(!1));this.pauseAllButtonEl=c.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 l=t.createDiv({cls:"mapping-pagination",attr:{"data-role":"pending-pagination"}});new p.ButtonComponent(l).setButtonText("\u2039").setDisabled(!0),this.pendingPageInfoEl=l.createSpan({text:"0-0 / 0",attr:{"data-role":"pending-page-info"}}),new p.ButtonComponent(l).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=L()){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:P(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"}});ie(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(c){this.plugin.showConnectionError(c)}})()}).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(c=>c.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 te(){return{status:"unknown",checkedAt:"",activeBaseUrl:"",lastSuccessfulBaseUrl:"",datasetCount:0,latencyMs:0}}function De(a){if(f(a))return ne(a.reason)?a.reason:void 0}function be(a){let r=a.document;if(Z(r))return r;let e=a.data;return Z(e)?e:void 0}function Z(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 Ae(a){let r=te(),e=f(a)?a:{},t=["unknown","missing_config","connected","failed"].includes(String(e.status))?e.status:r.status,i=ne(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(k(e,"datasetCount")||r.datasetCount,0,1e5,r.datasetCount),latencyMs:v(k(e,"latencyMs")||r.latencyMs,0,6e5,r.latencyMs),error:S(e,"error"),reason:i,statusCode:k(e,"statusCode")}}function ke(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 ne(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 X(a){return{synced:a.synced,skipped:a.skipped,failed:a.failed}}function ee(a){return(a||"").replace(/^https?:\/\//i,"")||"-"}function Pe(a){return new Promise(r=>window.setTimeout(r,a))}function ie(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 c=a.createDiv("tags");(e.datasetIds.length?e.datasetIds:[""]).forEach(h=>{c.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 L(){return`map-${Date.now()}-${Math.random().toString(36).slice(2,8)}`}function w(a){return f(a)&&S(a,"message")||String(a)} +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){this.settings.debugLogging&&console.log("[Dify Sync]",...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=Ae(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.display()}catch(e){this.showConnectionError(e)}}applyConnectionProbe(e){var n;let t=ke(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 A(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=k(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.plugin=t}display(){let{containerEl:e}=this;e.empty(),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.display()))()})})});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.display()))()});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.display()}})()}),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.display()}).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.display()}),c.onClick(()=>{this.mainMappingPage=Math.min(n-1,this.mainMappingPage+1),this.display()})}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.display()},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.display()}).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.display()},{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.display()))()}),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.display()))()}).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.display()))()}).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.display()}})()}),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"),new p.ButtonComponent(c).setButtonText(this.confirmText).setWarning().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 Ae(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 ke(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 f(a)&&S(a,"message")||String(a)} diff --git a/main.ts b/main.ts index 561d02f..05ebb05 100644 --- a/main.ts +++ b/main.ts @@ -1693,7 +1693,9 @@ class DifySyncSettingTab extends PluginSettingTab { private renderConnectionSection(containerEl: HTMLElement) { const { card } = this.createReviewSection(containerEl, 'connection-title', this.plugin.t('settingsReviewConnectionTitle')); let keyInput: HTMLInputElement; - this.createNativeSetting(card, 'setting-row') + const apiKeySetting = this.createNativeSetting(card, 'setting-row'); + apiKeySetting.controlEl.addClass('has-secret-toggle'); + apiKeySetting .setName(this.createSettingName(this.plugin.t('apiKeyName'), { required: true })) .setTooltip(this.plugin.t('apiKeyDesc')) .addText((text) => { @@ -2653,7 +2655,9 @@ class MappingEditorModal extends Modal { private renderDatasetChoices() { this.datasetOptionsEl.empty(); + this.datasetOptionsEl.removeClass('is-empty'); if (this.plugin.settings.knowledgeBases.length === 0) { + this.datasetOptionsEl.addClass('is-empty'); this.datasetOptionsEl.createDiv({ text: this.plugin.t('datasetsDropdownEmpty'), cls: 'dataset-empty-state' }); return; } diff --git a/styles.css b/styles.css index 30adbe7..ed480d4 100644 --- a/styles.css +++ b/styles.css @@ -9,7 +9,7 @@ } .dify-sync-error { - color: var(--text-error) !important; + color: var(--text-error); font-weight: bold; } @@ -105,7 +105,7 @@ .dify-sync-settings .is-hidden, .dify-sync-modal .is-hidden { - display: none !important; + display: none; } /* Theme bridge: Obsidian variables shared by the settings page and plugin modals. */ @@ -192,32 +192,30 @@ } .dify-sync-settings .heading-text { - display: inline-block !important; - position: static !important; - width: auto !important; - max-width: 100% !important; - flex: 0 0 auto !important; - height: auto !important; - clip: auto !important; - overflow: visible !important; - visibility: visible !important; - margin: 0 !important; - padding: 0 !important; - text-indent: 0 !important; + display: inline-block; + position: static; + width: auto; + max-width: 100%; + flex: 0 0 auto; + height: auto; + clip: auto; + overflow: visible; + visibility: visible; + margin: 0; + padding: 0; color: var(--text-normal); text-align: left; } .dify-sync-settings .title-line .heading-text { - all: unset; - display: block !important; + display: block; box-sizing: border-box; - width: auto !important; - max-width: 100% !important; - margin: 0 !important; - padding: 0 !important; - text-indent: 0 !important; + width: auto; + max-width: 100%; + margin: 0; + padding: 0; color: var(--text-normal); + font-family: inherit; text-align: left; font-size: 1.45rem; font-weight: 720; @@ -238,7 +236,7 @@ .dify-sync-settings .title-line > .heading-text, .dify-sync-settings .title-line > .settings-subtitle { align-self: flex-start; - margin-left: 0 !important; + margin-left: 0; text-align: left; } @@ -406,11 +404,11 @@ section[aria-labelledby="mapping-title"] .section-card { min-height: 2.25rem; } -.dify-sync-settings .setting-item-control:has(.secret-toggle) { +.dify-sync-settings .setting-item-control.has-secret-toggle { position: relative; } -.dify-sync-settings .setting-item-control:has(.secret-toggle) input { +.dify-sync-settings .setting-item-control.has-secret-toggle input { padding-inline-end: 3rem; } @@ -433,14 +431,14 @@ section[aria-labelledby="mapping-title"] .section-card { top: 50%; right: 8px; width: 30px; - height: 28px !important; - min-height: 28px !important; - max-height: 28px !important; - padding: 0 !important; + height: 28px; + min-height: 28px; + max-height: 28px; + padding: 0; line-height: 1; - border: 0 !important; - background: transparent !important; - box-shadow: none !important; + border: 0; + background: transparent; + box-shadow: none; color: var(--dify-prototype-muted); transform: translateY(-50%); } @@ -456,9 +454,9 @@ section[aria-labelledby="mapping-title"] .section-card { .dify-sync-settings .secret-toggle:hover, .dify-sync-settings .secret-toggle:active, .dify-sync-settings .secret-toggle:focus-visible { - border: 0 !important; - background: transparent !important; - box-shadow: none !important; + border: 0; + background: transparent; + box-shadow: none; color: var(--text-normal); transform: translateY(-50%); } @@ -910,7 +908,7 @@ section[aria-labelledby="mapping-title"] .section-card { grid-auto-rows: max-content; } -.dify-sync-modal.mapping-modal #dataset-dropdown-menu:has(.dataset-empty-state) { +.dify-sync-modal.mapping-modal #dataset-dropdown-menu.is-empty { align-content: center; } @@ -931,16 +929,16 @@ section[aria-labelledby="mapping-title"] .section-card { width: 100%; text-align: left; margin: 0.125rem 0; - border-color: transparent !important; - background: transparent !important; - box-shadow: none !important; + border-color: transparent; + background: transparent; + box-shadow: none; } .dify-sync-modal .select-option:hover, .dify-sync-modal .select-option:focus-visible { - border-color: transparent !important; - background: var(--background-modifier-hover) !important; - box-shadow: none !important; + border-color: transparent; + background: var(--background-modifier-hover); + box-shadow: none; } .dify-sync-settings .help-icon { @@ -1284,8 +1282,8 @@ section[aria-labelledby="mapping-title"] .section-card { } .dify-sync-modal.mapping-modal .dify-sync-obsidian-folder-tree .dify-sync-folder-toggle { - position: static !important; - inset: auto !important; + position: static; + inset: auto; display: inline-flex; align-items: center; justify-content: center; @@ -1295,10 +1293,10 @@ section[aria-labelledby="mapping-title"] .section-card { min-width: 24px; height: 28px; min-height: 28px; - margin: 0 !important; + margin: 0; padding: 0; line-height: 1; - transform: none !important; + transform: none; color: var(--text-faint); } @@ -1652,8 +1650,8 @@ section[aria-labelledby="mapping-title"] .section-card { .diagnostic-body, .dify-sync-diagnostic-line ) { - font-size: var(--dify-body-font-size) !important; - font-weight: 400 !important; + font-size: var(--dify-body-font-size); + font-weight: 400; } .toast-region { @@ -1705,21 +1703,13 @@ section[aria-labelledby="mapping-title"] .section-card { /* Obsidian shell scrollbar workaround: hide the outer native settings scrollbar while preserving plugin scroll behavior. */ .dify-sync-outer-scroll-host { - overflow-y: hidden !important; - scrollbar-width: none !important; - -ms-overflow-style: none !important; + overflow-y: hidden; } .dify-sync-inner-scroll-host { overscroll-behavior: contain; } -.dify-sync-outer-scroll-host::-webkit-scrollbar { - width: 0 !important; - height: 0 !important; - display: none !important; -} - .dify-sync-settings .app { --font: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans SC", sans-serif; --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; @@ -1765,8 +1755,8 @@ section[aria-labelledby="mapping-title"] .section-card { } .dify-sync-settings .app .metric-value { - font-size: 22px !important; - font-weight: 720 !important; + font-size: 22px; + font-weight: 720; } .dify-sync-settings .app .section-card, diff --git a/tests/settings-review-ui.test.mjs b/tests/settings-review-ui.test.mjs index 3d8e7a5..afa322c 100644 --- a/tests/settings-review-ui.test.mjs +++ b/tests/settings-review-ui.test.mjs @@ -55,6 +55,19 @@ function getCssBlock(source, selector) { return source.slice(start, end + 2); } +test("styles avoid Obsidian Marketplace CSS lint warning patterns", () => { + for (const [pattern, label] of [ + [/!important/, "important overrides"], + [/:has\(/, "broad :has selectors"], + [/\ball\s*:/, "all shorthand resets"], + [/\btext-indent\s*:/, "text-indent resets"], + [/\bscrollbar-width\s*:/, "Firefox scrollbar styling"], + [/::-webkit-scrollbar/, "WebKit scrollbar styling"], + ]) { + assert.doesNotMatch(stylesSource, pattern, `styles.css should not use ${label}`); + } +}); + test("settings tab renders restored review layout with native Obsidian setting controls", () => { const settingsTabBlock = mainSource.slice( mainSource.indexOf("class DifySyncSettingTab"), @@ -154,16 +167,11 @@ test("settings modal content hides scrollbars without disabling scroll", () => { ]); sourceContainsAll(stylesSource, [ ".dify-sync-outer-scroll-host", - "overflow-y: hidden !important;", - "scrollbar-width: none !important;", - "-ms-overflow-style: none !important;", + "overflow-y: hidden;", ".dify-sync-inner-scroll-host", "overscroll-behavior: contain;", - ".dify-sync-outer-scroll-host::-webkit-scrollbar", - "width: 0 !important;", - "height: 0 !important;", - "display: none !important;", ]); + assert.doesNotMatch(stylesSource, /scrollbar-width|::-webkit-scrollbar|-ms-overflow-style/, "Scrollbar workaround should avoid marketplace CSS scrollbar warnings"); assert.equal( mainSource.includes("this.addScrollbarHost(document.body);"), false, @@ -365,7 +373,7 @@ test("settings topbar renders localized plugin subtitle below the title", () => ".dify-sync-settings .title-line > .heading-text,", ".dify-sync-settings .title-line > .settings-subtitle", "align-self: flex-start;", - "margin-left: 0 !important;", + "margin-left: 0;", "text-align: left;", ]); }); @@ -463,6 +471,7 @@ test("connection initial labels, placeholders, status dot, and summary metrics a ]); sourceContainsAll(mainSource, [ ".setName(this.createSettingName(this.plugin.t('apiKeyName'), { required: true }))", + "apiKeySetting.controlEl.addClass('has-secret-toggle')", ".setName(this.createSettingName(this.plugin.t('apiUrlName'), { required: true }))", "required.className = 'required-marker';", ]); @@ -483,17 +492,17 @@ test("connection initial labels, placeholders, status dot, and summary metrics a "box-shadow: none;", ".dify-sync-settings .native-setting-row .setting-item-description", "display: none;", - ".dify-sync-settings .setting-item-control:has(.secret-toggle) input", + ".dify-sync-settings .setting-item-control.has-secret-toggle input", "padding-inline-end: 3rem;", "display: inline-flex;", "align-items: center;", "justify-content: center;", "top: 50%;", "right: 8px;", - "height: 28px !important;", - "min-height: 28px !important;", - "max-height: 28px !important;", - "padding: 0 !important;", + "height: 28px;", + "min-height: 28px;", + "max-height: 28px;", + "padding: 0;", "line-height: 1;", "transform: translateY(-50%);", ".dify-sync-settings .secret-toggle .svg-icon", @@ -635,6 +644,8 @@ test("mapping modal implements restored builder, pending mappings, and delete pr "pendingEmptyDesc", "pendingAdded", "datasetsDropdownEmpty", + "this.datasetOptionsEl.removeClass('is-empty');", + "this.datasetOptionsEl.addClass('is-empty');", "dataset-empty-state", "dataset-checkmark", "setIcon(checkmark, 'check')", @@ -868,8 +879,8 @@ test("only API key and Dify service URL retain native setting help text", () => "white-space: nowrap;", ".dify-sync-settings .heading-text {", "width: fit-content;", - "flex: 0 0 auto !important;", - "margin: 0 !important;", + "flex: 0 0 auto;", + "margin: 0;", ]); assert.match( @@ -879,8 +890,8 @@ test("only API key and Dify service URL retain native setting help text", () => ); assert.match( getCssBlock(stylesSource, ".dify-sync-settings .title-line .heading-text {"), - /all: unset;[\s\S]*padding: 0[\s\S]*text-indent: 0/, - "Settings topbar heading should reset native/theme heading offsets instead of moving the title group", + /display: block;[\s\S]*padding: 0;[\s\S]*font-family: inherit/, + "Settings topbar heading should explicitly reset native/theme heading offsets instead of moving the title group", ); assert.match( mainSource, @@ -968,7 +979,7 @@ test("major settings headings align with the background cards", () => { ".dify-sync-settings .title-line,", ".dify-sync-settings .heading-line {", ".dify-sync-settings .title-line .heading-text {", - "all: unset;", + "font-family: inherit;", ]); assert.doesNotMatch( @@ -1002,9 +1013,8 @@ test("test vault plugin styles keep natural topbar title alignment", () => { ".dify-sync-settings .title-line,", ".dify-sync-settings .heading-line {", ".dify-sync-settings .title-line .heading-text {", - "all: unset;", - "padding: 0", - "text-indent: 0", + "font-family: inherit;", + "padding: 0;", ]); assert.doesNotMatch( source, @@ -1073,9 +1083,9 @@ test("custom dropdowns are scoped to the mapping modal", () => { "height: 14px;", "align-self: center;", ".dify-sync-modal .select-option", - "border-color: transparent !important;", - "background: transparent !important;", - "box-shadow: none !important;", + "border-color: transparent;", + "background: transparent;", + "box-shadow: none;", ]); assert.match( @@ -1156,6 +1166,7 @@ test("mapping modal picker columns stay constrained within the dialog", () => { ".dify-sync-modal.mapping-modal #folder-dropdown-menu,", ".dify-sync-modal.mapping-modal #dataset-dropdown-menu", "height: 440px;", + ".dify-sync-modal.mapping-modal #dataset-dropdown-menu.is-empty", ".dify-sync-modal .dataset-select-row", "grid-template-columns: minmax(0, 1fr) auto;", "gap: 8px;",